3. 是网站建设的重点,企业建设网站注意事项,北京一网数据软件有限公司,一家专门做灯的网站配置xv6环境
参考这篇文章即可#xff1a;环境配置 对于xv6的使用#xff0c;更推荐用VSCode等文本编辑器进行启动#xff0c;毕竟面对着命令提示符#xff0c;还是太过寒酸了。 每次实验完成之后需要进行提交和测试#xff0c;并且要新创一个time.txt文件告知完成时间。…配置xv6环境
参考这篇文章即可环境配置 对于xv6的使用更推荐用VSCode等文本编辑器进行启动毕竟面对着命令提示符还是太过寒酸了。 每次实验完成之后需要进行提交和测试并且要新创一个time.txt文件告知完成时间。 每次创建文件会需要在Makefile中指定文件的编译顺序。即一般进行以下操作 $U/_操作名\ 可以通过github进行提交。 注意代码中的注释往往包含重要信息务必要仔细查看
实验1 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语言。
实验思路
这题很简单要求我们实现一个sleep函数来进行休眠操作。
我们看要求中让我们去看user中的调用参数示例这也是我们后续使用的很多的一个范式。
我们去看看echo.c 可以看到这个主函数中调用了两个参数argc和*argv[]一个是命令行参数的数量一个是用来接收命令行参数的数组。
我们回到sleep.c中知道了该如何接受命令行参数也就知道了如何写它的框架。
#includekernel/types.h
#includeuser/user.hint main(int argc,char** argv)
{if(argc!2)//这里指的是在命令行中输出时要用到的参数我们这里要输出sleep 10也就是休眠10个时间单位所以也就有两个参数。{printf(Error Example:sleep 2\n);exit(-1);}...
}然后我们再参考sleep要求我们输入的参数它是一个int类型的参数是用来表示休眠的时间。
那么我们如果需要接受一个int类型的参数来休眠指定时间也就需要将命令行参数转类型成整数类型。这里使用到了atoi
在ulib.c中可以找到 我们照葫芦画瓢。
int num_of_tick atoi(argv[1]);//这里argv[1]是指的int具体是多少用一个变量来读取。
if(sleep(num_of_tick) 0)
{printf(Can not sleep\n);exit(-1);
}到此一个简单的sleep程序就实现完成了。实际上我们并不需要去写sleep的实现过程因为我们只需要直接调用即可。我们要做的重要的事情是在命令行中实现该函数的调用。
完整代码
#includekernel/types.h
#includeuser/user.hint main(int argc,char** argv)
{if(argc!2)//两个参数一个是程序名一个是参数这里是sleep int{printf(Error Example:sleep 2\n);exit(-1);}//sleep系统调用函数在在user.h中被定义但深层是通过汇编语言绑定到sys_sleep函数上的int num_of_tick atoi(argv[1]);if(sleep(num_of_tick)0){printf(Can not sleep\n);exit(-1);}exit(0);
}实验2 pingpong难度Easy
实验要求 编写一个使用UNIX系统调用的程序来在两个进程之间“ping-pong”一个字节请使用两个管道每个方向一个。父进程应该向子进程发送一个字节;子进程应该打印“pid: received ping”其中pid是进程ID并在管道中写入字节发送给父进程然后退出;父级应该从读取从子进程而来的字节打印“pid: received pong”然后退出。您的解决方案应该在文件*user/pingpong.c*中。 提示
使用pipe来创造管道使用fork创建子进程使用read从管道中读取数据并且使用write向管道中写入数据使用getpid获取调用进程的pid将程序加入到***Makefile***的UPROGSxv6上的用户程序有一组有限的可用库函数。您可以在user/user.h*中看到可调用的程序列表源代码系统调用除外位于user/ulib.c*、**user/printf.c*和user/umalloc.c*中。
实验思路
我们看提示使用pipe来创造管道的目的是什么呢因为这是作为父进程与子进程之间进行传输的方式。我们的实验目的是实现子父进程之间的字符串传递那么就可以这样去想通过两个管道实现了父进程和子进程之间的简单通信。父进程向子进程发送 “ping” 消息子进程接收到后打印消息并向父进程发送 “pong” 消息父进程接收到后打印消息。
所以说我们创建两个pipe每个pipe都有两个文件描述符一个用于读一个用于写。
int p1[2], p2[2];
pipe(p1), pipe(p2);我们知道了文件描述符0、1、2分别表示什么意思之后这里就好理解了。
标准输入stdin文件描述符为 0用于从键盘或其他输入设备读取数据。标准输出stdout文件描述符为 1用于向屏幕或其他输出设备写入数据。标准错误stderr文件描述符为 2用于向屏幕或其他输出设备写入错误信息。
好我们知道了这些含义那么后续就可以使用它们来进行读写操作。
其次我们需要建立一个缓冲区来进行数据的缓存保存从管道读取的信息相当于子进程和父进程之间的一个“中介”。
char buf[5]; // 用于保存从管道读取的信息
int size; //用于保存读取的字节数好一切前置准备就绪后我们来创建子进程和父进程。
在创建前我们需要知道pid 用于区分父进程和子进程并根据不同的返回值执行相应的代码逻辑。
并且我们根据实验要求来理解子进程和父进程分别要做些什么。
在子进程中
关闭管道 p1 的写端。从管道 p1 的读端读取数据并打印接收到的信息。关闭管道 p2 的读端。向管道 p2 的写端写入 “pong\n”。退出子进程。
int pid fork();//pid用于判断创建的进程
//如果pid0那么说明是创建的子进程
if (pid 0) {// 子进程执行的代码close(p1[1]); // 关闭管道1的写端if ((size read(p1[0], buf, sizeof buf)) 0) { // 从管道1读取数据printf(%d: received , getpid());write(1, buf, size);} else {printf(%d: receive failed\n, getpid());}close(p2[0]); // 关闭管道2的读端write(p2[1], pong\n, 5); // 向管道2写数据exit(0);
}在父进程中
关闭管道 p1 的读端。向管道 p1 的写端写入 “ping\n”。等待子进程结束。关闭管道 p2 的写端。从管道 p2 的读端读取数据并打印接收到的信息。
//如果返回的是子进程的进程ID那么说明创建的是父进程
else if (pid 0) {// 父进程执行的代码close(p1[0]); // 关闭管道1的读端write(p1[1], ping\n, 5); // 向管道1写数据wait(0); // 等待子进程结束close(p2[1]); // 关闭管道2的写端if ((size read(p2[0], buf, sizeof buf)) 0) { // 从管道2读取数据printf(%d: received , getpid());write(1, buf, size);} else {printf(%d: receive failed\n, getpid());}
}
//错误处理
else {printf(fork error\n);
}注意我们发现在主要代码的前后通常都会有close操作也就是关闭管道的读端或者写端。其实这里的作用是防止管道进入或者出去什么“奇怪的东西”避免了意外的读写操作从而产生资源泄露。
完整代码
// user/pingpong.c
#include kernel/types.h
#include kernel/stat.h
#include user/user.hint main(int argc, char *argv[]) {int p1[2], p2[2];pipe(p1), pipe(p2);char buf[5]; // 用于保存从管道读取的信息int size;int pid fork();if (pid 0) {//读取父进程传过来的信息close(p1[1]); // 关闭管道1的写端if ((size read(p1[0], buf, sizeof buf)) 0) { // 从管道1读取不大于buf个字节的数据到bufprintf(%d: received , getpid());write(1, buf, size);} else {printf(%d: receive failed\n, getpid());}//向父进程写信息close(p2[0]); // 关闭管道2的读端write(p2[1], pong\n, 5); // 向管道2写从“pong\n开始的不大于5个字节的数据exit(0);} else if (pid 0) {//向子进程写信息close(p1[0]);write(p1[1], ping\n, 5);wait(0);//读取子进程传过来的信息close(p2[1]);if ((size read(p2[0], buf, sizeof buf)) 0) {printf(%d: received , getpid());write(1, buf, size);} else {printf(%d: receive failed\n, getpid());}} else {printf(fork error\n);}exit(0);
}实验3 primes难度Moderate
实验要求 使用管道编写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
实验思路
首先我们看提示中需要注意的第一点要及时关闭不需要的文件描述符也就与pingpong中管道读写的关闭类似及时解除引用。
其次我们需要定义一个最大常量保证在超过这个常量时应该调用wait直到管道中止。
#define SIZE 34接下来我们就进行程序的分析如果我们需要进行筛选操作筛选出1-34中所有的素数那么我们可以如何进行思考呢没错使用递归就可以实现。
好那我们直接递归函数recur进行该函数的编写即可。
//recur函数的功能递归调用找出质数筛选
void recur(int p[2])
{int primes, nums;int p1[2];close(0); // 关闭标准输入dup(p[0]); // 复制管道的读端到标准输入close(p[0]); // 关闭管道的读端close(p[1]); // 关闭管道的写端if (read(0, primes, 4)){printf(prime %d\n, primes); // 打印由父进程传来的第一个质数pipe(p1); // 创建新的管道if (fork() 0)//创建子进程{recur(p1); // 递归调用}else{while(read(0, nums, 4)){if(nums % primes ! 0)//将符合条件的数字传给子进程{write(p1[1], nums, 4); // 将不是primes的倍数的数写入管道}}close(p1[1]);//关闭写端close(0);//关闭标准输入wait(0);//等待子进程结束}}else{close(0);}exit(0);
}然后将其与主函数结合即可。
int main()
{int p[2];pipe(p);for (int i 2; i SIZE; i){write(p[1], i, 4); // 将 2 到 SIZE (34) 写入管道}if (fork() 0) // 创建子进程{recur(p);} else {close(p[1]);wait(0);}exit(0);
}这里的递归函数的妙处就在于
它利用管道和递归调用来实现质数筛选算法。每一层递归创建一个新的管道用于传递筛选后的数。
主进程将 2 到 SIZE 的数写入管道子进程通过递归调用 recur 函数筛选出质数并将不是当前质数倍数的数传递给下一个子进程。每个子进程负责筛选一个质数并将结果打印出来。
完整代码
#include kernel/types.h
#include kernel/stat.h
#include user/user.h#define SIZE 34//recur函数的功能递归调用找出质数筛选
void recur(int p[2])
{int primes, nums;int p1[2];close(0); // 关闭标准输入dup(p[0]); // 复制管道的读端到标准输入close(p[0]); // 关闭管道的读端close(p[1]); // 关闭管道的写端if (read(0, primes, 4)){printf(prime %d\n, primes); // 打印由父进程传来的第一个质数pipe(p1); // 创建新的管道if (fork() 0)//创建子进程{recur(p1); // 递归调用}else{while(read(0, nums, 4)){if(nums % primes ! 0)//将符合条件的数字传给子进程{write(p1[1], nums, 4); // 将不是primes的倍数的数写入管道}}close(p1[1]);//关闭写端close(0);//关闭标准输入wait(0);//等待子进程结束}}else{close(0);}exit(0);
}int main()
{int p[2];pipe(p);for (int i 2; i SIZE; i){write(p[1], i, 4); // 将2到SIZE(34)写入管道}if(fork()0)//创建子进程{recur(p);}else{close(p[1]);wait(0);}
exit(0);
}实验4 find难度Moderate
实验要求 写一个简化版本的UNIX的find程序查找目录树中具有特定名称的所有文件你的解决方案应该放在*user/find.c* 提示
查看***user/ls.c***文件学习如何读取目录使用递归允许find下降到子目录中不要在“.”和“..”目录中递归对文件系统的更改会在qemu的运行过程中一直保持要获得一个干净的文件系统请运行make clean然后make qemu你将会使用到C语言的字符串要学习它请看《C程序设计语言》KR,例如第5.5节注意在C语言中不能像python一样使用“”对字符串进行比较而应当使用strcmp()将程序加入到Makefile的UPROGS
实验思路
提示中要我们看ls.c我们照做
#include kernel/types.h
#include kernel/stat.h
#include user/user.h
#include kernel/fs.hchar*
fmtname(char *path)
{static char buf[DIRSIZ1];char *p;// Find first character after last slash.for(ppathstrlen(path); p path *p ! /; p--);p;// Return blank-padded name.if(strlen(p) DIRSIZ)return p;memmove(buf, p, strlen(p));memset(bufstrlen(p), , DIRSIZ-strlen(p));return buf;
}void
ls(char *path)
{char buf[512], *p;int fd;struct dirent de;//目录项struct stat st;//文件属性if((fd open(path, 0)) 0){//如果文件描述符0说明打开文件失败fprintf(2, ls: cannot open %s\n, path);return;}if(fstat(fd, st) 0){fprintf(2, ls: cannot stat %s\n, path);close(fd);return;}switch(st.type){case T_FILE:printf(%s %d %d %l\n, fmtname(path), st.type, st.ino, st.size);break;case T_DIR:if(strlen(path) 1 DIRSIZ 1 sizeof buf){printf(ls: path too long\n);break;}strcpy(buf, path);p bufstrlen(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(ls: cannot stat %s\n, buf);continue;}printf(%s %d %d %d\n, fmtname(buf), st.type, st.ino, st.size);}break;}close(fd);
}int
main(int argc, char *argv[])
{int i;if(argc 2){ls(.);exit(0);}for(i1; iargc; i)ls(argv[i]);exit(0);
}我们逐帧分析
char* fmtname(char *path)
{static char buf[DIRSIZ1];char *p;// Find first character after last slash.for(ppathstrlen(path); p path *p ! /; p--);p;// Return blank-padded name.if(strlen(p) DIRSIZ)return p;memmove(buf, p, strlen(p));memset(bufstrlen(p), , DIRSIZ-strlen(p));return buf;
}这部分用来干什么呢答案是格式化文件名。这个函数用于找到路径中最后一个斜杠的部分也就是“最里面”。最后返回使用空格填充的文件名。
void ls(char *path)
{char buf[512], *p;int fd;struct dirent de;struct stat st;if((fd open(path, 0)) 0){fprintf(2, ls: cannot open %s\n, path);return;}if(fstat(fd, st) 0){fprintf(2, ls: cannot stat %s\n, path);close(fd);return;}switch(st.type){//如果是文件case T_FILE:printf(%s %d %d %l\n, fmtname(path), st.type, st.ino, st.size);break;//如果是目录case T_DIR:if(strlen(path) 1 DIRSIZ 1 sizeof buf){printf(ls: path too long\n);break;}strcpy(buf, path);p bufstrlen(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(ls: cannot stat %s\n, buf);continue;}printf(%s %d %d %d\n, fmtname(buf), st.type, st.ino, st.size);}break;}close(fd);
}这部分又是干什么呢答案就是ls列出目录内容。
它分为两种情况1.如果是文件就直接打印文件信息2.如果是目录就遍历目录中每个文件和子目录并打印其信息。
我们试着使用一下这个ls.c
假设你的目录结构如下
/home/joolin/xv6-labs-2020/user/├── ls.c├── file1.txt├── file2.txt└── subdir├── file3.txt└── file4.txt首先你需要在 xv6 中编译 ls.c 文件
gcc -o ls ls.c列出当前目录的内容 如果你在 [user](vscode-file://vscode-app/d:/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 目录下运行 ls 命令而不带任何参数它将列出当前目录的内容 ./ls 输出可能如下 file1.txt 1 2 100file2.txt 1 3 200subdir 2 4 4096我们只举这么一个例子目的只是为了知道它究竟有什么用。 解释输出
file1.txt、file2.txt 是文件名。1 是文件类型1 表示普通文件2 表示目录。2、3、4、5、6 是 inode 号。100、200、300、400、4096 是文件大小以字节为单位。
通过例子你可以看到 ls.c 程序如何列出目录中的文件和子目录并显示它们的详细信息。
好现在我们知道了ls.c的作用那么再去模仿其改写find.c岂不是得心应手
我们需要根据find.c的需求来进行程序的编写。
它要我们做什么
ls.c 用于列出目录内容而 find.c 用于查找符合条件的文件。
可以理解为ls.c是遍历列出而find.c是指定内容。
我们都知道文件系统是一层一层的那么自然而然联想到递归。
所以我们的函数实现也会使用到递归。
而模仿ls.c中的代码我们知道首先要判断是否能获取到目录和文件属性然后要针对不同类型的文件进行不同的处理。
//错误情况
if ((fd open(path, 0)) 0)
{fprintf(2, find: cannot open %s\n, path);return;
}if (fstat(fd, st) 0)
{fprintf(2, find: cannot stat %s\n, path);close(fd);return;
}
//不同类型不同处理
switch (st.type)
{
case T_FILE:fprintf(2, path error\n);return;
case T_DIR:...break;
}针对文件就直接返回错误
针对目录就继续处理。
首先处理目录项。注意目录与目录项是不一样的这里着重讲一下
目录Directory
定义目录是文件系统中的一种特殊文件用于包含其他文件和子目录。目录本身是一个文件但它的内容是一个文件列表。也就是说它是一个列表。
目录项Directory Entry
定义目录项是目录中的一个条目表示目录中的一个文件或子目录。每个目录项包含文件或子目录的名称和 inode 号。也就是说它可以是一个实际的文件。
//处理目录
strcpy(buf, path);
p buf strlen(buf);
*p /;
while (read(fd, de, sizeof(de)) sizeof(de))
{if (de.inum 0)continue;if (!strcmp(de.name, .) || !strcmp(de.name, ..))continue;memmove(p, de.name, DIRSIZ);...
}//处理目录项
if ((fd1 open(buf, 0)) 0)
{if (fstat(fd1, st) 0){switch (st.type){case T_FILE:if (!strcmp(de.name, name)){printf(%s\n, buf);}close(fd1);break;case T_DIR:close(fd1);find(buf, name);break;case T_DEVICE:close(fd1);break;}}
}打开目录项并获取其文件属性。如果是文件且文件名匹配打印文件路径如果是目录递归调用 find 函数继续搜索如果是设备文件直接关闭文件描述符。
完整代码
#include kernel/types.h
#include kernel/stat.h
#include user/user.h
#include kernel/fs.hvoid find(char *path, char *name)
{char buf[128], *p; // 缓冲区int fd, fd1; // 文件描述符、struct dirent de; // 目录项struct stat st; // 文件属性// 如果文件描述符0则说明文件打开失败if ((fd open(path, 0)) 0)//这里的参数0表示以只读方式打开文件{fprintf(2, find: cannot open %s\n, path);return;}// 如果文件属性获取失败则文件打开失败if (fstat(fd, st) 0){fprintf(2, find: cannot stat %s\n, path);close(fd);return;}switch (st.type){case T_FILE:// 文件/* code */fprintf(2, path error\n); // 路径错误return;case T_DIR:// 目录strcpy(buf, path); // 复制路径p buf strlen(buf); // 指针指向路径末尾*p /; // 路径末尾加上/,表示这是个目录while (read(fd, de, sizeof(de)) sizeof(de))//循环读取目录项{{ // 遍历搜索目录if (de.inum 0)continue; // 如果inum为0则说明该目录为空}if (!strcmp(de.name, .) || !strcmp(de.name, ..)) // 如果目录名为.或者..则跳过{continue;}memmove(p, de.name, DIRSIZ); // 将目录名复制到p指向的位置if ((fd1 open(buf, 0)) 0){if (fstat(fd1, st) 0){switch (st.type){case T_FILE: // 如果是文件进行文件路径打印if (!strcmp(de.name, name)) // 如果找到了文件即目标文件名和文件名一致{printf(%s\n, buf); // 打印文件路径}close(fd1);break;case T_DIR: // 如果是目录就递归调用find函数直到找到最终的目标文件名close(fd1); // 关闭文件描述符find(buf, name);break;case T_DEVICE://如果是设备文件直接关闭文件描述符close(fd1);break; }}}}break;}close(fd);
}int main(int argc, char *argv[])
{if (argc ! 3){fprintf(2, Usage: find path name\n);exit(1);}find(argv[1], argv[2]);exit(0);
}实验5 xargs难度Moderate
实验要求 编写一个简化版UNIX的xargs程序它从标准输入中按行读取并且为每一行执行一个命令将行作为参数提供给命令。你的解决方案应该在user/xargs.c** 提示
使用fork和exec对每行输入调用命令在父进程中使用wait等待子进程完成命令。要读取单个输入行请一次读取一个字符直到出现换行符‘\n’。***kernel/param.h***声明MAXARG如果需要声明argv数组这可能很有用。将程序添加到***Makefile***中的UPROGS。对文件系统的更改会在qemu的运行过程中保持不变要获得一个干净的文件系统请运行make clean然后make qemu
实验思路
实验要求我们编写一个程序读取行内容并且按照行来执行命令执行完后将行作为参数提供给命令。
提示中说我们可以看看fork和exec前者我们已经了解我们直接去看看后者。 这里传了两个参数一个是表示要执行的可执行文件的路径一个是表示命令行参数数组。
我们从名字上来分析exec——execute也就是执行的意思那么再结合其给定的参数不难猜——它的作用是用来通过给定参数读取并执行文件。
好接下来我们根据实验要求逐步实现。
首先它要我们读取行内容那么我们就使用read就完事了。
char stdIn[512];
int size read(0, stdIn, sizeof stdIn);其次它要我们按照行来执行指令那么我们首先需要知道的是行的数量在C语言中换行通常使用\n来表示我们将其作为指标进行计算即可。
int line 0;
for(int k0;ksize;k){
if(stdIn[k]\n){line;}
}然后我们直接将数据按照行的形式进行存储这里的话我们可以用到一个二维数组来进行存储。
char output[line][64];
for (int k 0; k size; k) {output[i][j] stdIn[k];if (stdIn[k] \n) {output[i][j - 1] 0;i;j 0;}
}接下来我们就要进行执行的过程了。我们根据提示得知命令行参数数组的大小不能超过MAXARG将每一行数据作为参数拼接到命令参数后并通过 fork 创建子进程执行命令。exec(argv[1], arguments) 用于执行指定的命令并将参数传递给它。父进程等待子进程结束。
char *arguments[MAXARG];
for (j 0; j argc - 1; j) {arguments[j] argv[1 j];
}
i 0;
while (i line) {arguments[j] output[i];if (fork() 0) {exec(argv[1], arguments);exit(0);}wait(0);
}主要代码都已完成。需要注意的是这个程序可以用来批量处理数据但是不能MAXARG大小否则xv6会资源报错。
完整代码
// user/xargs.c
#include kernel/types.h
#include kernel/stat.h
#include user/user.h
#include kernel/param.hint main(int argc, char *argv[]) {//从标准输入读取数据char stdIn[512];int size read(0, stdIn, sizeof stdIn);//将数据分行存储int i 0, j 0;int line 0;for (int k 0; k size; k) {if (stdIn[k] \n) { // 根据换行符的个数统计数据的行数line;}}char output[line][64]; // 根据提示中的MAXARG命令参数长度最长为32个字节for (int k 0; k size; k) {output[i][j] stdIn[k];if (stdIn[k] \n) {output[i][j - 1] 0; // 用0覆盖掉换行符。C语言没有字符串类型char类型的数组中0表示字符串的结束i; // 继续保存下一行数据j 0;}}//将数据分行拼接到argv[2]后并运行char *arguments[MAXARG];for (j 0; j argc - 1; j) {arguments[j] argv[1 j]; // 从argv[1]开始保存原本的命令命令参数}i 0;while (i line) {arguments[j] output[i]; // 将每一行数据都分别拼接在原命令参数后if (fork() 0) {exec(argv[1], arguments);exit(0);}wait(0);}exit(0);
}]) { //从标准输入读取数据 char stdIn[512]; int size read(0, stdIn, sizeof stdIn); //将数据分行存储 int i 0, j 0; int line 0; for (int k 0; k size; k) { if (stdIn[k] ‘\n’) { // 根据换行符的个数统计数据的行数 line; } } char output[line][64]; // 根据提示中的MAXARG命令参数长度最长为32个字节 for (int k 0; k size; k) { output[i][j] stdIn[k]; if (stdIn[k] ‘\n’) { output[i][j - 1] 0; // 用0覆盖掉换行符。C语言没有字符串类型char类型的数组中0’表示字符串的结束 i; // 继续保存下一行数据 j 0; } } //将数据分行拼接到argv[2]后并运行 char *arguments[MAXARG]; for (j 0; j argc - 1; j) { arguments[j] argv[1 j]; // 从argv[1]开始保存原本的命令命令参数 } i 0; while (i line) { arguments[j] output[i]; // 将每一行数据都分别拼接在原命令参数后 if (fork() 0) { exec(argv[1], arguments); exit(0); } wait(0); } exit(0); }