获取子进程和父进程的PID
1 |
|
fork–创建一个子进程
1 |
|
当 fork 成功返回,父子进程就创建成功了。但是,关于 fork 这个方法要讲得东西还是蛮多的,下面逐一介绍:
(一)父子进程的创建遵循写时复制
1 |
|
在这个程序中,可以让父进程睡眠 3s ,让子进程有机会修改 public_var 变量。如果父进程输出结果为 20,说明父子进程共享变量;如果父进程输出结果为 10,说明父子进程不共享变量。
子进程创建成功,此时父子进程是共用物理内存页的,对应下图中左半部分。可是当子进程开始修改对应物理页的数据,就会触发这块被修改的物理页写时复制机制(没有修改的物理页继续共享,谁修改谁拥有一块新的物理页并脱离原来的物理页),对应下图中右半部分。这就合理解释上述变量变与不变的现象了。
(二)fork 与 用户态缓冲区的数据
代码 A 和 代码 B 唯一的代码不同是 第一行的输出一个有换行符,一个没有换行符。
我们前面学习到,printf 函数是行缓冲区,即遇到换行符才会清空缓冲区。由于代码 A 没有换行符,导致用户态文件缓冲区中留有数据,那么 fork() 时,这部分数据也会复制,并且父子进程各自拥有自己的副本。
用两道面试题检验你:
1 |
|
(三)父子进程的文件描述符
fork() 时,子进程会复制父进程的 PCB ,其中 PCB 中含有打开的文件描述符列表。因此,父子进程拥有各自的打开文件描述符列表。但是,它们共享同一个打开文件。
所以,当你父进程修改文件偏移量,子进程同样受影响,尽管它们是不同的文件描述符,但是指向相同的文件。
下面看代码示例:
1 |
|
让父进程休眠 2s,以让子进程修改偏移量。如果最后父进程文件描述符的偏移量为 0 表明不会被子进程影响,如果不是就代表子进程和父进程虽然是不同的文件描述符,但是指向同一个文件,互相影响。
终止进程
正常终止
(一)_exit
1 |
|
status 表示程序的终止状态,父经常可以调用 wait() 获取该状态。
(二)exit
程序一般不会直接调用 _exit() ,而是调用库函数 exit() ,它会在调用 _exit() 前执行各种动作。
1 |
|
执行动作如下:
- 调用退出处理程序 (通过 atexit() 和 on_exit() 注册的函数),其执行顺序与注册顺序相反。
- 刷新 stdio 流缓冲区。
- 将 status 作为参数,调用 _exit() 系统调用。
退出处理函数由用户事先注册,当进程调用 exit() 正常终止时,会自动执行事先注册的退出处理函数。
1 |
|
它们的第一个参数就是用来提交的回调函数,并且可以提交多个回调函数,但是函数列表被执行是按照注册的顺序的相反顺序来执行。
异常终止
1 |
|
会给调用进程 (自己) 发送 SIGABRT 信号,该信号会导致进程终止,并产生 core 文件。
监控子进程
孤儿进程是父进程先于子进程结束的一种状态,但是不会对操作系统造成危害,因为会有 init 进程托管。
僵尸进程是子进程结束但是附近成没有感知到的一种状态,这种状态的进程不会被回收,会对操作系统造成影响,因为操作系统可以创建的进程数量是有限的。我们得确保父进程调用 wait 或 waitpid。
wait–等待子进程终止
1 |
|
调用 wait 的进程,会阻塞在 wait 方法处,直到有一个进程终止才解除阻塞,返回值是子进程的 PID。
status 参数是一个传出参数,<sys/wait.h> 头文件中定义了一组宏用于解析 status。
WIFEXITED(status)
宏用于检查子进程是否正常结束。
WEXITSTATUS(status)
宏用于获取正常结束时的退出码。
WCOREDUMP(status)
宏用于检查子进程产生 core 文件。这个宏并没有在 POSIX.1- 2001 标准中规定。因此,使用的时候,应该用 #ifdef WCOREDUMP … #endif 包裹起来。
1 |
|
wait 存在的一些限制:
- 如果有多个子进程, wait() 是无法等待某个特定子进程终止的, 只能依次等待每一个子进程终止。
- 如果没有子进程终止, wait() 会一直阻塞。有时候会希望非阻塞的等待:如果没有子进程终止,立刻返回。
- 只能监控子进程是否终止。对于子进程因某个信号 (如 SIGSTOP 或 SIGTTIN ) 而停止,或是已停止子进程收到 SIGCONT 信号后恢复执行, wait() 是无法监控这些情况的。
waitpid–等待子进程状态发生改变
1 |
|
pid 参数:
- pid > 0,表示等待进程 ID 为 pid 的子进程。
- pid = 0,表示等待同进程组的所有子进程。
- pid = -1,表示等待任意子进程。 wait(&status) 与 waitpid(-1, &status, 0) 等价。
- pid < -1,表示等待进程组 ID 为 |pid| 的所有子进程。
options 参数:
- WNOHANG 不阻塞。如果参数 pid 指定的子进程没有一个发生状态改变,则立即返回, waitpid()的返回值为 0。
- WUNTRACED 监控子进程是否因为某个信号而停止。
- WCONTINUED 监控已停止的子进程是否收到 SIGCONT 信号而恢复执行。
明显看到 waitpid 要比 wait 功能丰富,它可以突破 wait 存在的一些限制。
执行程序
execve()
可以将新程序加载到当前进程的内存空间。在这一过程中,会执行如下操作:
- 清楚当前进程的代码段、数据段、堆、栈、上下文…
- 加载新的可执行程序,设置代码段,数据段等
- 从新可执行程序 main() 的第一行开始执行
1 |
|
exec函数簇
下面这些库函数都是建立在系统调用 execve() 之上的,它们为执行新可执行程序提供了多种 API 选择。这些函数只是在指定程序名、命令行参数列表以及环境变量的方式上有所不同。
system–执行 shell 命令
1 |
|