当前位置: 首页 > article >正文

05-Linux系统编程之进程(下)

一、子进程资源回收

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 回收子进程资源的特点:

    1. 如果父进程还有子进程在运行,调用 wait 会阻塞,直到某一个子进程退出,父进程回收该子进程资源;
    2. 父进程每调用一次 wait ,只能回收一个子进程资源,如果父进程没有子进程 wait 解阻塞;
    3. 如果子进程先结束,父进程后调用 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. 从运行结果可以看到,子进程已经被 1 号进程接管了,此时的子进程是孤儿进程;
    2. 要想强制结束孤儿进程,可以通过命令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
  • 说明:
    1. 代码里面我们调用了三次 fork 函数,本意是创建三个子进程,加上父进程一共 4 个子进程;
    2. 但实际上这里创建了 8 个进程,即 2^3 个进程。
  • 通过上面的进程号我们可以看出端倪:
第2次调用 fork:
父进程6171创建了子进程6172

第2次调用 fork:
父进程6171创建了子进程6173
子进程6172创建了子进程6174

第3次调用 fork:
父进程6171创建了子进程6175
子进程6172创建了子进程6176
子进程6173创建了子进程6177
子进程 6174创建了子进程6178
  • 说明:
    1. 根据上面的分析我们已经可以看出 BUG ,因为父进程创建了子进程,接着再调用 fork 的时候,父进程和上一次创建的子进程又会分别创建一个子进程,那么每次进程总数就是调用 fork 前进程总数的 2 倍,因此调用 n 次 fork ,就会创建 2^n 个进程;
    2. 子进程之所以还会继续创建子进程,是因为子进程会复制父进程的使用代码资源,包括未调用的 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
  • 说明:
    1. 可以看到,这里总共创建了 4 个进程每一个父进程:7021,三个子进程:7022、7023、7024;
    2. 其原理就是将 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进程组介绍

  1. 进程组是一个或多个进程的集合。

  2. 组长进程:某个进程 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组里,也验证了下面第三条。

  3. 一个父进程创建多个子进程,那么这些子进程默认和父进程在同一个进程组;

  4. 如果组长进程结束了,进程组并不会结束,也不会产生新的进程组,直到这个进程组里的进程全部结束或转移到其它进程组,这个进程组才会结束;

    • 代码演示
    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     
    

    可以看到,组长进程结束了,但进程组还在,而且也没有出现新的组长进程,强制结束使用进程组中的进程,进程组才结束。

  5. 如果产生孤儿进程,我们通过 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会话介绍

  1. 会话是一个或多个进程组的集合;
  2. 会话首进程:即进程 ID等于进程组 ID等于会话 ID 的进程称为会话首进程;
  3. 创建会话时,用于会话首进程的进程不能是组长进程,如果将一个组长进程设置成会话首进程,那么同组的其中进程也会改变进程组,会影响到其它进程,为防止这种情况发送,系统直接不允许将组长进程设置成某个会话的会话首进程;
  4. 该进程成为一个新进程组的组长进程需有 root 权限(ubuntu 不需要);
  5. 新会话丢弃原有的控制终端,该会话没有控制终端,因此我们需要在一个进程里面创建子进程,然后终止父进程,使子进程没有控制终端;
  6. 建立新会话时,先调用 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.守护进程

  • 创建守护进程的步骤:

    1. 创建子进程,父进程退出(必须) 所有工作在子进程中进行,形式上脱离了控制终端;
    2. 在子进程中创建新会话(必须) ,使子进程完 全独立出来,脱离控制;
    3. 改变当前目录为根目录(不是必须), 使用 chdir() 函数,防止占用可卸载的文件系统,也可以换成其它路径;
    4. 重设文件权限掩码(不是必须) umask() 函数,防止继承的文件创建屏蔽字拒绝某些权限,增加守护进程灵活性;
    5. 关闭文件描述符(不是必须) ,继承的打开文件不会用到,浪费系统资源,无法卸载;
    6. 开始执行守护进程核心工作(必须) ,守护进程退出处理程序模型。
  • 代码演示

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)
    {
        // 核心任务代码
    }
}

http://www.kler.cn/a/472417.html

相关文章:

  • 第3章:Go语言复合数据类型
  • 【网络协议】静态路由详解
  • 【AI-21】深度学习框架中的神经网络
  • 计算机网络(第8版)第3章--PPP课后习题
  • 深入Android架构(从线程到AIDL)_18 SurfaceView的UI多线程02
  • 虹软人脸识别
  • Transformer深度学习实战TT100K中国交通标志识别
  • 2025第2周 | JavaScript中的函数的参数默认值和剩余参数
  • Excel | 空格分隔的行怎么导入excel?
  • 【论文+源码】创建一个基于Spring Boot的体育场管理系统
  • Golang开发-案例整理汇总
  • 如何优化 Linux 服务器的磁盘 I/O 性能
  • flink state源码解析
  • 鸿蒙APP之从开发到发布的一点心得
  • 【Elasticsearch】索引创建、修改、删除与查看
  • Android UI:View:Scroll
  • 68.基于SpringBoot + Vue实现的前后端分离-心灵治愈交流平台系统(项目 + 论文PPT)
  • 哦?将文本转换为专业流程图的终极解决方案?
  • 监控系统zabbix1.0
  • vulnhub靶场【DC系列】之4
  • 游戏引擎学习第75天
  • 代码管理助手-Git
  • docker内外如何实现ROS通信
  • 未来无人机发展趋势!
  • Ubuntu 下测试 NVME SSD 的读写速度
  • 吾杯网络安全技能大赛——Misc方向WP