进程的数据输入输出
问题:进程参数 和 环境变量 对于进程意味着什么?
进程参数和环境变量的意义
- 一般情况下,子进程的创建是为了解决某个子问题
- 子进程解决问题需要父进程
"数据输入"
(进程参数 & 环境变量) 设计原则:
- 子进程启动时必然用到的参数使用进程参数传递
- 子进程解决问题可能用到的参数使用环境变量传递
思考 : 子进程 如何将结果 返回 父进程?
#include <stdio.h>
int main()
{
printf("Test: Hello World!\n");
return 19;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out
Test: Hello World!
tiansong@tiansong:~/Desktop/linux$ echo $? // 打印最近一个进程的执行结果
19
深入理解父子进程
- 子进程的创建是为了并行的解决子问题(问题分解)
- 父进程需要通过子进程的结果最终解决问题(并获取结果)
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
进程等待系统接口
pid_t wait(int *status);
- 等待一个子进程完成, 并返回子进程标识和状态信息
- 当有多个子进程完成,随机挑选一个子进程返回
pid_t waitpid(pid_t pid, int *status, int options);
- 可等待特定的子进程或一组子进程
- 在子进程还未终止时,可通过 options 设置不必等待(直接返回)
进程退出系统接口
- 头文件:
#include <unistd.h>
void _exit(int status);
// 系统调用,终止当前进程- 头文件:
#include <stdlib.h>
void exit(int status);
// 库函数,先做资源清理,再通过系统调用终止进程void abort(void);
// 异常终止当前进程(通过产生 SIGABRT 信号终止)
进程退出状态解读
下面的程序运行后会发生什么?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid = 0;
int a = 1;
int b = 0;
int status = 0;
printf("parent = %d\n", getpid());
if ((pid = fork()) == 0) exit(-1);
printf("child = %d\n", pid);
if ((pid = fork()) == 0) abort();
printf("child = %d\n", pid);
if ((pid = fork()) == 0) a = a /b, exit(-1);
printf("child = %d\n", pid);
sleep(3);
while ((pid = wait(&status)) > 0) {
printf("child: %d, status = %x\n", pid, status);
}
return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out
parent = 8785
child = 8786
child = 8787
child = 8788
child: 8786, status = ff00 // ff ←→ -1
child: 8787, status = 86
child: 8788, status = 88
进程退出状态详解
core dump : 进程异常终止前状态
宏 | 描述 |
WIFEXITED(stat) | 通过 stat 判断进程是否正常结束 [main-return, exit(...)] |
WIFSIGNALED(stat) | 通过 stat 判断进程是否因为信号而被终止 [abort(...)] |
WIFSTOPPED(stat) | 通过 stat 判断进程是否因为信号而被暂停执行 |
WEXITSTATUS(stat) | 获取正常结束时的状态值 |
WTERMSIG(stat) | 获取导致进程终止的信号值 |
WSTOPSIG(stat) | 获取导致进程终停的信号值 |
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid = 0;
int a = 1;
int b = 0;
int status = 0;
printf("parent = %d\n", getpid());
if ((pid = fork()) == 0) exit(-1);
printf("child = %d\n", pid);
if ((pid = fork()) == 0) abort();
printf("child = %d\n", pid);
if ((pid = fork()) == 0) a = a /b, exit(-1);
printf("child = %d\n", pid);
sleep(3);
while ((pid = wait(&status)) > 0) {
if (WIFEXITED(status)) {
printf("Normal - child: %d, code: %d\n", pid, (char)WEXITSTATUS(status));
}
else if (WIFSIGNALED(status)) {
printf("Signaled - child: %d, code: %d\n", pid, WTERMSIG(status));
}
else {
printf("Signaled - child: %d, code: %d\n", pid, WSTOPSIG(status));
}
}
return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out
parent = 8699
child = 8700
child = 8701
child = 8702
Normal - child: 8700, code: -1
Signaled - child: 8701, code: 6
Signaled - child: 8702, code: 8
僵尸进程深度分析
根据实验做成的推理:父进程创建了三个子进程后休眠,在休眠的这段时间内,根据代码和打印,子进程已经运行结束.意外着三个子进程的生命期已经完结,在系统中应该不再存在.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid = 0;
int a = 1;
int b = 0;
int status = 0;
printf("parent = %d\n", getpid());
if ((pid = fork()) == 0) exit(-1);
printf("child = %d\n", pid);
if ((pid = fork()) == 0) abort();
printf("child = %d\n", pid);
if ((pid = fork()) == 0) a = a /b, exit(-1);
printf("child = %d\n", pid);
sleep(120); // 修改这里,便于实验观察
while ((pid = wait(&status)) > 0) {
if (WIFEXITED(status)) {
printf("Normal - child: %d, code: %d\n", pid, (char)WEXITSTATUS(status));
}
else if (WIFSIGNALED(status)) {
printf("Signaled - child: %d, code: %d\n", pid, WTERMSIG(status));
}
else {
printf("Signaled - child: %d, code: %d\n", pid, WSTOPSIG(status));
}
}
return 19;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out &
[1] 8921
parent = 8921
child = 8923
child = 8924
child = 8925
tiansong@tiansong:~/Desktop/linux$ pstree -p -A -s 8921 // 证明三个子进程存在于系统中
systemd(1)---sh(2545)---node(2555)---node(2738)---bash(2947)---a.out(8921)-+-a.out(8923)
|-a.out(8924)
`-a.out(8925)
tiansong@tiansong:~/Desktop/linux$ ps ax | grep pts/4 // 证明三个子进程存在于系统中
2947 pts/4 Ss 0:00 /bin/bash --init-file /home/tiansong/.vscode-server/bin/6509174151d557a81c9d0b5f8a5a1e9274db5585/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
8921 pts/4 S 0:00 ./a.out
8923 pts/4 Z 0:00 [a.out] <defunct> // Z 僵尸状态
8924 pts/4 Z 0:00 [a.out] <defunct> // Z 僵尸状态
8925 pts/4 Z 0:00 [a.out] <defunct> // Z 僵尸状态
9313 pts/4 R+ 0:00 ps ax
9314 pts/4 S+ 0:00 grep --color=auto pts/4
再次实验证明,三个子进程在父进程休眠期间已经运行结束,然而运行结束之后,这三个子进程的"尸体"并没有完成回收(因此进程树中被打印,在进程表中被发现 Z ),进入"僵尸状态"
僵尸进程(僵死状态)
- 理论上,进程 退出 / 终止 后应立即释放所 有系统资源
- 然而,
为了给父进程提供一些重要信息
,子进程 退出 / 终止 所占用的部分资源会暂留 当父进程收集这部分信息后(wait / waitpid), 子进程所有资源被释放
- 父进程调用 wait(), 为子进程 "收尸" 处理并释放暂留资源
- 若父进程退出,init / systemd 为子进程"收尸" 处理并释放暂留资源
僵尸进程的危害
僵尸进程保留进程的
终止状态 和 资源
的使用信息- 进程为何退出,进程消耗多少 CPU 时间,进程最大内存驻留值,等
如果僵尸进程得不到回收,那么可能影响正常进程的创建
- 进程创建最重要的资源是内存和进程标识(PID)
- 僵尸进程的存在可看作一种类型的内存泄露
- 当系统僵尸进程过多,可能导致进程标识不足,无法创建新进程
wait(...) 的局限
- 不能等待指定子进程,如果存在多个子进程,只能逐一等待完成
- 如果不存在终止的子进程,父进程只能阻塞等待
- 只针对终止的进程,无法发现暂停的进程
waitpid(...)
pid_t waitpid(pid_t pid, int *status, int options);
- 返回值相同,终止子进程标识符
- 状态值意义相同,记录子进程终止信息
- 特殊之处
pid | 意 义 |
pid > 0 | 等待进程标识为 pid 的进程 |
pid == 0 | 等待当前进程组中的任意子进程 |
pid == -1 | 等待任意子进程,即:wait(&stat) ←→ waitpid(-1, &stat, 0) |
pid < -1 | 等待指定进程组中的任意子进程 |
僵尸进程运行指南
下面的程序运行后发生什么?
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
static void worker(pid_t pid)
{
printf("grand-child: %d\n", pid);
sleep(150);
}
int main(int argc, char* argv[])
{
pid_t pid = 0;
int status = 0;
printf("parent = %d\n", getpid());
pid = fork(); // 父进程
if( pid < 0 ) {
printf("fork error\n");
}
else if( pid == 0 ) { // 子进程
int i = 0;
for(i=0; i<5; i++) {
if( (pid = fork()) == 0 ) { // 孙进程
worker(getpid());
break;
}
}
sleep(60);
printf("child(%d) is over...\n", getpid());
}
else {
printf("wait child = %d\n", pid);
sleep(120);
while( waitpid(pid, &status, 0) == pid ) {
printf("Parent is over - child: %d, status = %x\n", pid, status);
}
}
return 0;
}
请仔细阅读下述执行流
// 后台运行程序,创建 1 个父进程,1 个子进程,5 个孙子进程
//================================================== 第一阶段
tiansong@tiansong:~/Desktop/linux$ ./a.out &
[1] 15532
parent = 15532
wait child = 15534
grand-child: 15535
grand-child: 15536
grand-child: 15537
grand-child: 15538
grand-child: 15539
// 确认存在 1 个父进程,1 个子进程,5 个孙子进程
tiansong@tiansong:~/Desktop/linux$ ps ax | grep pts/7
12984 pts/7 Ss 0:00 /bin/bash --init-file /home/tiansong/.vscode-server/bin/6509174151d557a81c9d0b5f8a5a1e9274db5585/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
15532 pts/7 S 0:00 ./a.out
15534 pts/7 S 0:00 ./a.out
15535 pts/7 S 0:00 ./a.out
15536 pts/7 S 0:00 ./a.out
15537 pts/7 S 0:00 ./a.out
15538 pts/7 S 0:00 ./a.out
15539 pts/7 S 0:00 ./a.out
15617 pts/7 R+ 0:00 ps ax
15618 pts/7 S+ 0:00 grep --color=auto pts/7
// 确认存在 1 个父进程,1 个子进程,5 个孙子进程
tiansong@tiansong:~/Desktop/linux$ pstree -p -A -s 15532
systemd(1)---sh(2545)---node(2555)---node(2738)---bash(12984)---a.out(15532)---a.out(15534)-+-a.out(15535)
|-a.out(15536)
|-a.out(15537)
|-a.out(15538)
`-a.out(15539)
//================================================== 第二阶段
// 子进程结束
tiansong@tiansong:~/Desktop/linux$ child(15534) is over
// 子进程成为僵尸进程, Z
ps ax | grep pts/7
12984 pts/7 Ss 0:00 /bin/bash --init-file /home/tiansong/.vscode-server/bin/6509174151d557a81c9d0b5f8a5a1e9274db5585/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
15532 pts/7 S 0:00 ./a.out
15534 pts/7 Z 0:00 [a.out] <defunct>
15535 pts/7 S 0:00 ./a.out
15536 pts/7 S 0:00 ./a.out
15537 pts/7 S 0:00 ./a.out
15538 pts/7 S 0:00 ./a.out
15539 pts/7 S 0:00 ./a.out
15815 pts/7 R+ 0:00 ps ax
15816 pts/7 S+ 0:00 grep --color=auto pts/7
// 孙进程从当前进程数消失!!
tiansong@tiansong:~/Desktop/linux$ pstree -p -A -s 15532
systemd(1)---sh(2545)---node(2555)---node(2738)---bash(12984)---a.out(15532)---a.out(15534)
// 孙进程被 systemd 托管
tiansong@tiansong:~/Desktop/linux$ pstree -p -A -s 1
systemd(1)-+-ModemManager(912)-+-{ModemManager}(945)
| `-{ModemManager}(956)
|-NetworkManager(845)-+-{NetworkManager}(927)
| `-{NetworkManager}(930)
|-VGAuthService(800)
|-a.out(15535)
|-a.out(15536)
|-a.out(15537)
|-a.out(15538)
|-a.out(15539)
......
//================================================== 第三阶段
// 父进程运行结束
tiansong@tiansong:~/Desktop/linux$ Parent is over - child: 15534, status = 0
// 确认父进程,子进程消失(被父进程清理)
ps ax | grep pts/7
[1]+ Done ./a.out
12984 pts/7 Ss 0:00 /bin/bash --init-file /home/tiansong/.vscode-server/bin/6509174151d557a81c9d0b5f8a5a1e9274db5585/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
15535 pts/7 S 0:00 ./a.out
15536 pts/7 S 0:00 ./a.out
15537 pts/7 S 0:00 ./a.out
15538 pts/7 S 0:00 ./a.out
15539 pts/7 S 0:00 ./a.out
15931 pts/7 R+ 0:00 ps ax
15932 pts/7 S+ 0:00 grep --color=auto pts/7
//================================================== 第四阶段
// 孙进程结束
tiansong@tiansong:~/Desktop/linux$ child(15539) is over...
child(15536) is over...
child(15535) is over...
child(15537) is over...
child(15538) is over...
// 孙进程消失,被 systemd 清理
ps ax | grep pts/7
12984 pts/7 Ss 0:00 /bin/bash --init-file /home/tiansong/.vscode-server/bin/6509174151d557a81c9d0b5f8a5a1e9274db5585/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
15969 pts/7 R+ 0:00 ps ax
15970 pts/7 S+ 0:00 grep --color=auto pts/7
利用
wait(...)
返回值判断是否继续等待子进程while((pid = wait(&status)) > 0) {}
利用
waitpid(...)
及init / systemd
回收子进程- 通过
fork()
创建孙进程解决子进程 [将孙进程托付给init/systemd
进程]
- 通过
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。