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

news2025/1/8 11:24:07

一、子进程资源回收

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.coloradmin.cn/o/2273183.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HDFS异构存储和存储策略

一、HDFS异构存储类型 1.1 冷、热、温、冻数据 通常&#xff0c;公司或者组织总是有相当多的历史数据占用昂贵的存储空间。典型的数据使用模式是新传入的数据被应用程序大量使用&#xff0c;从而该数据被标记为"热"数据。随着时间的推移&#xff0c;存储的数据每周…

【51单片机】02LED流水灯实验

点亮你的LED 一、点亮第一个LED1.GPIO介绍2.P1、P2、P3端口 二、LED实验2.尝试点亮LED3.LED流水灯 一、点亮第一个LED 1.GPIO介绍 这块内容这里可以做简单的了解&#xff0c;与数电知识强相关。后续可以再回过头来学习 GPIO (general purpose input output) 通用输入输出端口…

springboot 集成 etcd

springboot 集成 etcd 往期内容 ETCD 简介docker部署ETCD 前言 好久不见各位小伙伴们&#xff0c;上两期内容中&#xff0c;我们对于分布式kv存储中间件有了简单的认识&#xff0c;完成了docker-compose 部署etcd集群以及可视化工具 etcd Keeper&#xff0c;既然有了认识&a…

云安全相关博客阅读(一)

2024-03-04 Cloudflare announces Firewall for AI 关注问题&#xff1a; 传统的WAF功能能够保护web和api安全&#xff0c;但是随着LLM等AI模型等出现&#xff0c;保护这些AI相关应用等安全是一个新出现的问题虽然AI应用是新的场景&#xff0c;但是以往的攻击方法也能够直接用…

2025年01月07日Github流行趋势

项目名称&#xff1a;khoj 项目地址url&#xff1a;https://github.com/khoj-ai/khoj项目语言&#xff1a;Python历史star数&#xff1a;20105今日star数&#xff1a;363项目维护者&#xff1a;debanjum, sabaimran, MythicalCow, aam-at, shantanuSakpal项目简介&#xff1a;你…

从零手写线性回归模型:PyTorch 实现深度学习入门教程

系列文章目录 01-PyTorch新手必看&#xff1a;张量是什么&#xff1f;5 分钟教你快速创建张量&#xff01; 02-张量运算真简单&#xff01;PyTorch 数值计算操作完全指南 03-Numpy 还是 PyTorch&#xff1f;张量与 Numpy 的神奇转换技巧 04-揭秘数据处理神器&#xff1a;PyTor…

【python】matplotlib(radar chart)

文章目录 1、功能描述和原理介绍2、代码实现3、效果展示4、完整代码5、多个雷达图绘制在一张图上6、参考 1、功能描述和原理介绍 基于 matplotlib 实现雷达图的绘制 一、雷达图的基本概念 雷达图&#xff08;Radar Chart&#xff09;&#xff0c;也被称为蛛网图或星型图&…

数据库环境安装(day1)

网址&#xff1a;MySQL 下载&#xff08;环境准备&#xff09;&#xff1a; &#xff08;2-5点击此处&#xff0c;然后选择合适的版本&#xff09; 1.linux在线YUM仓库 下载/安装: wget https://repo.mysql.com//mysql84-community-release-el9-1.noarch.rpm rpm -i https://r…

Fabric链码部署测试

参考链接&#xff1a;运行 Fabric 应用程序 — Hyperledger Fabric Docs 主文档 (hyperledger-fabric.readthedocs.io) &#xff08;2&#xff09;fabric2.4.3部署运行自己的链码 - 知乎 (zhihu.com) Fabric2.0测试网络部署链码 - 辉哥哥~ - 博客园 (cnblogs.com) 1.启动测试…

数据结构与算法之二叉树: LeetCode 107. 二叉树的层序遍历 II (Ts版)

二叉树的层序遍历 II https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/description/ 描述 给你二叉树的根节点 root &#xff0c;返回其节点值 自底向上的层序遍历 。 &#xff08;即按从叶子节点所在层到根节点所在的层&#xff0c;逐层从左向右遍历&a…

Python插件化开发实战:开发个图片浏览器

在本篇教程中&#xff0c;我将详细介绍如何使用Python开发一个基于插件架构的图片浏览器。这个项目将展示如何实现插件系统、如何处理图片显示,以及如何使用wxPython构建GUI界面。 “C:\pythoncode\pythonplugin\your_project\main_app.py” 项目概述 我们将开发一个具有以下…

根据python代码自动生成类图的实现方法[附带python源码]

概述 利用python库抽象语法树(AST)和类图描述语言(PlantUML),实现自动将python代码生成类图的目的。 环境 windowsvscodepythonplantuml ✒️网上好像大部分都是用Pyreverse库来实现的&#xff0c;但是我实际测试发现只能在一个文件中才能行&#xff0c;当然应该有解决方法…

下载b站高清视频

需要使用的edge上的一个扩展插件&#xff0c;所以选择使用edge浏览器。 1、在edge浏览器上下载 强力视频下载合并 扩展插件 2、在edge上打开b站&#xff0c;登录自己账号&#xff08;登录后才能下载到高清&#xff01;&#xff01;&#xff09;。打开一个视频&#xff0c;选择自…

flutter 专题二十四 Flutter性能优化在携程酒店的实践

Flutter性能优化在携程酒店的实践 一 、前言 携程酒店业务使用Flutter技术开发的时间快接近两年&#xff0c;这期间有列表页、详情页、相册页等页面使用了Flutter技术栈进行了跨平台整合&#xff0c;大大提高了研发效率。在开发过程中&#xff0c;也遇到了一些性能相关问题和…

UE5 打包要点

------------------------- 1、需要环境 win sdk &#xff0c;大约3G VS&#xff0c;大约10G 不安装就无法打包&#xff0c;就是这么简单。 ----------------------- 2、打包设置 编译类型&#xff0c;开发、调试、发行 项目设置-地图和模式&#xff0c;默认地图 项目…

vulnhub靶场【DC系列】之5

前言 靶机&#xff1a;DC-5&#xff0c;IP地址为192.168.10.4 攻击&#xff1a;kali&#xff0c;IP地址为192.168.10.2 都采用VMWare&#xff0c;网卡为桥接模式 对于文章中涉及到的靶场以及工具&#xff0c;我放置网盘中https://pan.quark.cn/s/2fcf53ade985 主机发现 使用…

双模态视觉特征流用于医学报告生成|文献速递-视觉大模型医疗图像应用

Title 题目 Dual-modality visual feature flow for medical report generation 双模态视觉特征流用于医学报告生成 01 文献速递介绍 医学报告生成是一项新兴的跨模态文本生成任务&#xff08;X. Zeng et al., 2020&#xff1b;Najdenkoska et al., 2022&#xff1b;Li et…

基于Fluent和深度学习算法驱动的流体力学计算与应用

物理模型与深度学习的融合&#xff1a;研究如何将传统的物理模型与深度学习算法相结合&#xff0c;以提高流体力学问题的预测准确性和计算效率。复杂流动模拟&#xff1a;利用深度学习技术对复杂流动现象进行模拟和分析&#xff0c;包括湍流、多相流、非牛顿流体等&#xff0c;…

记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-动态表单(2)

接1的内容&#xff0c;那么有这么一个需求&#xff01; 需求分析 需要修改某一个配置的时候 1.从对应的api中读取消息&#xff0c;消息内容为Json格式的 2.基于当前的Json渲染成表单提供给管理端的客户呈现 3.管理端的用户可以基于这个表单的内容进行修改&#xff0c;然后提交…

pycharm-pyspark 环境安装

1、环境准备&#xff1a;java、scala、pyspark、python-anaconda、pycharm vi ~/.bash_profile export SCALA_HOME/Users/xunyongsun/Documents/scala-2.13.0 export PATH P A T H : PATH: PATH:SCALA_HOME/bin export SPARK_HOME/Users/xunyongsun/Documents/spark-3.5.4-bin…