参考这篇文章即可:环境配置
time.txt
文件告知完成时间。
$U/_操作名\
实现xv6的UNIX程序
sleep
:您的sleep
应该暂停到用户指定的计时数。一个滴答(tick)是由xv6内核定义的时间概念,即来自定时器芯片的两个中断之间的时间。您的解决方案应该在文件\user/sleep.c\
中
sleep
应该打印一条错误信息atoi
将其转换为数字(详见***/user/ulib.c***)sleep
sleep
系统调用的xv6内核代码(查找sys_sleep
),**user/user.h*提供了sleep
的声明以便其他程序调用,用汇编程序编写的user/usys.S***可以帮助sleep
从用户区跳转到内核区。main
函数调用exit()
以退出程序。sleep
程序添加到***Makefile***中的UPROGS
中;完成之后,make qemu
将编译您的程序,并且您可以从xv6的shell运行它。这题很简单,要求我们实现一个sleep函数来进行休眠操作。
我们看要求中,让我们去看user中的调用参数示例,这也是我们后续使用的很多的一个范式。
我们去看看echo.c
可以看到,这个主函数中调用了两个参数,argc
和*argv[]
,一个是命令行参数的数量,一个是用来接收命令行参数的数组。
我们回到sleep.c
中,知道了该如何接受命令行参数,也就知道了如何写它的框架。
#include<kernel/types.h>
#include<user/user.h>
int 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的实现过程,因为我们只需要直接调用即可。我们要做的重要的事情是:在命令行中实现该函数的调用。
#include<kernel/types.h>
#include<user/user.h>
int 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);
}
编写一个使用UNIX系统调用的程序来在两个进程之间“ping-pong”一个字节,请使用两个管道,每个方向一个。父进程应该向子进程发送一个字节;子进程应该打印“
<pid>: received ping
”,其中<pid>
是进程ID,并在管道中写入字节发送给父进程,然后退出;父级应该从读取从子进程而来的字节,打印“<pid>: received pong
”,然后退出。您的解决方案应该在文件*user/pingpong.c*中。
pipe
来创造管道fork
创建子进程read
从管道中读取数据,并且使用write
向管道中写入数据getpid
获取调用进程的pidUPROGS
我们看提示,使用pipe
来创造管道的目的是什么呢?因为这是作为父进程与子进程之间进行传输的方式。我们的实验目的是实现子父进程之间的字符串传递,那么就可以这样去想:通过两个管道实现了父进程和子进程之间的简单通信。父进程向子进程发送 “ping” 消息,子进程接收到后打印消息并向父进程发送 “pong” 消息,父进程接收到后打印消息。
所以说我们创建两个pipe,每个pipe都有两个文件描述符,一个用于读一个用于写。
int p1[2], p2[2];
pipe(p1), pipe(p2);
我们知道了文件描述符0、1、2分别表示什么意思之后,这里就好理解了。
0
,用于从键盘或其他输入设备读取数据。1
,用于向屏幕或其他输出设备写入数据。2
,用于向屏幕或其他输出设备写入错误信息。好,我们知道了这些含义,那么后续就可以使用它们来进行读写操作。
其次,我们需要建立一个缓冲区来进行数据的缓存,保存从管道读取的信息,相当于子进程和父进程之间的一个“中介”。
char buf[5]; // 用于保存从管道读取的信息
int size; //用于保存读取的字节数
好,一切前置准备就绪后,我们来创建子进程和父进程。
在创建前,我们需要知道pid
用于区分父进程和子进程,并根据不同的返回值执行相应的代码逻辑。
并且我们根据实验要求,来理解子进程和父进程分别要做些什么。
在子进程中:
p1
的写端。p1
的读端读取数据,并打印接收到的信息。p2
的读端。p2
的写端写入 “pong\n”。int pid = fork();//pid用于判断创建的进程
//如果pid==0,那么说明是创建的子进程
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.h"
int 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个字节的数据到buf
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写从“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);
}
使用管道编写
prime sieve
(筛选素数)的并发版本。这个想法是由Unix管道的发明者Doug McIlroy提出的。请查看这个网站(翻译在下面),该网页中间的图片和周围的文字解释了如何做到这一点。您的解决方案应该在*user/primes.c*文件中。 您的目标是使用pipe
和fork
来设置管道。第一个进程将数字2到35输入管道。对于每个素数,您将安排创建一个进程,该进程通过一个管道从其左邻居读取数据,并通过另一个管道向其右邻居写入数据。由于xv6的文件描述符和进程数量有限,因此第一个进程可以在35处停止。
wait
等待整个管道终止,包括所有子孙进程等等。因此,主primes
进程应该只在打印完所有输出之后,并且在所有其他primes
进程退出之后退出。write
端关闭时,read
返回零。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);
}
写一个简化版本的UNIX的
find
程序:查找目录树中具有特定名称的所有文件,你的解决方案应该放在*user/find.c*
find
下降到子目录中.
”和“..
”目录中递归make clean
,然后make qemu
==
”对字符串进行比较,而应当使用strcmp()
UPROGS
提示中要我们看ls.c,我们照做:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
char*
fmtname(char *path)
{
static char buf[DIRSIZ+1];
char *p;
// Find first character after last slash.
for(p=path+strlen(path); p >= path && *p != '/'; p--)
;
p++;
// Return blank-padded name.
if(strlen(p) >= DIRSIZ)
return p;
memmove(buf, p, strlen(p));
memset(buf+strlen(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 = 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("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(i=1; i<argc; i++)
ls(argv[i]);
exit(0);
}
我们逐帧分析:
char* fmtname(char *path)
{
static char buf[DIRSIZ+1];
char *p;
// Find first character after last slash.
for(p=path+strlen(path); p >= path && *p != '/'; p--)
;
p++;
// Return blank-padded name.
if(strlen(p) >= DIRSIZ)
return p;
memmove(buf, p, strlen(p));
memset(buf+strlen(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 = 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("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 100
file2.txt 1 3 200
subdir 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)
//处理目录
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.h"
void 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);
}
编写一个简化版UNIX的
xargs
程序:它从标准输入中按行读取,并且为每一行执行一个命令,将行作为参数提供给命令。你的解决方案应该在user/xargs.c**
fork
和exec
对每行输入调用命令,在父进程中使用wait
等待子进程完成命令。MAXARG
,如果需要声明argv
数组,这可能很有用。UPROGS
。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 k=0;k<size;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.h"
int 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);
}