6.S081的Lab学习——Lab1: Xv6 and Unix utilities

文章目录

  • 前言
  • 一、启动xv6(难度:Easy)
    • 解析:
  • 二、sleep(难度:Easy)
    • 解析:
  • 三、pingpong(难度:Easy)
    • 解析:
  • 四、Primes(素数,难度:Moderate/Hard)
    • 解析:
  • 五、find(难度:Moderate)
    • 解析:
  • 六、xargs(难度:Moderate)
    • 解析:
  • 总结


前言

一个本硕双非的小菜鸡,备战24年秋招。打算尝试6.S081,将它的Lab逐一实现,并记录期间心酸历程。
代码下载

官方网站:6.S081官方网站

安装方式:
通过 APT 安装 (Debian/Ubuntu)
确保你的 debian 版本运行的是 “bullseye” 或 “sid”(在 ubuntu 上,这可以通过运行 cat /etc/debian_version 来检查),然后运行:

sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu 

(“buster”上的 QEMU 版本太旧了,所以你必须单独获取。

qemu-system-misc 修复
此时此刻,似乎软件包 qemu-system-misc 收到了一个更新,该更新破坏了它与我们内核的兼容性。如果运行 make qemu 并且脚本在 qemu-system-riscv64 -machine virt -bios none -kernel/kernel -m 128M -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 之后出现挂起

则需要卸载该软件包并安装旧版本:

  $ sudo apt-get remove qemu-system-misc
  $ sudo apt-get install qemu-system-misc=1:4.2-3ubuntu6

在 Arch 上安装

sudo pacman -S riscv64-linux-gnu-binutils riscv64-linux-gnu-gcc riscv64-linux-gnu-gdb qemu-arch-extra

测试您的安装
若要测试安装,应能够检查以下内容:

$ riscv64-unknown-elf-gcc --version
riscv64-unknown-elf-gcc (GCC) 10.1.0
...

$ qemu-system-riscv64 --version
QEMU emulator version 5.1.0

您还应该能够编译并运行 xv6: 要退出 qemu,请键入:Ctrl-a x。

# in the xv6 directory
$ make qemu
# ... lots of output ...
init: starting sh
$

一、启动xv6(难度:Easy)

获取实验室的xv6源代码并切换到util分支

$ git clone git://g.csail.mit.edu/xv6-labs-2020
Cloning into 'xv6-labs-2020'...
...
$ cd xv6-labs-2020
$ git checkout util
Branch 'util' set up to track remote branch 'util' from 'origin'.
Switched to a new branch 'util'

Xv6-labs-2020存储库与本书的xv6-riscv稍有不同;它主要添加一些文件。如果你好奇的话,可以执行git log:

$ git log

您将需要使用Git版本控制系统管理和提交文件以及后续的实验室作业。接下来,切换到一个分支(执行git checkout util),其中包含针对该实验室定制的xv6版本。要了解关于Git的更多信息,请查看Git用户手册。Git允许您跟踪对代码所做的更改。例如,如果你完成了其中一个练习,并且想检查你的进度,你可以通过运行以下命令来提交你的变化:

$ git commit -am 'my solution for util lab exercise 1'
Created commit 60d2135: my solution for util lab exercise 1
 1 files changed, 1 insertions(+), 0 deletions(-)
$

您可以使用git diff命令跟踪您的更改。运行git diff将显示自上次提交以来对代码的更改,git diff origin/util将显示相对于初始xv6-labs-2020代码的更改。这里,origin/xv6-labs-2020是git分支的名称,它是包含您下载的初始代码分支。

构建并运行xv6

$ make qemu
riscv64-unknown-elf-gcc    -c -o kernel/entry.o kernel/entry.S
riscv64-unknown-elf-gcc -Wall -Werror -O -fno-omit-frame-pointer -ggdb -DSOL_UTIL -MD -mcmodel=medany -ffreestanding -fno-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie   -c -o kernel/start.o kernel/start.c
...  
riscv64-unknown-elf-ld -z max-page-size=4096 -N -e main -Ttext 0 -o user/_zombie user/zombie.o user/ulib.o user/usys.o user/printf.o user/umalloc.o
riscv64-unknown-elf-objdump -S user/_zombie > user/zombie.asm
riscv64-unknown-elf-objdump -t user/_zombie | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > user/zombie.sym
mkfs/mkfs fs.img README  user/xargstest.sh user/_cat user/_echo user/_forktest user/_grep user/_init user/_kill user/_ln user/_ls user/_mkdir user/_rm user/_sh user/_stressfs user/_usertests user/_grind user/_wc user/_zombie 
nmeta 46 (boot, super, log blocks 30 inode blocks 13, bitmap blocks 1) blocks 954 total 1000
balloc: first 591 blocks have been allocated
balloc: write bitmap block at sector 45
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0

xv6 kernel is booting

hart 2 starting
hart 1 starting
init: starting sh
$

如果你在提示符下输入 ls,你会看到类似如下的输出:

$ ls
.              1 1 1024
..             1 1 1024
README         2 2 2059
xargstest.sh   2 3 93
cat            2 4 24256
echo           2 5 23080
forktest       2 6 13272
grep           2 7 27560
init           2 8 23816
kill           2 9 23024
ln             2 10 22880
ls             2 11 26448
mkdir          2 12 23176
rm             2 13 23160
sh             2 14 41976
stressfs       2 15 24016
usertests      2 16 148456
grind          2 17 38144
wc             2 18 25344
zombie         2 19 22408
console        3 20 0

这些是mkfs在初始文件系统中包含的文件;大多数是可以运行的程序。你刚刚跑了其中一个:ls。

xv6没有ps命令,但是如果您键入Ctrl-p,内核将打印每个进程的信息。如果现在尝试,您将看到两行:一行用于init,另一行用于sh。

退出 qemu : Ctrl-a x。

解析:

没啥好说的,配置环境是个大关。
按着官方文档来就好了,6.S081官方环境配置链接
唯一问题是:安装RISC-V交叉编译工具 超级无敌巨坑!!!估计其他环境搭建文章肯定也提到这事了。
以下这句官方配置代码,仅限于ubuntu 20.04,我之前使用 ubuntu 16.04报了个镜像源链接错误,害得我换了个遍也没解决,最后无奈下载ubuntu 20.04。

sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu 

剩下什么安装QEMU,下载源码都没啥,一步步按着官方操作就行。

最后make qemu一下,ls出结果,Ctrl-a按完松手再按x退出。
在这里插入图片描述

二、sleep(难度:Easy)

实现xv6的UNIX程序sleep:您的sleep应该暂停到用户指定的计时数。一个滴答(tick)是由xv6内核定义的时间概念,即来自定时器芯片的两个中断之间的时间。您的解决方案应该在文件user/sleep.c中

提示:

在你开始编码之前,请阅读《book-riscv-rev1》的第一章

看看其他的一些程序(如/user/echo.c, /user/grep.c, /user/rm.c)查看如何获取传递给程序的命令行参数

如果用户忘记传递参数,sleep应该打印一条错误信息

命令行参数作为字符串传递; 您可以使用atoi将其转换为数字(详见/user/ulib.c)

使用系统调用sleep

请参阅kernel/sysproc.c以获取实现sleep系统调用的xv6内核代码(查找sys_sleep),user/user.h提供了sleep的声明以便其他程序调用,用汇编程序编写的user/usys.S可以帮助sleep从用户区跳转到内核区。

确保main函数调用exit()以退出程序。

将你的sleep程序添加到Makefile中的UPROGS中;完成之后,make qemu将编译您的程序,并且您可以从xv6的shell运行它。

看看Kernighan和Ritchie编著的《C程序设计语言》(第二版)来了解C语言。

从xv6 shell运行程序:

$ make qemu
...
init: starting sh
$ sleep 10
(nothing happens for a little while)
$

如果程序在如上所示运行时暂停,则解决方案是正确的。运行make grade看看你是否真的通过了睡眠测试。

请注意,make grade运行所有测试,包括下面作业的测试。如果要对一项作业运行成绩测试,请键入(不要启动XV6,在外部终端下使用):

$ ./grade-lab-util sleep

这将运行与sleep匹配的成绩测试。或者,您可以键入:

$ make GRADEFLAGS=sleep grade

效果是一样的。

解析:

这题练手题,明白int argc, char *argv[]都是啥,会调sleep函数就ok。
argc是命令行总的参数个数 。
argv[]为保存命令行参数的字符串指针。其中第0个参数是程序的全名,以后的参数为命令行后面跟的用户输入的参数。

就比如说该命令sleep 10。其中argc就应该是1,argv[1]就是输入的10。

答案:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
	if (argc != 2) {
		fprintf(2, "usage: sleep pattern [file ...]\n");
		exit(1);
	}
	sleep(atoi(argv[1]));
	exit(0);
}

记得修改makefile文件,在152行那里添加

$U/_sleep\

如果出现/usr/bin/env: “python”: 没有那个文件或目录,这个错误。把grade-lab-util中第一句改成:

#!/usr/bin/env python3

就好了

在这里插入图片描述

三、pingpong(难度:Easy)

使用管道编写prime sieve(筛选素数)的并发版本。这个想法是由Unix管道的发明者Doug McIlroy提出的。请查看这个网站(翻译在下面),该网页中间的图片和周围的文字解释了如何做到这一点。您的解决方案应该在user/primes.c文件中。

提示:

使用pipe来创造管道

使用fork创建子进程

使用read从管道中读取数据,并且使用write向管道中写入数据

使用getpid获取调用进程的pid

将程序加入到Makefile的UPROGS

xv6上的用户程序有一组有限的可用库函数。您可以在user/user.h中看到可调用的程序列表;源代码(系统调用除外)位于user/ulib.c、user/printf.c和user/umalloc.c中。

运行程序应得到下面的输出

$ make qemu
...
init: starting sh
$ pingpong
4: received ping
3: received pong
$

如果您的程序在两个进程之间交换一个字节并产生如上所示的输出,那么您的解决方案是正确的。

解析:

通过管道,记得多做异常判断和使用完了就要关闭读写。

答案:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fcntl.h"
#include "user/user.h"
#include "kernel/param.h"

int main(int argc, char *argv[])
{
	int p1[2], p2[2];
	pipe(p1);//子进程->父进程
	pipe(p2);//父进程->子进程
	if (argc != 1) {
		printf("Parameter missing\n");
		exit(0);
	}

	char *buf = argv[1];

	int pid = fork();

	if (pid > 0) {
		close(p1[1]);
		close(p2[0]);

		if (write(p2[1], &buf, sizeof(char)) != sizeof(char)) {
			printf(" father write error \n");
			exit(0);
		}
		if (read(p1[0], &buf, sizeof(char)) != sizeof(char)) {
			printf(" father read error \n");
			exit(0);
		} else {
			printf("%d: received pong \n", getpid());
			exit(0);
		}
		close(p1[0]);
		close(p2[1]);
	} else if (pid == 0) {
		close(p1[0]);
		close(p2[1]);

		if (read(p2[0], &buf, sizeof(char)) != sizeof(char)) {
			printf(" child read error \n");
			exit(0);
		}
		if (write(p1[1], &buf, sizeof(char)) != sizeof(char)) {
			printf(" child write error \n");
			exit(0);
		} else {
			printf("%d: received ping \n", getpid());
			exit(0);
		}
		close(p1[1]);
		close(p2[0]);
	} else {
		printf(" fork error!\n ");
		close(p1[0]);
		close(p1[1]);
		close(p2[0]);
		close(p2[1]);
		exit(0);
	}
}

结果:
在这里插入图片描述

四、Primes(素数,难度:Moderate/Hard)

使用管道编写prime sieve(筛选素数)的并发版本。这个想法是由Unix管道的发明者Doug McIlroy提出的。请查看这个网站(翻译在下面),该网页中间的图片和周围的文字解释了如何做到这一点。您的解决方案应该在user/primes.c文件中。

您的目标是使用pipe和fork来设置管道。第一个进程将数字2到35输入管道。对于每个素数,您将安排创建一个进程,该进程通过一个管道从其左邻居读取数据,并通过另一个管道向其右邻居写入数据。由于xv6的文件描述符和进程数量有限,因此第一个进程可以在35处停止。

提示:

请仔细关闭进程不需要的文件描述符,否则您的程序将在第一个进程达到35之前就会导致xv6系统资源不足。

一旦第一个进程达到35,它应该使用wait等待整个管道终止,包括所有子孙进程等等。因此,主primes进程应该只在打印完所有输出之后,并且在所有其他primes进程退出之后退出。

提示:当管道的write端关闭时,read返回零。

最简单的方法是直接将32位(4字节)int写入管道,而不是使用格式化的ASCII I/O。

您应该仅在需要时在管线中创建进程。

将程序添加到Makefile中的UPROGS

如果您的解决方案实现了基于管道的筛选并产生以下输出,则是正确的:

$ make qemu
...
init: starting sh
$ primes
prime 2
prime 3
prime 5
prime 7
prime 11
prime 13
prime 17
prime 19
prime 23
prime 29
prime 31
$

解析:

难点在于看懂那个文章和递归遍历
参考:

p = get a number from left neighbor
print p
loop:
    n = get a number from left neighbor
    if (p does not divide n)
        send n to right neighbor
p = 从左邻居中获取一个数
print p
loop:
    n = 从左邻居中获取一个数
    if (n不能被p整除)
        将n发送给右邻居

生成进程可以将数字2、3、4、…、1000输入管道的左端:行中的第一个进程消除2的倍数,第二个进程消除3的倍数,第三个进程消除5的倍数,依此类推。

答案:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fcntl.h"
#include "user/user.h"
#include "kernel/param.h"

int lpipe_first_data(int lpipe[2], int *dst)
{
  if (read(lpipe[0], dst, sizeof(int)) == sizeof(int)) {
    printf("prime %d\n", *dst);
    return 0;
  }
  return -1;
}

void traversal(int lpipe[2])
{
	close(lpipe[1]);
	int first;

	if (lpipe_first_data(lpipe, &first) == 0) {
		int p[2];
		pipe(p);
		
		int data;

		while (read(lpipe[0], &data, sizeof(int)) == sizeof(int)) {
			if (data % first)
				write(p[1], &data, sizeof(int));
		}
		close(lpipe[0]);
		close(p[1]);
		
		if (fork() == 0)
			traversal(p);
		else {
			close(p[0]);
			wait(0);
		}
	}
	exit(0);
}

int main(int argc, char *argv[])
{
	if (argc != 2) {
		printf("Parameter missing\n");
		exit(0);
	}

	int nums = atoi(argv[1]);

	int p[2];
	pipe(p);

	for (int i = 2; i <= nums; i++) {
		write(p[1], &i, sizeof(int));
	}

	if (fork() == 0) {
		traversal(p);
	} else {
		close(p[1]);
		close(p[0]);
		wait(0);
	}
	exit(0);
}

在这里插入图片描述

五、find(难度:Moderate)

写一个简化版本的UNIX的find程序:查找目录树中具有特定名称的所有文件,你的解决方案应该放在user/find.c

提示:

查看user/ls.c文件学习如何读取目录
使用递归允许find下降到子目录中
不要在“.”和“…”目录中递归
对文件系统的更改会在qemu的运行过程中一直保持;要获得一个干净的文件系统,请运行make clean,然后make qemu
你将会使用到C语言的字符串,要学习它请看《C程序设计语言》(K&R),例如第5.5节
注意在C语言中不能像python一样使用“==”对字符串进行比较,而应当使用strcmp()
将程序加入到Makefile的UPROGS
如果你的程序输出下面的内容,那么它是正确的(当文件系统中包含文件b和a/b的时候)

$ make qemu
...
init: starting sh
$ echo > b
$ mkdir a
$ echo > a/b
$ find . b
./b
./a/b
$

解析:

确实没啥说的,代码基本上都是ls.c中的内容。

答案:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

void find(char* path, char* filename)
{
	char buf[512], *p;
	int fd;
	struct dirent de;
	struct stat st;

	if((fd = open(path, 0)) < 0){
	fprintf(2, "find: cannot open %s\n", path);
	return;
	}

	//fstat() 用来将参数filedes 所指向的文件状态复制到参数buf 所指向的结构
	if(fstat(fd, &st) < 0)	{
		fprintf(2, "find: cannot stat %s\n", path);
		close(fd);
    		return;
  	}

	//参数错误,find的第一个参数必须是目录
	if (st.type != T_DIR) {
		fprintf(2, "usage: find <DIRECTORY> <filename>\n");
		return;
	}
	
	//检查路径长度是否适合存储在buf中
	if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf) {
		printf("find: path too long\n");
		return;
	}
	strcpy(buf, path);
	p = buf+strlen(buf);
	*p++ = '/';
	while(read(fd, &de, sizeof(de)) == sizeof(de)) {
		if(de.inum == 0)
			continue;
	memmove(p, de.name, DIRSIZ);
	p[DIRSIZ] = 0;
	if(stat(buf, &st) < 0) {
		printf("find: cannot stat %s\n", buf);
		continue;
      }
	//不要在“.”和“..”目录中递归
	if (st.type == T_DIR && strcmp(p, ".") != 0 && strcmp(p, "..") != 0) {
		find(buf, filename);
	} else if (strcmp(filename, p) == 0)
		printf("%s\n", buf);
	}
	close(fd);
}

int main(int argc, char* argv[])
{
	if (argc != 3) {
		printf("Parameter missing\n");
		exit(0);
	}
	
	find(argv[1], argv[2]);
	return 0;
}

结果:
在这里插入图片描述

六、xargs(难度:Moderate)

编写一个简化版UNIX的xargs程序:它从标准输入中按行读取,并且为每一行执行一个命令,将行作为参数提供给命令。你的解决方案应该在user/xargs.c

下面的例子解释了xargs的行为

$ echo hello too | xargs echo bye
bye hello too
$

注意,这里的命令是echo bye,额外的参数是hello too,这样就组成了命令echo bye hello too,此命令输出bye hello too

请注意,UNIX上的xargs进行了优化,一次可以向该命令提供更多的参数。 我们不需要您进行此优化。 要使UNIX上的xargs表现出本实验所实现的方式,请将-n选项设置为1。例如

$ echo "1\n2" | xargs -n 1 echo line
line 1
line 2
$

提示:

使用fork和exec对每行输入调用命令,在父进程中使用wait等待子进程完成命令。
要读取单个输入行,请一次读取一个字符,直到出现换行符(‘\n’)。
kernel/param.h声明MAXARG,如果需要声明argv数组,这可能很有用。
将程序添加到Makefile中的UPROGS。
对文件系统的更改会在qemu的运行过程中保持不变;要获得一个干净的文件系统,请运行make clean,然后make qemu
xargs、find和grep结合得很好

$ find . b | xargs grep hello

将对“.”下面的目录中名为b的每个文件运行grep hello。

要测试您的xargs方案是否正确,请运行shell脚本xargstest.sh。如果您的解决方案产生以下输出,则是正确的:

$ make qemu
...
init: starting sh
$ sh < xargstest.sh
$ $ $ $ $ $ hello
hello
hello
$ $

你可能不得不回去修复你的find程序中的bug。输出有许多$ ,因为xv6 shell没有意识到它正在处理来自文件而不是控制台的命令,并为文件中的每个命令打印$。

解析:

这题我感觉是第一节中最难的,主要是没有换行符判断很难受。
听从大佬解法使用滑动窗口解决。
大佬解法链接
具体解释看代码注释。
答案:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"

int main(int argc, char *argv[])
{
	char buf[512] = {0};//滑动窗口
	char *xargv[MAXARG] = {0};
	uint size = 0;//字节大小
	int endIndex = 0;//字段末尾标识符
	
	for (int i = 1; i < argc; i++) {
		xargv[i - 1] = argv[i];
  	}

	while (!(endIndex && size == 0)) {
		if (!endIndex) {
			int readBytes = read(0, buf + size, 512 - size);
			if (readBytes < 0) {
				printf(" read error \n");
				exit(0);
			}
			/*如果读取到 0 字节(即已到达标准输入的末尾),则关闭标准输入并设置为真*/
			if (readBytes == 0) {
				close(0);
				endIndex = 1;
			}
			size += readBytes;
		}
		/*处理读取的行*/
		char* lineEnd = strchr(buf, '\n');//每一行的结束
		while (lineEnd) {
			char xbuf[513] = {0};
			memcpy(xbuf, buf, lineEnd - buf);
			xargv[argc - 1] = xbuf;
			int pid = fork();
			if (pid == 0) {
				/*子进程使用 exec 执行由 argv[1] 指定的程序,并将 xargv 作为参数传递*/
				if (!endIndex) 
					close(0);
				if (exec(argv[1], xargv) < 0) {
					fprintf(2, "xargv: exec fails [file ...]\n");
					exit(1);
				}
			} else if (pid > 0) {
				/*父进程从滑动窗口中移除已处理的行*/
				memmove(buf, lineEnd + 1, size - (lineEnd - buf) - 1);
				size -= lineEnd - buf + 1;
				memset(buf + size, 0, 512 - size);
				
				/*回收僵尸*/
				int status;
				int ret = wait(&status);
				//成功返回0 失败返回-1
				if( ret < 0) {
					printf("wait error");
					exit(1);
				}
				/*继续查找和处理下一行*/
				lineEnd = strchr(buf, '\n');
			} else {
				printf("%d: received ping \n", getpid());
				exit(0);
			}
		}
	}
	exit(0);
}

其实还是有点小毛病,使用给出的sh < xargstest.sh,输出有点问题
在这里插入图片描述
其他是对的
在这里插入图片描述
在这里插入图片描述
之后再仔细瞅瞅。


总结

我稍微有些操作系统的学习知识,做的不算太卡。哈工大的我感觉底层东西介绍的多,但是有些其他知识覆盖面少,就比如进程通信、锁、管道之类的就没咋说。
个人感觉比起csapp的lab容易。
现在据说这个实验也没那么新颖了,可以根据自己理解整点新东西。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/449218.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++初阶:类与对象(尾篇)

目录 1. 构造函数与初始化列表1.1 对象的创建与构造函数的初始化1.2 初始化列表及构造函数存在的意义1.3 explicit关键字与构造函数的类型转换 2. static成员变量与static成员函数2.1 static成员变量2.2 static成员函数 3. 日期类流插入操作符的重载与友元3.1 友元3.2 友元函数…

Ubuntu20.04配置静态ip

文章目录 前言配置静态ip 前言 在 Linux 创建时默认是动态 ip&#xff0c;我们需要将这台 Linux 作为服务器&#xff0c;所以需要将动态分配的 ip 设置为静态的&#xff0c;防止 ip 变换。 可使用 ip addr 查看当前的 网卡 和 ip 信息&#xff1a; 从上图可看到动态分配的 i…

Python 导入Excel三维坐标数据 生成三维曲面地形图(体) 5-1、线条平滑曲面且可通过面观察柱体变化(一)

环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 pandas 2.1.4 openpyxl 3.1.2 scipy 1.12.0 代码: import pandas as pd import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.interpolate import griddata fr…

HAProxy 简单介绍

一 HAProxy介绍 &#xff08;一&#xff09;发展历史 HAProxy是法国开发者威利塔罗(Willy Tarreau)在2000年使用C语言开发的一个开源软件&#xff0c;是一款具备高并发(一万以上)、高性能的TCP和HTTP负载均衡器&#xff0c;支持基于cookie的持久性&#xff0c;自动故障切换…

GPT与R 在生态环境领域数据统计分析

原文链接&#xff1a;GPT与R 在生态环境领域数据统计分析https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247597092&idx2&sn0a7ac5cf03d37c7b4659f870a7b71a77&chksmfa823dc3cdf5b4d5ee96a928a1b854a44aff222c82b2b7ebb7ca44b27a621edc4c824115babe&…

软件杯 垃圾邮件(短信)分类算法实现 机器学习 深度学习

文章目录 0 前言2 垃圾短信/邮件 分类算法 原理2.1 常用的分类器 - 贝叶斯分类器 3 数据集介绍4 数据预处理5 特征提取6 训练分类器7 综合测试结果8 其他模型方法9 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 垃圾邮件(短信)分类算…

实战案例——Kafka集群部署

1. 规划节点 IP主机名节点192.168.100.10zookeeper1集群节点192.168.100.20zookeeper2集群节点192.168.100.30zookeeper集群节点 2. 基础准备 使用ZooKeeper集群搭建的3个节点来构建Kafka集群&#xff0c;因为Kafka服务依赖于ZooKeeper服务&#xff0c; 所以不再多创建云主机…

打卡--MySQL8.0 二 (用户权限管理)

一、mysql8修改了安全规则&#xff0c;不能像mysql5.7 一次性创建用户并授权&#xff0c;需要分批创建。 1、注意在MySQL8.0版本中创建用户一定要在配置文件中增加如下内容&#xff0c;来兼容旧的程序运行。 default_authentication_pluginmysql_native_password 2、创建用户…

LM2904DT运算放大器中文资料规格书PDF数据手册引脚图参数图片功能概述

产品概述&#xff1a; 该电路由两个独立的高增益运算放大器&#xff08;运算放大器&#xff09;组成&#xff0c;内部实现了频率补偿。它们专为汽车和工业控制系统而设计。该电路采用单电源供电&#xff0c;工作电压范围很广。低功耗与电源电压的大小无关。 应用领域包括传感…

【力扣 - 和为K的子数组】

题目描述 给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1], k 2 输出&#xff1a;2 示例 2&#xff1a; 输入&#xff1a;num…

ElementUI两个小坑

1.form表单绑定的是一个对象&#xff0c;表单里的一个输入项是对象的一个属性之一&#xff0c;修改输入项&#xff0c;表单没刷新的问题&#xff0c; <el-form :model"formData" :rules"rules" ref"editForm" class"demo-ruleForm"…

【机器学习】机器学习创建算法第1篇:机器学习算法课程定位、目标【附代码文档】

机器学习&#xff08;算法篇&#xff09;完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;机器学习算法课程定位、目标&#xff0c;K-近邻算法&#xff0c;1.1 K-近邻算法简介&#xff0c;1.2 k近邻算法api初步使用定位,目标,学习目标,1 什么是K-近邻算法,…

Java旋转矩阵

题目&#xff1a; 给你一幅由 N N 矩阵表示的图像&#xff0c;其中每个像素的大小为 4 字节。请你设计一种算法&#xff0c;将图像旋转 90 度。 不占用额外内存空间能否做到&#xff1f; 示例 1: 给定 matrix [ [1,2,3], [4,5,6], [7,8,9] ], 原地旋转输入矩阵&…

山姆・阿尔特曼重返OpenAI董事会;Car-GPT:LLMs能否最终实现自动驾驶?

&#x1f989; AI新闻 &#x1f680; 山姆・阿尔特曼重返OpenAI董事会 摘要&#xff1a;经历长达数月的审查后&#xff0c;山姆・阿尔特曼已重返OpenAI董事会&#xff0c;并作为返回条件之一&#xff0c;OpenAI还新增了三名外部女性董事会成员。这标志着公司正努力摆脱去年11…

开源模型应用落地-业务优化篇(八)

一、前言 在之前的学习中&#xff0c;我相信您已经学会了一些优化技巧&#xff0c;比如分布式锁、线程池优化、请求排队、服务实例扩容和消息解耦等等。现在&#xff0c;我要给您介绍最后一篇业务优化的内容了。这个优化方法是通过定时统计问题的请求频率&#xff0c;然后将一些…

交叉编译x264 zlib ffmpeg以及OpenCV等 以及解决交叉编译OpenCV时ffmpeg始终为NO的问题

文章目录 环境编译流程nasm编译x264编译zlib编译libJPEG编译libPNG编译libtiff编译 FFmpeg编译OpenCV编译问题1解决方案 问题2解决方案 总结 环境 系统&#xff1a;Ubutu 18.04交叉编译链&#xff1a;gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu 我的路径/opt/toolch…

【Flutter】报错Target of URI doesn‘t exist ‘package:flutter/material.dart‘

运行别人项目 包无法导入报错&#xff1a;Target of URI doesn’t exist ‘package:flutter/material.dart’ 解决方法 flutter packages get成功 不会报错

php CI框架异常报错通过钉钉自定义机器人发送

php CI框架异常报错通过钉钉自定义机器人发送 文章目录 php CI框架异常报错通过钉钉自定义机器人发送前言一、封装一个异常监测二、封装好钉钉信息发送总结 前言 我们在项目开发中&#xff0c;经常会遇到自己测试接口没问题&#xff0c;上线之后就会测出各种问题&#xff0c;主…

K8s — PVC|PV Terminating State

在本文中&#xff0c;我们将讨论PV和PVC一直Terminating的状态。 何时会Terminting? 在以下情况下&#xff0c;资源将处于Terminating状态。 在删除Bounded 状态的PVC之前&#xff0c;删除了对应的PV&#xff0c;PV在删除后是Terminting状态。删除PVC时&#xff0c;仍有引用…

python向多个用户发送文字、图片内容邮件和excel附件

话不多说&#xff0c;直接上代码&#xff0c;需要把发件里面的smtp_info替换为自己的信息&#xff0c;其中password是指邮箱在开通POP/SMTP功能后获取的授权码&#xff01; import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import M…