0%

lab1-Utils

Lab 1:Unix utilities

课程地址:https://pdos.csail.mit.edu/6.828/2020/schedule.html
Lab 地址:https://pdos.csail.mit.edu/6.828/2020/labs/util.html
代码仓库:https://github.com/ajackchan/my-xv6-riscv/tree/util

sleep

编写一个 sleep 程序,让它在 xv6 系统中根据用户指定的时间暂停运行,时间单位为系统时钟的周期数。

代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "kernel/types.h"  
#include "kernel/stat.h"
#include "user/user.h"
    
#ifndef STDERR
#define STDERR 2
#endif
    
int main(int argc,char *argv[])
{   
    if(argc != 2){
        fprintf(STDERR,"usage: sleep <second>\n");
        exit(1);
    }
    
    //字符串转整数
    int time = atoi(argv[1]);
    
    // 检查 atoi 返回值是否为0,且参数不是"0"
    if(time == 0 && argv[1][0] != '0'){
        fprintf(STDERR,"Invalid number: %s\n",argv[1]);
        exit(1);
    }
    
    //调用 sleep 系统调用
    sleep(time);
    exit(0);
}

    
1. 引入必要的头文件

1
2
3
#include "kernel/types.h"  
#include "kernel/stat.h"
#include "user/user.h" 

这些头文件包含了基本的数据类型、状态定义和用户级系统调用的声明,如 exit 和 sleep

2. 定义 STDERR 宏

1
2
3
#ifdef STDERR  
#define STDERR 2
#endif

这个部分定义了 STDERR 为值为 2 的宏。2 是标注错误输出的文件描述符。

3. main 函数的定义

1
int main(int argc,char* argv[]) 

main 函数接受两个参数:
argc:命令行参数的数量。
argv:一个字符串数组。包含命令行传入的参数。

4. 参数数量检查

1
2
3
4
if (argc != 2) {  
    fprintf(STDERR, "usage: sleep <second>\n");
    exit(1);
}

如果传递的参数数量不等于 2(即程序名称和一个时间参数),程序会向标准错误输出 usage: sleep 提示用户正确的用法,然后使用 exit(1) 退出程序,表示程序异常结束。

5. 将字符串转换为整数

1
int time = atoi(argv[1]);  

使用 atoi 函数将字符串参数转换为整数,argv[1] 是用户输入的时间值。

6. 检查转换后的整数值是否有效

1
2
3
4
if (time == 0 && argv[1][0] != '0') {  
    fprintf(STDERR, "Invalid number: %s\n", argv[1]);
    exit(1);
}

检查转换后的时间值是否为 0。如果 time 等于 0 且输入的字符串不是 “0”,程序会认为用户输入了无效的数字,并输出错误信息,然后退出。

pingpong

编写一个程序,通过一对管道在父进程和子进程之间传递一个字节数据,实现类似“乒乓”的效果。父进程发送一个字节给子进程,子进程收到后打印消息并将字节返回给父进程,父进程再打印接收到的消息,最后两个进程都退出。

使用 fork() 复制本进程创建子进程,创建两个管道,分别用于父子之间两个方向的数据传输。

代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include "kernel/types.h"
#include "user/user.h"

//REC、SND 分别代表管道的读取端和写入端
enum PIPE_END {REC = 0, SND = 1};

int main(int argc, char* argv[]) {
    if(argc != 1) {
        fprintf(2,"usage: pingpong (no parameter)\n");
        exit(1);
    }

    int p1[2]; // 父到子
    int p2[2]; // 子到父
    pipe(p1);
    pipe(p2);

    int pid = fork();

    if(pid == 0) { // 子进程
        close(p1[SND]);
        close(p2[REC]);

        char buf;
       
        if(read(p1[REC], &buf, 1) > 0) {
            printf("%d: recevied ping\n", getpid());
        }

        write(p2[SND], "p", 1);

        close(p1[REC]);
        close(p2[SND]);
       
        exit(0);
    }
    else if(pid > 0) { // 父进程
        close(p1[REC]);
        close(p2[SND]);

        write(p1[SND], "p", 1);

        char buf;
        if(read(p2[REC], &buf, 1) > 0) {
            printf("%d: received pong\n",getpid());
        }

        close(p1[SND]);
        close(p2[REC]);
        wait(0);
        exit(0);
    }
    else {
        fprintf(2, "failed to fork\n");
        exit(1);
    }

    exit(0);
}

1. 定义枚举

1
enum PIPE_END {REC = 0, SND = 1};  

枚举定义 REC 和 SND,分别代表管道的读取端(REC = 0)和写入端(SND = 1)

2. 创建两个管道

1
2
3
4
int p1[2]; // 父到子  
int p2[2]; // 子到父
pipe(p1);
pipe(p2);

p1p2是两个管道。每个管道是一个长度为2的整数数组,pipe(p) 会分别为 p[0] 和 p[1] 赋值,表示读取端和写入端的文件描述符。
p1 用于父进程向子进程发送消息。
p2 用于子进程向父进程返回消息。

3.创建子进程

1
 int pid = fork();

fork() 系统调用用于创建一个子进程。pid 变量存储返回值:
    - pid == 0,表示当前进程是子进程。
    - pid > 0,表示当前进程是父进程,并且 pid 是子进程的 PID
    - pid < 0,表示 fork() 失败

4. 子进程逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (pid == 0) { // 子进程  
    close(p1[SND]);
    close(p2[REC]);
    
    char buf;
    if (read(p1[REC], &buf, 1) > 0) {
        printf("%d: received ping\n", getpid());
    }
    
    write(p2[SND], "P", 1);
    close(p1[REC]);
    close(p2[SND]);
    exit(0);
}

- 关闭未使用的管道端口
    - 子进程不需要使用 p1 的写入端(SND)和 p2 的读取端(REC),所以关闭这些端口。
- 读取消息
    - 使用 read() 从 p1[REC] 读取一个字节(buf),这将阻塞子进程,直到有数据可读。
    - 如果读取成功,打印 <pid>: received ping,其中 <pid> 是子进程的进程 ID
- 发送消息
    - 使用 write() 向 p2[SND] 写入一个字节 “P“,这表示子进程将消息发送回父进程。
- 关闭剩余的管道端口
    - 关闭 p1[REC] 和 p2[SND],然后子进程正常退出。

5. 父进程逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
else if (pid > 0) { // 父进程  
    close(p1[REC]);
    close(p2[SND]);
    
    write(p1[SND], "P", 1);
    
    char buf;
    if (read(p2[REC], &buf, 1) > 0) {
        printf("%d: received pong\n", getpid());
    }
    
    close(p1[SND]);
    close(p2[REC]);
    wait(0);
    exit(0);
}

- 关闭未使用的管道端口
    - 父进程不需要使用 p1 的读取端(REC)和 p2 的写入端(SND),所以关闭这些端口。
- 发送消息
    - 使用 write() 向 p1[SND] 写入一个字节 “P“,这将启动子进程的读取操作。
- 等待并读取子进程的响应
    - 使用 read() 从 p2[REC] 读取一个字节(buf),这将阻塞父进程,直到子进程写入数据。
    - 如果读取成功,打印 <pid>: received pong,其中 <pid> 是父进程的进程 ID
- 关闭剩余的管道端口
    - 关闭 p1[SND] 和 p2[REC],等待子进程结束(wait(0)),然后父进程正常退出。

primes

实现一个经典的”埃拉托斯特尼筛法”(Sieve of Eratosthenes)的多进程版本,使用 pipefork 创建进程链,每个进程从前一个进程接收数字并筛选出素数,直到处理完从2到35之间的所有数字。

代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

enum PIPE_END {REC = 0,SND = 1};

//执行素数筛选
void sieve(int pleft[2]) {
    int p;

    // 读取当前管道中的第一个数,即素数
    if(read(pleft[REC], &p, sizeof(p)) == 0 || p == -1) {
        // 如果读取失败或遇到哨兵值-1,则退出
        close(pleft[REC]);
        exit(0);
    }

    printf("prime %d\n", p);

    int pright[2];
    pipe(pright);   // 创建用于传递给下一个筛子的管道

    if(fork() == 0) {
        // 子进程,处理下一个筛子阶段
        close(pright[SND]);
        close(pleft[REC]);

        sieve(pright);  // 递归调用 sieve 处理下一个筛子阶段
    }
    else {
        // 父进程,负责筛选和传递数据
        close(pright[REC]);

        int buf;
        while(read(pleft[REC], &buf, sizeof(buf)) > 0 && buf != -1) {
            if(buf % p != 0) {
                write(pright[SND], &buf, sizeof(buf));
            }
        }

        close(pleft[REC]);
        close(pright[SND]);

        wait(0);
    }

    exit(0);
}

int main(int argc, char* argv[]) {
    int input_pipe[2];
    pipe(input_pipe); // 创建主进程的输入管道

    if(fork() == 0) {
        // 子进程,执行筛选
        close(input_pipe[SND]);  // 关闭子进程不需要的写入端
        sieve(input_pipe);
    }
    else {
        // 主进程,生成数列并传递给子进程
        close(input_pipe[REC]);

        for(int i = 2;i <= 35;i++) {
            write(input_pipe[SND], &i, sizeof(i));
        }

        int sentinel = -1;
        write(input_pipe[SND], &sentinel, sizeof(sentinel)); // 写入哨兵值表示结束
        close(input_pipe[SND]);   // 关闭写入端

        wait(0);  // 等待子进程完成
    }

    exit(0);
}

主函数 main
负责初始化程序,创建并输入数列,然后启动第一个筛选阶段

  1. 创建输入管道并启动子进程
1
2
3
4
5
6
7
8
int input_pipe[2];
pipe(input_pipe); // 创建主进程的输入管道

if(fork() == 0) {
    // 子进程,执行筛选
    close(input_pipe[SND]);  // 关闭子进程不需要的写入端
    sieve(input_pipe);
}
  • 子进程 负责处理数列筛选,执行 sieve 函数。
  • 父进程 生成数列,并将其通过 input_pipe 传递给子进程。
  1. 生成数列并传递给子进程
1
2
3
4
5
6
7
8
9
10
for (int i = 2; i <= 35; i++) {
write(input_pipe[1], &i, sizeof(i));
}

int sentinel = -1;
write(input_pipe[1], &sentinel, sizeof(sentinel));
close(input_pipe[1]);

wait(0);
exit(0);
  • 主进程生成 [2, 35] 之间的数字,并通过管道 input_pipe 传递给子进程。
  • sentinel 哨兵值 -1 被传入管道,标识数列的结束。
  • 关闭管道的写入端,等待子进程完成后退出程序。

sieve 函数

pleft[2] 是从前一个进程传入的管道,通过它,当前进程接收一系列数字,并筛掉可被某个素数整除的数字,然后将剩下的数字传给下一个进程。

  1. 读取素数并输出
1
2
3
4
5
if (read(pleft[0], &p, sizeof(p)) == 0 || p == -1) {
close(pleft[0]);
exit(0);
}
printf("prime %d\n", p);
  1. 创建管道和子进程
1
2
3
4
5
6
7
8
int pright[2];
pipe(pright);

if (fork() == 0) {
close(pright[1]);
close(pleft[0]);
sieve(pright);
}
  • pipe(pright):为下一层进程创建一个新的管道 pright
  • fork() 创建一个新的子进程:
    • 子进程,处理下一个筛子阶段
    • 父进程,负责筛选和传递数据
  1. 筛选并传递剩余数字
1
2
3
4
5
6
int buf;
while (read(pleft[0], &buf, sizeof(buf)) > 0 && buf != -1) {
if (buf % p != 0) {
write(pright[1], &buf, sizeof(buf));
}
}
  • 父进程不断从 pleft 读取数字,并筛选掉那些可以被当前素数 p 整除的数字。
  • 将剩余的数字写入到 pright 管道,传递给下一个进程进行处理。

find

编写一个简单的 UNIX find 程序版本,用于在目录树中查找具有特定名称的所有文件。

该功能的基本原理与 ls 命令类似,实际上可以通过对 ls.c 代码进行修改来实现。

代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fs.h"
#include "user/user.h"

void find(char* path, char* target) {
    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 ;
    }

    // 获取文件/目录的状态
    if(fstat(fd, &st) < 0) {
        fprintf(2, "find: cannot stat %s\n", path);
        close(fd);

        return ;
    }

    //判断文件类型
    switch (st.type) {
    case T_FILE:
        if(strcmp(path + strlen(path) - strlen(target), target) == 0) {
            printf("%s\n", path);
        }

        break;

    case T_DIR:
        if(strlen(path) + 1 + DIRSIZ + 1 > sizeof(buf)) {
            fprintf(2, "find: 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) {
                fprintf(2, "find: cannot stat %s\n",buf);
                continue;
            }

            //跳过 '.' 和 '..'
            if(strcmp(de.name, ".") != 0 && strcmp(de.name, "..") != 0) {
                find(buf,target);
            }
        }

        break;
    }

    close(fd);
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        fprintf(2, "Usage: find <directory> <filename>\n");
        exit(1);
    }

    find(argv[1], argv[2]);

    exit(0);
}
  1. find(char* path, char* target)

这是一个递归函数,接受两个参数:

  • path:需要搜索的目录路径。
  • target:要查找的目标文件名。

函数的作用是遍历目录下的所有文件和子目录,查找与 target 文件名匹配的文件。

  1. 打开目录并检查文件状态
1
2
3
4
5
6
7
8
9
10
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;
}
  • open(path, 0):以只读模式打开目录或文件,返回文件描述符 fd,如果无法打开,打印错误信息。
  • fstat(fd, &st):获取文件或目录的元数据信息(存储在 st 结构体中),用于判断它是文件还是目录。
  1. 判断文件类型
1
2
3
4
5
6
7
switch (st.type) {
case T_FILE:
if(strcmp(path + strlen(path) - strlen(target), target) == 0) {
printf("%s\n", path);
}
break;
...
  • 如果 st.typeT_FILE(文件),判断当前文件的名称是否与 target 相同。如果相同,就打印文件的路径。
  • strcmp(path + strlen(path) - strlen(target), target) == 0):通过比较路径末尾的文件名和目标文件名来确定是否匹配。
  1. 如果是递归,继续递归
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
case T_DIR:
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof(buf)) {
fprintf(2, "find: 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) {
fprintf(2, "find: cannot stat %s\n", buf);
continue;
}
if(strcmp(de.name, ".") != 0 && strcmp(de.name, "..") != 0) {
find(buf, target);
}
}
break;
  • 如果当前文件是 T_DIR(目录),将其路径保存在 buf 中,并开始读取目录内容。
  • while(read(fd, &de, sizeof(de)) == sizeof(de)):逐个读取目录中的文件或子目录项。
  • 如果 de.inum == 0,表示这个目录项为空,跳过。
  • 使用 memmove 将目录项的名称移动到缓冲区 buf,构建完整的路径名。
  • 跳过 ... 这两个特殊目录(当前目录和父目录),然后递归调用 find(buf, target),继续在子目录中查找。

xargs

编写一个简单版本的 xargs 程序,从标准输入读取行并为每行运行一个命令,将行内容作为命令的参数。

代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fs.h"
#include "kernel/param.h"
#include "user/user.h"

#define BUF_SIZE 2048
#define MAX_ARGS MAXARG

// 带参数列表,执行某个程序
void run(char* program, char** args) {
    if(fork() == 0) {
        exec(program, args);
        fprintf(2, "exec %s failed\n", program);
        exit(1);
    }
}

// 处理读入的参数并执行
void execute_command(char* program, char** argsbuf, char** pa) {
    *pa = 0;    // 参数列表末尾用 null 标识列表结束
    run(program, argsbuf);  // 执行命令
}

int main(int argc, char* argv[]) {
    char buf[BUF_SIZE]; // 读入时使用的内存池
    char *p = buf, *last_p = buf;   // 当前参数的结束、开始指针
    char *argsbuf[MAX_ARGS];    //全部参数列表,包含 argv 传进来的参数和 stdin 读入的参数
    char **args = argsbuf;  // 指向 argsbuf 中第一个从 stdin 读入的参数

    // 将 argv 提供的参数加入到最终的列表中
    for(int i = 1; i < argc; i++) {
        if(args - argsbuf >= MAX_ARGS - 1) {
            //fprintf(2, "too many arguments\n");
            exit(1);
        }

        *args++ = argv[i];
    }

    char **pa = args;
    int n;

    while((n = read(0, p, 1)) > 0) {
        if(*p == ' ' || *p == '\n') {
            *p = '\0';  // 将空格或换行替换为 '\0',分割开各个参数
            *pa++ = last_p;

            if(pa - argsbuf >= MAX_ARGS - 1) {
                fprintf(2, "too many arguments\n");
                exit(1);
            }

            last_p = p + 1;

            if(*p == '\n') {
                execute_command(argv[1], argsbuf, pa);
                pa = args;
            }
        }

        p++;
        if(p - buf >= BUF_SIZE) {
            fprintf(2, "input line too long\n");
            exit(1);
        }
    }

    if(n < 0) {
        fprintf(2, "read error\n");
        exit(1);
    }

    if(pa != args) {    // 如果最后一行不是空行
        *p = '\0';
        *pa++ = last_p;
        execute_command(argv[1], argsbuf, pa);      
    }

    while(wait(0) != -1) {};    // 循环等待所有子进程完成
    exit(0);
}
  1. run 函数 —— 执行程序
1
2
3
4
5
6
7
void run(char* program, char** args) {
if(fork() == 0) {
exec(program, args);
fprintf(2, "exec %s failed\n", program);
exit(1);
}
}
  • 功能:
    • run 函数用于执行一个指定的程序。程序路径通过 program 传递,参数通过 args 列表传递。
  • 逻辑:
    • fork(): 创建一个子进程。在子进程中,fork() 返回 0。
    • 如果 fork() 成功返回 0,子进程使用 exec(program, args) 替换当前进程映像为指定的程序。如果 exec 失败,程序会输出错误信息并调用 exit(1) 退出子进程。
  • 总结:
    • 子进程调用 exec 来执行指定命令。如果执行失败,则输出错误信息,并退出子进程。
  1. execute_command 函数 —— 构造参数列表并调用 run
1
2
3
4
void execute_command(char* program, char** argsbuf, char** pa) {
*pa = 0; // 参数列表末尾用 null 标识列表结束
run(program, argsbuf); // 执行命令
}
  • 功能:
    • 该函数负责构造命令的参数列表并调用 run 来执行指定的命令。
  • 逻辑:
    • *pa = 0: 将当前参数列表的末尾置为 NULL,这在 exec 函数中用来标识参数列表的结束。
    • 然后调用 run(program, argsbuf),将程序名和参数传递给 run 执行。
  1. 主函数 —— 读取标准输入并构建参数列表
1
2
3
4
5
int main(int argc, char* argv[]) {
char buf[BUF_SIZE]; // 读入时使用的内存池
char *p = buf, *last_p = buf; // 当前参数的结束、开始指针
char *argsbuf[MAX_ARGS]; // 全部参数列表,包含 argv 传进来的参数和 stdin 读入的参数
char **args = argsbuf; // 指向 argsbuf 中第一个从 stdin 读入的参数

初始化:

  • buf[BUF_SIZE]: 定义一个缓冲区用于从标准输入中读取数据,大小为 BUF_SIZE(2048 字节)。
  • plast_p: p 用于遍历缓冲区,last_p 记录每个参数的起始位置。
  • argsbuf: 定义了一个指针数组,用于存储命令的参数列表,包括命令行传入的参数和从标准输入中读取的参数。
  • args: 初始化为 argsbuf,它用于指向下一个要存储参数的位置。
1
2
3
4
5
6
for(int i = 1; i < argc; i++) {
if(args - argsbuf >= MAX_ARGS - 1) {
exit(1);
}
*args++ = argv[i];
}

命令行参数处理:

  • 通过遍历命令行参数,将每个参数(从 argv[1] 开始)添加到 argsbuf 中。
  • 每存储一个参数,args 会指向下一个空位。
  • 如果参数数量超过 MAX_ARGS - 1,程序会提前退出以避免数组溢出。
  1. 读取标准输入并执行命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
char **pa = args;
int n;

while((n = read(0, p, 1)) > 0) {
if(*p == ' ' || *p == '\n') {
*p = '\0'; // 将空格或换行替换为 '\0',分割开各个参数
*pa++ = last_p;

if(pa - argsbuf >= MAX_ARGS - 1) {
exit(1);
}

last_p = p + 1;

if(*p == '\n') {
execute_command(argv[1], argsbuf, pa);
pa = args;
}
}

p++;
if(p - buf >= BUF_SIZE) {
exit(1);
}
}
  • 读取输入:
    • read(0, p, 1): 每次从标准输入读取一个字符。
    • 当遇到空格 ' ' 或换行符 '\n' 时,将其替换为 '\0',这样每个参数字符串以 '\0' 结束,便于处理。
    • 每当检测到一个参数结束(空格或换行),将该参数的起始位置 last_p 存入 argsbuf 中。
  • 换行符处理:
    • 遇到换行符 '\n' 表示该行输入结束,调用 execute_command 来执行命令,将收集到的参数列表传入执行。执行后将 pa 重置为指向第一个参数的位置 args,准备处理下一行输入。
  1. 错误处理与最后的命令执行
1
2
3
4
5
6
7
8
9
10
11
12
13
    if(n < 0) {
exit(1);
}

if(pa != args) { // 如果最后一行不是空行
*p = '\0';
*pa++ = last_p;
execute_command(argv[1], argsbuf, pa);
}

while(wait(0) != -1) {}; // 循环等待所有子进程完成
exit(0);
}
  • 错误处理:
    • 如果 read 函数返回负值,表示读取发生错误,程序会退出。
  • 最后的命令执行:
    • 如果最后一行没有换行符但包含输入,则执行 execute_command 来处理这最后的命令。
  • 等待子进程:
    • while(wait(0) != -1): 等待所有子进程结束。wait 返回 -1 表示没有更多的子进程需要等待。
-------------本文结束感谢您的阅读-------------