一、子进程资源回收
1.概述
在每个进程退出的时候,内核释放该进程所有的资源,包括一些存储在栈区、全局区的数据、打开的文件、占用的内存等。但是仍有一部分信息没有释放,这些信息主要指进程控制块 PCB 的信息(包括进程号、退出状态、运行时间等)。
因此,就需要父进程来回收子进程的 PCB 资源。
回收资源的方法有两种:父进程可以通过调用 wait 或 waitpid 彻底清除掉子进程,同时还能得到子进程的退出状态。
2. wait 回收子进程资源
2.1 wait 等待子进程退出
- 函数介绍
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收该子进程的资源。
参数:
status : 子进程退出时的状态信息。(如果父进程不关心子进程的退出信息,可以将 status置 NULL)
返回值:
成功:已经结束子进程的进程号
失败: -1
-
wait 回收子进程资源的特点:
- 如果父进程还有子进程在运行,调用 wait 会阻塞,直到某一个子进程退出,父进程回收该子进程资源;
- 父进程每调用一次 wait ,只能回收一个子进程资源,如果父进程没有子进程 wait 解阻塞;
- 如果子进程先结束,父进程后调用 wait, wait 立即解阻塞并回收该子进程资源。
-
代码演示
void test11()
{
pid_t pid = fork();
if (pid > 0)
{
printf("父进程正在等待子进程退出... ...\n");
wait(NULL);
printf("父进程已经回收子进程%d资源\n", pid);
}
else if (pid == 0)
{
int i = 0;
for (i = 10; i >= 1; i--)
{
printf("子进程%d还剩%ds退出\n", getpid(), i);
sleep(1);
}
printf("子进程%d退出\n", getpid());
_exit(-1);
}
}
- 运行结果
父进程正在等待子进程退出... ...
子进程4589还剩10s退出
子进程4589还剩9s退出
子进程4589还剩8s退出
子进程4589还剩7s退出
子进程4589还剩6s退出
子进程4589还剩5s退出
子进程4589还剩4s退出
子进程4589还剩3s退出
子进程4589还剩2s退出
子进程4589还剩1s退出
子进程4589退出
父进程已经回收子进程4589资源
- 说明:上面的案例可以看到,父进程先运行了,然后遇到 wait 函数,因为有正在运行的子进程且当前没有子进程退出,因此父进程一直阻塞在 wait 函数这里,直到子进程运行结束退出了, wait 函数解堵塞回收子进程资源,又因为这个子进程结束,就没有其它子进程在运行了,因此 wait 函数结束,向下运行其它代码。
2.2 wait 获取子进程的退出状态
退出信息通过 int 类型的数据保存,在一个 int 中包含了多个字段,直接使用这个值是没有意 义的,我们需要用宏定义取出其中的每个字段,类似于我们前面用过的位域。
步骤 1:通过宏 WIFEXITED(status) 判断子进程是否是正常退出,如果正常退出,返回值为非 0;
步骤 2:如果子进程正常退出,使用宏 WEXITSTATUS(status) 返回子进程的退出状态,退出状态保存在 status 变量的 8~16 位。
- 代码演示
void test12()
{
pid_t pid = fork();
if (pid > 0)
{
// 定义一个 int 变量保存退出状态
int status;
printf("父进程正在等待子进程退出... ...\n");
wait(&status);
if (WIFEXITED(status)) // 判断如果非 0 正常退出
{
// 获取退出状态值
printf("子进程退出状态:%d\n", WEXITSTATUS(status));
}
printf("父进程已经回收子进程%d资源\n", pid);
}
else if (pid == 0)
{
int i = 0;
for (i = 3; i >= 1; i--)
{
printf("子进程%d还剩%ds退出\n", getpid(), i);
sleep(1);
}
printf("子进程%d退出\n", getpid());
_exit(88); // 随意设置一个退出状态值
}
}
- 运行结果
父进程正在等待子进程退出... ...
子进程4823还剩3s退出
子进程4823还剩2s退出
子进程4823还剩1s退出
子进程4823退出
子进程退出状态:88
父进程已经回收子进程4823资源
- 说明:上面的结果可以看到,已经成功获取了子进程退出状态值。
3. waitpid 回收子进程资源
3.1函数介绍
- 函数介绍
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
功能:等待子进程终止,如果子进程终止了,此函数会回收子进程的资源。
参数:
pid : 参数 pid 的值有以下几种类型:
pid > 0 回收指定进程号的子进程
pid = 0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid 不会等待它
pid = -1 等待任意子进程,此时 waitpid 和 wait 作用一样
pid < -1 等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值
status : 进程退出时的状态信息,和 wait() 用法一样,不关心写 NULL
options : options 提供了一些额外的选项来控制 waitpid()。
0:同 wait(),阻塞父进程,等待子进程退出。
WNOHANG:没有任何已经结束的子进程,则立即返回,设置不阻塞
WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(不怎么用到)
返回值:
waitpid() 的返回值比 wait() 稍微复杂一些,一共有 3 种情况:
1) 当正常返回的时候,waitpid() 返回收集到的已经回收子进程的进程号;
2) 如果设置了选项 WNOHANG,而调用中 waitpid() 还有子进程在运行,且没有子进程退出,返回 0;父进程的所有子进程都已经退出了,返回-1;返回>0 表示等到一个子进程退出;
3) 如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在,如:当 pid 所对应的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid() 就会出错返回,这时 errno 被设置为 ECHILD
3.2 wait 与 waitpid 等价的情况
wait 与 waitpid 等价:即 waitpid 满足回收所有进程,同时阻塞。
pid_t waitpid(-1, int *status, 0);
- 代码演示
void test12()
{
pid_t pid = fork();
if (pid > 0)
{
// 定义一个 int 变量保存退出状态
int status;
printf("父进程正在等待子进程退出... ...\n");
waitpid(-1, &status, 0);
if (WIFEXITED(status)) // 判断如果非 0 正常退出
{
// 获取退出状态值
printf("子进程退出状态:%d\n", WEXITSTATUS(status));
}
printf("父进程已经回收子进程%d资源\n", pid);
}
else if (pid == 0)
{
int i = 0;
for (i = 3; i >= 1; i--)
{
printf("子进程%d还剩%ds退出\n", getpid(), i);
sleep(1);
}
printf("子进程%d退出\n", getpid());
_exit(88); // 随意设置一个退出状态值
}
}
- 运行结果
父进程正在等待子进程退出... ...
子进程5223还剩3s退出
子进程5223还剩2s退出
子进程5223还剩1s退出
子进程5223退出
子进程退出状态:88
父进程已经回收子进程5223资源
- 说明:可以看到,和上面的运行效果是一样的。
二、特殊的进程
特殊的进程有:孤儿进程、僵尸进程、守护进程。
1.僵尸进程
僵尸进程:即子进程结束,但父进程没有回收子进程资源(PCB),这样的子进程称为僵尸进程。
危害:因为 PCB 资源没有被释放,占用大量的进程号,进程号是有限的。
- 代码演示
void test13()
{
pid_t pid = fork();
if (pid > 0)
{
printf("父进程号:%d\n", getpid());
while(1); // 阻塞,不让父进程退出
}
else if (pid == 0)
{
printf("子进程号:%d\n",getpid());
_exit(-1);
}
}
- 运行结果
父进程号:5738
子进程号:5739
// 阻塞在这里
- ps 命令查看进程
edu@edu:~$ ps -ajx | grep a.out
2693 5738 5738 2693 pts/21 5738 R+ 1000 1:20 ./a.out
5738 5739 5738 2693 pts/21 5738 Z+ 1000 0:00 [a.out] <defunct>
- 说明:上面的运行结果可以看到,子进程变成了僵尸进程,后面有个 。
2.孤儿进程
2.1普通的孤儿进程
孤儿进程:即父进程已经结束了,但子进程还在运行,此时的子进程没了父进程,成为了孤儿进程。
不管子进程没有危害,因为即使没有之前的父进程为其回收资源,但会被 1 号进程接管,会有 1 号进程回收其 PCB 资源。
- 代码演示
void test14()
{
pid_t pid = fork();
if (pid > 0)
{
printf("父进程号:%d\n", getpid());
_exit(-1); // 父进程退出
}
else if (pid == 0)
{
printf("子进程号:%d\n",getpid());
while(1); // 阻塞,不让子进程退出
}
}
- 运行结果
父进程号:5888
子进程号:5889
- ps 命令查看进程
edu@edu:~$ ps -ajx | grep a.out
1 5889 5888 2693 pts/21 2693 R 1000 0:23 ./a.out
- 说明:
- 从运行结果可以看到,子进程已经被 1 号进程接管了,此时的子进程是孤儿进程;
- 要想强制结束孤儿进程,可以通过命令
kill -9 进程号
。
2.3守护进程
守护进程:又叫精灵进程,是一种脱离终端的后台服务程序,一般为周期性,是特殊的孤儿进程。
守护进程要结合终端的知识一起看,这里先介绍。
三、多进程
创建多进程并不是简单的调用一次 fork 就创建一个子进程,这种创建方法会产生 BUG ,创建出超过预期数目的子进程。
1.创建多进程的 BUG
- 代码演示
void test15()
{
fork();
fork();
fork();
while(1);
}
- ps 命令查看进程
edu@edu:~$ ps -ajx | grep a.out
2693 6171 6171 2693 pts/21 6171 R+ 1000 0:02 ./a.out
6171 6172 6171 2693 pts/21 6171 R+ 1000 0:02 ./a.out
6171 6173 6171 2693 pts/21 6171 R+ 1000 0:02 ./a.out
6172 6174 6171 2693 pts/21 6171 R+ 1000 0:02 ./a.out
6171 6175 6171 2693 pts/21 6171 R+ 1000 0:02 ./a.out
6172 6176 6171 2693 pts/21 6171 R+ 1000 0:02 ./a.out
6173 6177 6171 2693 pts/21 6171 R+ 1000 0:02 ./a.out
6174 6178 6171 2693 pts/21 6171 R+ 1000 0:02 ./a.out
- 说明:
- 代码里面我们调用了三次 fork 函数,本意是创建三个子进程,加上父进程一共 4 个子进程;
- 但实际上这里创建了 8 个进程,即 2^3 个进程。
- 通过上面的进程号我们可以看出端倪:
第2次调用 fork:
父进程6171创建了子进程6172
第2次调用 fork:
父进程6171创建了子进程6173
子进程6172创建了子进程6174
第3次调用 fork:
父进程6171创建了子进程6175
子进程6172创建了子进程6176
子进程6173创建了子进程6177
子进程 6174创建了子进程6178
- 说明:
- 根据上面的分析我们已经可以看出 BUG ,因为父进程创建了子进程,接着再调用 fork 的时候,父进程和上一次创建的子进程又会分别创建一个子进程,那么每次进程总数就是调用 fork 前进程总数的 2 倍,因此调用 n 次 fork ,就会创建 2^n 个进程;
- 子进程之所以还会继续创建子进程,是因为子进程会复制父进程的使用代码资源,包括未调用的 fork 函数,因此子进程会接着创建它的那个 fork 函数后面调用往后的所有 fork 函数。
2.多进程的正确打开方式
根据前面的分析,我们发现 BUG 的主要原因就是子进程也调用了 fork ,那么我们只需要防止子进程调用 fork 就行了。
2.1创建多进程
- 代码演示
void test16()
{
int i = 0, n = 3;
// 创建 n 个子进程
for (i = 0; i < n; i++)
{
pid_t pid = fork();
if (pid == 0)
break;
}
while(1); // 阻塞,为了查看现象,运行完结束 ps 命令就没法查找到该进程了
}
- ps 命令查看进程
edu@edu:~$ ps -ajx | grep a.out
2693 7021 7021 2693 pts/21 7021 R+ 1000 0:03 ./a.out
7021 7022 7021 2693 pts/21 7021 R+ 1000 0:03 ./a.out
7021 7023 7021 2693 pts/21 7021 R+ 1000 0:03 ./a.out
7021 7024 7021 2693 pts/21 7021 R+ 1000 0:03 ./a.out
- 说明:
- 可以看到,这里总共创建了 4 个进程每一个父进程:7021,三个子进程:7022、7023、7024;
- 其原理就是将 fork 函数放到循环里去调用,然后判断 fork 函数的返回值,如果返回值是 0 ,则说明在子进程里面,就 break 退出循环,子进程不会继续调用 fork ,返回值非 0 ,说明在父进程里,不会退出循环,会执行完整的循环,调用指定次数的 fork 函数,创建指定个数的子进程。
2.2父子进程的代码分区
上面我们已经知道了如何正确创建多个子进程,那么我们要怎么区分哪个是对应子进程执行的代码呢,什么情况下是父进程执行的代码呢。
- 代码演示
void test17()
{
int i = 0, n = 3;
// 创建 n 个子进程
for (i = 0; i < n; i++)
{
pid_t pid = fork();
if (pid == 0)
break;
}
if (i == 0)
{
printf("子进程1,进程号:%d\n", getpid());
}
else if (i == 1)
{
printf("子进程2,进程号:%d\n", getpid());
}
else if (i == 2)
{
printf("子进程3,进程号:%d\n", getpid());
}
else if (i == n)
{
printf("父进程,进程号:%d\n", getpid());
}
while(1);
}
- 运行结果
父进程,进程号:7613
子进程3,进程号:7616
子进程2,进程号:7615
子进程1,进程号:7614
- ps 命令查看进程
edu@edu:~$ ps -ajx | grep a.out
2693 7613 7613 2693 pts/21 7613 R+ 1000 0:27 ./a.out
7613 7614 7613 2693 pts/21 7613 R+ 1000 0:27 ./a.out
7613 7615 7613 2693 pts/21 7613 R+ 1000 0:27 ./a.out
7613 7616 7613 2693 pts/21 7613 R+ 1000 0:27 ./a.out
2.3多个子进程资源回收
之前我们学习过 wait 回收单个子进程的资源,如果有多个子进程的时候,那该怎么回收呢?
- 代码演示
void test18()
{
int i = 0, n = 3;
// 创建 n 个子进程
for (i = 0; i < n; i++)
{
pid_t pid = fork();
if (pid == 0)
break;
}
if (i == 0)
{
int j = 0;
for (j = 3; j > 0; j--)
{
printf("子进程1,进程号:%d,还剩%d秒结束\n", getpid(), j);
sleep(1);
}
printf("子进程1:%d退出\n", getpid());
}
else if (i == 1)
{
int j = 0;
for (j = 4; j > 0; j--)
{
printf("子进程2,进程号:%d,还剩%d秒结束\n", getpid(), j);
sleep(1);
}
printf("子进程2:%d退出\n", getpid());
}
else if (i == 2)
{
int j = 0;
for (j = 5; j > 0; j--)
{
printf("子进程3,进程号:%d,还剩%d秒结束\n", getpid(), j);
sleep(1);
}
printf("子进程3:%d退出\n", getpid());
}
else if (i == n)
{
printf("父进程,进程号:%d\n", getpid());
// 回收子进程资源
while (1)
{
pid_t pid = waitpid(-1, NULL, WNOHANG); // 设置不挂起
if (pid > 0)
{
// 有一个子进程退出
printf("已回收子进程:%d的资源\n", pid);
}
else if (pid == 0)
{
// 还有子进程在运行,但没有子进程退出
continue;
}
else if (pid == -1)
{
printf("所有子进程已经退出\n");
break;
}
}
}
}
- 运行结果
父进程,进程号:8548
子进程2,进程号:8550,还剩4秒结束
子进程3,进程号:8551,还剩5秒结束
子进程1,进程号:8549,还剩3秒结束
子进程3,进程号:8551,还剩4秒结束
子进程2,进程号:8550,还剩3秒结束
子进程1,进程号:8549,还剩2秒结束
子进程3,进程号:8551,还剩3秒结束
子进程2,进程号:8550,还剩2秒结束
子进程1,进程号:8549,还剩1秒结束
子进程3,进程号:8551,还剩2秒结束
子进程2,进程号:8550,还剩1秒结束
子进程1:8549退出
已回收子进程:8549的资源
子进程2:8550退出
子进程3,进程号:8551,还剩1秒结束
已回收子进程:8550的资源
子进程3:8551退出
已回收子进程:8551的资源
所有子进程已经退出
- 说明:这种基本上是固定写法,只需要修改子进程和父进程执行的功能代码即可,这里提供 waitpid 回收子进程资源,可以通过返回值判断子进程的个数,以及什么时候子进程全部结束释放,结合前面 waitpid 函数的理解上面的代码。
四、进程扩展
1.终端
如图,在电脑终端,即屏幕开启一个终端窗口,就会创建一个 shell 进程,shell 是一个命令行界面,是操作系统提供的一个接口。shell 进程的 PCB 里面包含了控制终端,即可以往终端屏幕输出数据,或者从终端键盘获取数据。就和我们在 Linux 终端输入命令一样:
edu@edu:~/study/my_code$ ls
01-first_code.sh 02-shell变量.sh 03-file_operator.c 04-pid_code.c a.out a.txt b.txt c.txt test
我们可以往终端输入 ls 命令,shell 会解析这个命令,然后调用 ls 程序,ls 运行结束后将结果发送给 shell 进程,shell 进程再将数据显示在终端窗口上。
当我们调用 a.out 文件的时候,shell 进程会使用 fork() 创建新的进程,新的进程会继承 shell 进程的 PCB 信息,包括控制终端的信息,因此新的进程的控制终端也是同一个终端。shell 进程会将终端控制权限移交给 a.out ,一旦 a.out 运行结束,终端的权限会被 shell 回收。
如果,a.out 再创建一个子进程,父子进程都能识别终端,但是通过 scanf 获取输入的时候,只能一个个获取,因为终端输入的数据只有一份。一旦 a.out 结束,终端权限归还 shell ,子进程只能输出到终端,无法再通过终端获取。
#include <unistd.h>
char *ttyname(int fd);
功能:由文件描述符查出对应的文件名
参数:
fd:文件描述符
返回值:
成功:终端名
失败:NULL
2.进程组
2.1进程组介绍
-
进程组是一个或多个进程的集合。
-
组长进程:某个进程 id 和组 id 相同,那么这个进程就是组长进程;
- 代码演示
void test19() { int i = 0, n = 3; // 创建 n 个子进程 for (i = 0; i < n; i++) { pid_t pid = fork(); if (pid == 0) break; } while(1); }
- 运行结果
edu@edu:~$ ps -ajx | grep a.out 2693 12302 12302 2693 pts/21 12302 R+ 1000 0:01 ./a.out 12302 12303 12302 2693 pts/21 12302 R+ 1000 0:01 ./a.out 12302 12304 12302 2693 pts/21 12302 R+ 1000 0:01 ./a.out 12302 12305 12302 2693 pts/21 12302 R+ 1000 0:01 ./a.out
可以看到,父进程号为12302,进程组号为12302,那么父进程就是组长进程,它创建的三个子进程都在12302组里,也验证了下面第三条。
-
一个父进程创建多个子进程,那么这些子进程默认和父进程在同一个进程组;
-
如果组长进程结束了,进程组并不会结束,也不会产生新的进程组,直到这个进程组里的进程全部结束或转移到其它进程组,这个进程组才会结束;
- 代码演示
void test20() { int i = 0, n = 3; // 创建 n 个子进程 for (i = 0; i < n; i++) { pid_t pid = fork(); if (pid == 0) break; } if (i == 0) { while(1); } else if (i == 1) { while(1); } else if (i == 2) { while(1); } else if (i == n) { sleep(5); _exit(-1); } }
- 运行结果
edu@edu:~$ ps -ajx | grep a.out // 5秒前 2693 12443 12443 2693 pts/21 12443 S+ 1000 0:00 ./a.out 12443 12444 12443 2693 pts/21 12443 R+ 1000 0:02 ./a.out 12443 12445 12443 2693 pts/21 12443 R+ 1000 0:02 ./a.out 12443 12446 12443 2693 pts/21 12443 R+ 1000 0:01 ./a.out 2817 12448 12447 2817 pts/1 12447 S+ 1000 0:00 grep --color=auto a.out edu@edu:~$ ps -ajx | grep a.out // 5秒后 1 12444 12443 2693 pts/21 2693 R 1000 0:06 ./a.out 1 12445 12443 2693 pts/21 2693 R 1000 0:06 ./a.out 1 12446 12443 2693 pts/21 2693 R 1000 0:05 ./a.out edu@edu:~$ kill -9 -12443 edu@edu:~$ ps -ajx | grep a.out 2817 12481 12480 2817 pts/1 12480 R+ 1000 0:00 grep --color=auto a.out
可以看到,组长进程结束了,但进程组还在,而且也没有出现新的组长进程,强制结束使用进程组中的进程,进程组才结束。
-
如果产生孤儿进程,我们通过 kill 命令一个个很难结束所有进程,因此通过
kill -9 -进程组号
直接结束整个进程组即可。
2.2进程组相关函数
2.2.1获取当前进程组ID
- 函数介绍
#include <unistd.h>
pid_t getpgrp(void);
功能:获取当前进程的进程组 ID
参数:无
返回值:
总是返回调用者的进程组 ID
2.2.2获取指定进程组ID
- 函数介绍
pid_t getpgid(pid_t pid);
功能:获取指定进程的进程组 ID
参数:pid:进程号,如果 pid = 0,那么该函数作用和 getpgrp 一样
返回值:
成功:进程组 ID
失败:-1
2.2.3将进程添加到指定进程组
- 函数介绍
int setpgid(pid_t pid, pid_t pgid)
功能:改变进程默认所属的进程组,通常可用来加入一个现有的进程组或创建一个新进程组。
参数:
将参 1 对应的进程,加入参 2 对应的进程组中
返回值:
成功:0
失败:-1
3.会话
3.1会话介绍
- 会话是一个或多个进程组的集合;
- 会话首进程:即进程 ID等于进程组 ID等于会话 ID 的进程称为会话首进程;
- 创建会话时,用于会话首进程的进程不能是组长进程,如果将一个组长进程设置成会话首进程,那么同组的其中进程也会改变进程组,会影响到其它进程,为防止这种情况发送,系统直接不允许将组长进程设置成某个会话的会话首进程;
- 该进程成为一个新进程组的组长进程需有 root 权限(ubuntu 不需要);
- 新会话丢弃原有的控制终端,该会话没有控制终端,因此我们需要在一个进程里面创建子进程,然后终止父进程,使子进程没有控制终端;
- 建立新会话时,先调用 fork, 父进程终止,子进程调用 setsid 函数。
3.2会话案例
- 获取会话
#include <unistd.h>
pid_t getsid(pid_t pid);
功能:获取进程所属的会话 ID
参数:
pid:进程号,pid 为 0 表示查看当前进程 session ID
返回值:
成功:返回调用进程的会话 ID
失败:-1
- 设置会话
#include <unistd.h>
pid_t setsid(void);
功能:创建一个会话,并以自己的 ID 设置进程组 ID,同时也是新会话的 ID。调用了 setsid 函数的进程,既是新的会长,也是新的组长。
参数:无
返回值:
成功:返回调用进程的会话 ID
失败:-1
- 代码演示
void test21()
{
pid_t pid = fork();
if (pid > 0)
_exit(-1);
setsid();
while(1);
}
- ps 命令查看进程
edu@edu:~$ ps -ajx | grep a.out
1 14114 14114 14114 ? -1 Rs 1000 0:09 ./a.out
- 说明:可以看到,这里是一个孤儿进程,然后其 进程号等于进程组号等于会话号 ,因此,该进程是一个会话首进程。此时的这个进程已经脱离了终端的控制,之前我们通过当前终端创建的进程,只要当前终端关闭,那些进程都会被回收,但现在即使关闭了终端,该进程依然存在。
4.守护进程
-
创建守护进程的步骤:
- 创建子进程,父进程退出(必须) 所有工作在子进程中进行,形式上脱离了控制终端;
- 在子进程中创建新会话(必须) ,使子进程完 全独立出来,脱离控制;
- 改变当前目录为根目录(不是必须), 使用 chdir() 函数,防止占用可卸载的文件系统,也可以换成其它路径;
- 重设文件权限掩码(不是必须) umask() 函数,防止继承的文件创建屏蔽字拒绝某些权限,增加守护进程灵活性;
- 关闭文件描述符(不是必须) ,继承的打开文件不会用到,浪费系统资源,无法卸载;
- 开始执行守护进程核心工作(必须) ,守护进程退出处理程序模型。
-
代码演示
void test22()
{
// 1、创建子进程,父进程退出,形式上脱离终端(必须)
pid_t pid = fork();
if (pid > 0)
_exit(-1); // 父进程结束
// 2、子进程 设置会话 完全从当前终端 独立出来(必须)
setsid();
// 3、更改当前目录(将/根目录作为工作目录 不是必须)
chdir("/");
// 4、重置文件掩码(不是必须)
umask(0002);
// 5、关闭文件描述符 0、1、2 (不是必须)
close(0);
close(1);
close(2);
// 6、守护进程的核心任务
while (1)
{
// 核心任务代码
}
}