OSTEP 阅读笔记(Ch05.进程API)

第 5 章 插叙:进程 API

  • UNIX 系统中的进程 API
1
2
3
fork();
exec();
wait();

5.1 fork()

  • 调用一次,返回两次
    • 父进程返回子进程的 pid
    • 子进程返回 0
  • 父进程和子进程的运行顺序是不确定的
  • 由于CPU 调度程序非常复杂,所以我们不能假设哪个进程会先运行

5.2 wait()

  • 等待子进程运行结束后再运行
    • 具体等待哪个子进程、几个子进程由调用参数决定

5.3 exec()

  • 让子进程运行不一样的程序
  • 调用一次,从不返回
  • 没有创建新进程,而是直接将当前运行的程序替换为不同的运行程序
  • 从可执行程序中加载代码和静态数据,并用它覆写自己的代码段(以及静态数据),重新初始化堆、栈及其他内存空间
1
2
3
4
5
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p3.c"); // argument: file to count
myargs[2] = NULL; // marks end of array
execvp(myargs[0], myargs); // runs word count

5.4 为什么这样设计 API

  • UNIX shell
    • 给了shell 在 fork 之后 exec 之前运行代码的机会
    • 这样可以改变运行时环境,实现有趣的功能
  • 重定向文件输出

5.5 其他 API

  • kill()
  • 信号(signal)、
  • 查看 man 手册获取更多知识

作业

(1)

(2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char** argv) {
int pid;
// 当关闭了标准输出之后, open 找到的第一个可用的文件描述符就是 1
// 此时 printf 输出到 open 打开的文件里
close(STDOUT_FILENO);
open("2.txt", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);
pid = fork();
if(pid < 0) {
fprintf(stderr, "Fork Error!\n");
}
else if(pid == 0) {
// child
printf("cccccc");
} else {
// parent
printf("pppppp");
}
return 0;
}
  • 子进程和父进程都能共访问 open() 返回的文件描述符,因为父子进程虽然i有自己的文件描述符列表,但是共享打开文件
  • 父子进程同时写文件的时候不能保证谁先写入,我的 wsl 中结果为
1
ppppppcccccc

(3)

(4)

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
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>

int main(int argc, char** argv, char** env) {
int pid;
pid = fork();
if(pid < 0) {
fprintf(stderr, "Fork Error!\n");
}
else if(pid == 0) {
// child
char *myargs[2];
myargs[0] = strdup("/bin/ls");
myargs[1] = NULL;
// execl(myargs[0], myargs[0], NULL);
// execle(myargs[0], myargs[0], NULL, env);
// execlp("ls", myargs[0], NULL);
execv(myargs[0], myargs);
// execve(myargs[0], myargs, env);
// execvp("ls", myargs);
} else {
// parent
wait(NULL);
}
return 0;
}
  • 结果确实可以执行 ls 命令,输出当前目录下的文件按
  • 我的输出如下
1
2.c  2.out  2.txt  4.c  4.out
  • 同样的基本调用,不同的封装方式为了满足不同的应用场景需要
  • 区别如下
    • l:用参数列表的方式传递新程序的参数,最后一个参数需要显示的指定为 NULL
    • v:用数组的方式传递新程序的参数,数组最后一个值需要显示的指定为 NULL
    • e:设置新的环境变量,作为一个数组在最后一个参数传入
    • p:运行的可执行文件只需要传递文件名即可,否则需要传递全路径

(5)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>

int main(int argc, char** argv, char** env) {
int pid;
pid = fork();
if(pid < 0) {
fprintf(stderr, "Fork Error!\n");
}
else if(pid == 0) {
// child
printf("I'm child!\n");
} else {
// parent
printf("wait'return pid: %d\n", wait(NULL));
printf("child's pid: %d\n", pid);
printf("I'm father!\n");
}
return 0;
}
  • 输出如下
1
2
3
4
I'm child!
wait'return pid: 323
child's pid: 323
I'm father!
  • wait(NULL) 返回等待的子进程的 pid
  • 子进程使用 wait(NULL) 会返回 -1
  • 使用 man wait 可以查看具体的含义
    • wait(NULL) 操作当一个子进程结束的就会返回其 pid,等效于 waitpid(-1, &wstatus, 0)

(6)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>

int main(int argc, char** argv, char** env) {
int pid;
pid = fork();
if(pid < 0) {
fprintf(stderr, "Fork Error!\n");
}
else if(pid == 0) {
// child
printf("I'm child!\n");
} else {
// parent
printf("wait'return pid: %d\n", waitpid(-1, NULL, 0));
printf("child's pid: %d\n", pid);
printf("I'm father!\n");
}
return 0;
}
  • wait(NULL) 等价于 waitpid(-1, NULL, 0)

(7)

(8)

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
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char** argv, char** env) {
int pid;
int pipe_fd[2];
if(pipe(pipe_fd) == -1) {
fprintf(stderr, "Pipe Error!\n");
}
pid = fork();
if(pid < 0) {
fprintf(stderr, "Fork Error!\n");
}
else if(pid == 0) {
// child
dup2(pipe_fd[1], STDOUT_FILENO);
close(pipe_fd[0]);
printf("I'm child!\n");
close(pipe_fd[1]);
} else {
// parent
char* msg = NULL; // 系统帮助 malloc, 要求 len 设置为 0
size_t len = 0;
dup2(pipe_fd[0], STDIN_FILENO);
close(pipe_fd[1]);
getline(&msg, &len, stdin);
printf("I'm father!\nmessage from child: %s\n", msg);
close(pipe_fd[0]);
free(msg);
}
return 0;
}
  • 输出如下
1
2
3
I'm father!
message from child: I'm child!