Linux子进程
2022-02-01 # 学习笔记 # Linux

linux子进程的创建和回收

子进程的创建

我们可以采用以下方式循环的创建多个子进程

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

int main(int argc,char*argv[])
{
pid_t pid;
int i;
for (i = 0; i < 5; i++)
{
if (fork() == 0)
break;
}

printf("I'm %dth child\n", i + 1);

if (i == 5)
printf("I'm parent\n");

}

上述创建方式的问题

子进程没有被父进程回收

父进程先于子进程结束,子进程变成了孤儿进程。

在linux系统中,孤儿进程会被init进程所收养。并由init进程负责回收。

在上述创建的过程中,可能会存在着下面这种情况。

我们发现,该程序的输出结果夹在了两条bash之间,这是因为thread.out这个进程是bash的子进程。bash在发现该进程结束后,就继续输出了。但是此时thread.out这个进程的子进程还没有结束。因此我们需要让thread.out这个父进程在其子进程结束之后结束。因此我们需要引入wait函数。

子进程的回收

在上述例子中,子进程变成了孤儿进程。被init进程所收养和回收。但更好的方式是我们通过父进程的调用来手动回收子进程。此时便引入了wait和waitpid函数。

wait函数

1
pid_t wait(int * status)

参数说明:

pid_t——回收进程的pid (失败为-1)

status——回收进程的状态

status这一变量可通过调用不同的宏来读取,可以用来判断子进程是否为正常终止,或者异常终止的信号是多少。

注意:这一函数仅能回收一个子进程。如果要回收多个子进程的话,需要用while来循环调用。

status的使用

1.判断是否为正常结束

1
2
WIFEXITED(status)
WIFSIGNALED(status)

若程序为正常退出(通过exit() ,return from main()等方式),则WIFEXITED返回true

如果程序异常终止,被信号终止(例如kill -9),那么WIFSIGNALED会返回true

2.获取结束值

1
2
WEXITSTATUS(status)
WTERMSIG(status)

WEXITSTATUS在子进程正常退出的情况下调用,会得到子进程的返回值

WTERMSIG在子进程异常退出的情况下调用,会得到子进程异常终止的信息编号。(例如被kill -9终止,则会返回9)

3.过程总结

获取子进程正常终止值:

WIFEXITED(status) ——> 为真 ——>调用 WEXITSTATUS(status) ——> 得到 子进程 退出值。

获取导致子进程异常终止信号:

WIFSIGNALED(status) ——> 为真——>调用 WTERMSIG(status——> 得到 导致子进程异常 终止的信号编号

waitpid函数

1
pid_t waitpid(pid_t pid, int *status, int options)

作用和wait函数相同,但可以指定某一个进程回收,并且可以选择不阻塞。

参数说明

参数pid:

  • > 0:待回收的子进程pid

  • -1:任意子进程

  • 0:同组的子进程(?)

status:同wait

options:可指定为WNOHANG,表示回收方式为非阻塞。若指定为0,则同wait(NULL),表示阻塞等待

返回值pid_t:

  • > 0 : 成功回收的子进程 pid
  • 0 : 函数调用时, option指定了WNOHANG, 并且在函数调用时所指定的子进程没有结束。
  • -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
26
27
28
29
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
pid_t pid;
int i;
for (i = 0; i < 5; i++){
pid = fork();
if (pid == 0)
break;
}

if (i == 5){
int status;
pid_t wpid;
printf("I'm parent.\n");
while ((wpid = waitpid(-1, &status, WNOHANG)) != -1){
if (wpid == 0){
sleep(1);
}else{
printf("wait pid = %d\n", wpid);
}
}
}else {
printf("I'm %dth child. my pid = %d\n",i,getpid());
}

补充

僵尸进程

子进程结束后,父进程尚未做出回收操作,子进程其他资源虽然已经被系统回收(会关闭所有文件描述符,释放内存等),但是PCB依旧保留于内核中,使子进程变成僵尸进程。

每一个子进程都会经历僵尸进程的阶段,即已经结束但还未被回收的时间。僵尸进程在系统的进程列表中会被明确的用方括号和 标注出来,如下图

虽然每个进程都会经历僵尸进程的阶段,但是若一直不被父进程回收也是会存在着问题的。此时可以kill父进程。从而使子进程被init进程收养,由init进程负责回收

Q:为什么在进程结束后PCB仍然留在内核中

A:方便父进程判断子进程结束的方式和状态等,同时也可以接收子进程的返回值。如果是正常终止则保留着推出状态,如果是异常终止,则保留着使该进程终止的信号,方便父进程依此做出决策。