进程控制 3个月前

编程语言
348
进程控制

进程控制主要是指进程的退出, 进程的回收和进程的特殊状态 孤儿进程僵尸进程

1. 结束进程

想要直接退出某个进程可以在程序的任何位置调用exit()或者_exit()函数。 函数的参数相当于退出码, 如果参数值为 0 程序退出之后的状态码就是0, 如果是100退出的状态码就是100。

// 专门退出进程的函数, 在任何位置调用都可以
// 标准C库函数
#include <stdlib.h>
void exit(int status);

// Linux的系统函数
// 可以这么理解, 在linux中 exit() 函数 封装了 _exit()
#include <unistd.h>
void _exit(int status);

在 main 函数中直接使用 return 也可以退出进程, 假如是在一个普通函数中调用 return 只能返回到调用者的位置,而不能退出进程。

// ***** return 必须要在main()函数中调用, 才能退出进程 *****
// 举例:
// 没有问题的例子
int main()
{
    return 0;    // 进程退出了
}

////////////////////////// 不能退出的例子 //////////////////////////

int func()
{
    return 666;    // 返回到调用者调用该函数的位置, 返回到 main() 函数的第19行
}

int main()
{
    // 调用这个函数, 当前进程不能退出
    int ret = func();
}

2. 孤儿进程

在一个启动的进程中创建子进程,这时候父子进程同时运行,但是父进程由于某种原因先退出了,子进程还在运行,这时候这个子进程就可以被称之为孤儿进程。

操作系统是非常关爱运行的每一个进程的,当检测到某一个进程变成了孤儿进程,这时候系统中就会有一个固定的进程领养这个孤儿进程。 如果使用Linux没有桌面终端,这个领养孤儿进程的进程就是 init 进程(PID=1) 如果有桌面终端,这个领养孤儿进程就是桌面进程。

那么问题来了,系统为什么要领养这个孤儿进程呢? 在子进程退出的时候, 进程中的用户区可以自己释放, 但是进程内核区的pcb资源自己无法释放,必须要由父进程来释放子进程的pcb资源,孤儿进程被领养之后,这件事爹就可以代劳,避免系统资源的浪费。

下面这段代码就可以得到一个孤儿进程:

int main()
{
    // 创建子进程
    pid_t pid = fork();

    // 父进程
    if(pid > 0)
    {
        printf("我是父进程, pid=%d\n", getpid());
    }
    else if(pid == 0)
    {
        sleep(1);    // 强迫子进程睡眠1s, 这个期间, 父进程退出, 当前进程变成了孤儿进程
        // 子进程
        printf("我是子进程, pid=%d, 父进程ID: %d\n", getpid(), getppid());
    }
    return 0;
}
# 程序输出的结果
$ ./a.out 
我是父进程, pid=22459
我是子进程, pid=22460, 父进程ID: 1        # 父进程向退出, 子进程变成孤儿进程, 子进程被1号进程回收

3. 僵尸进程

在一个启动的进程中创建子进程,这时候就有了父子两个进程,父进程正常运行, 子进程先与父进程结束, 子进程无法释放自己的PCB资源, 需要父进程来做这个件事儿, 但是如果父进程也不管, 这时候子进程就变成了僵尸进程。

僵尸进程不能将它看成是一个正常的进程,这个进程已经死亡,用户区资源已经被释放,只是还占用一些内核资源(PCB)。

运行下面的代码就可以得到一个僵尸进程了:

int main()
{
    pid_t pid;
    // 创建子进程
    for(int i=0; i<5; ++i)
    {
        pid = fork();
        if(pid == 0)
        {
            break;
        }
    }

    // 父进程
    if(pid > 0)
    {
        // 需要保证父进程一直在运行
        // 一直运行不退出, 并且也做回收, 就会出现僵尸进程
        while(1)
        {
            printf("我是父进程, pid=%d\n", getpid());
            sleep(1);
        }
    }
    else if(pid == 0)
    {
        // 子进程, 执行这句代码之后, 子进程退出了
        printf("我是子进程, pid=%d, 父进程ID: %d\n", getpid(), getppid());
    }
    return 0;
}
# ps aux 查看进程信息
# Z+ --> 这个进程是僵尸进程, defunct, 表示进程已经死亡
robin     22598  0.0  0.0   4352   624 pts/2    S+   10:11   0:00 ./app
robin     22599  0.0  0.0      0     0 pts/2    Z+   10:11   0:00 [app] <defunct> # 子进程
robin     22600  0.0  0.0      0     0 pts/2    Z+   10:11   0:00 [app] <defunct> # 子进程
robin     22601  0.0  0.0      0     0 pts/2    Z+   10:11   0:00 [app] <defunct> # 子进程
robin     22602  0.0  0.0      0     0 pts/2    Z+   10:11   0:00 [app] <defunct> # 子进程
robin     22603  0.0  0.0      0     0 pts/2    Z+   10:11   0:00 [app] <defunct> # 子进程

消灭僵尸进程的方法是杀死这个僵尸进程的父进程,这样僵尸进程的资源就被系统回收了. kill -9僵尸进程PID的方式是不能消灭僵尸进程的,这个命令只对活着的进程有效,僵尸进程已经死了,鞭尸是不能解决问题的。


4. 进程回收

为了避免僵尸进程的产生,一般我们会在父进程中进行子进程的资源回收 回收方式有两种,一种是阻塞方式wait(),一种是非阻塞方式waitpid()

4.1 wait()

这是个阻塞函数,如果没有子进程退出, 函数会一直阻塞等待 当检测到子进程退出了, 该函数阻塞解除回收子进程资源。 这个函数被调用一次, 只能回收一个子进程的资源,如有多个子进程需要资源回收, 函数需被调用多次。

// 函数原型
// man 2 wait
#include <sys/wait.h>

pid_t wait(int *status);
  • 参数:传出参数,通过传递出的信息判断回收的进程是怎么退出的,如果不需要该信息可以指定为 NULL。取出整形变量中的数据需要使用一些宏函数,具体操作方式如下:
    • WIFEXITED(status): 返回1, 进程是正常退出的
    • WEXITSTATUS(status):得到进程退出时候的状态码,相当于 return 后边的数值, 或者 exit()函数的参数
    • WIFSIGNALED(status): 返回1, 进程是被信号杀死了
    • WTERMSIG(status): 获得进程是被哪个信号杀死的,会得到信号的编号
  • 返回值:
    • 成功:返回被回收的子进程的进程ID
    • 失败: -1
      • 没有子进程资源可以回收了, 函数的阻塞会自动解除, 返回-1
      • 回收子进程资源的时候出现了异常

下面代码演示了如何通过 wait()回收多个子进程资源:

// wait 函数回收子进程资源
#include <sys/wait.h>

int main()
{
    pid_t pid;
    // 创建子进程
    for(int i=0; i<5; ++i)
    {
        pid = fork();
        if(pid == 0)
        {
            break;
        }
    }

    // 父进程
    if(pid > 0)
    {
        // 需要保证父进程一直在运行
        while(1)
        {
            // 回收子进程的资源
            // 子进程由多个, 需要循环回收子进程资源
            pid_t ret = wait(NULL);
            if(ret > 0)
            {
                printf("成功回收了子进程资源, 子进程PID: %d\n", ret);
            }
            else
            {
                printf("回收失败, 或者是已经没有子进程了...\n");
                break;
            }
            printf("我是父进程, pid=%d\n", getpid());
        }
    }
    else if(pid == 0)
    {
        // 子进程, 执行这句代码之后, 子进程退出了
        printf("我是子进程, pid=%d, 父进程ID: %d\n", getpid(), getppid());
    }
    return 0;
}

4.2 waitpid()

waitpid() 函数可以看做是 wait() 函数的升级版,通过该函数可以控制回收子进程资源的方式是阻塞还是非阻塞,另外还可以通过该函数进行精准打击,可以精确指定回收某个或者某一类或者是全部子进程资源。

// 函数原型
// man 2 waitpid
#include <sys/wait.h>
// 这个函数可以设置阻塞, 也可以设置为非阻塞
// 这个函数可以指定回收哪些子进程的资源
pid_t waitpid(pid_t pid, int *status, int options);
  • 参数:
    • pid:
      • -1:回收所有的子进程资源, 和wait()是一样的, 无差别回收,并不是一次性就可以回收多个, 也是需要循环回收的
      • 大于0:指定回收某一个进程的资源 ,pid是要回收的子进程的进程ID
      • 0:回收当前进程组的所有子进程ID
      • 小于 -1:pid 的绝对值代表进程组ID,表示要回收这个进程组的所有子进程资源
    • status: NULL, 和wait的参数是一样的
    • options: 控制函数是阻塞还是非阻塞
      • 0: 函数行为是阻塞的 ==> 和wait一样
      • WNOHANG: 函数行为是非阻塞的
  • 返回值
    • 如果函数是非阻塞的, 并且子进程还在运行, 返回0
    • 成功: 得到子进程的进程ID
    • 失败: -1
      • 没有子进程资源可以回收了, 函数如果是阻塞的, 阻塞会解除, 直接返回-1
      • 回收子进程资源的时候出现了异常

下面代码演示了如何通过 waitpid()阻塞回收多个子进程资源:

// 和wait() 行为一样, 阻塞
#include <sys/wait.h>

int main()
{
    pid_t pid;
    // 创建子进程
    for(int i=0; i<5; ++i)
    {
        pid = fork();
        if(pid == 0)
        {
            break;
        }
    }

    // 父进程
    if(pid > 0)
    {
        // 需要保证父进程一直在运行
        while(1)
        {
            // 回收子进程的资源
            // 子进程由多个, 需要循环回收子进程资源
            int status;
            pid_t ret = waitpid(-1, &status, 0);  // == wait(NULL);
            if(ret > 0)
            {
                printf("成功回收了子进程资源, 子进程PID: %d\n", ret);
                                // 判断进程是不是正常退出
                if(WIFEXITED(status))
                {
                    printf("子进程退出时候的状态码: %d\n", WEXITSTATUS(status));
                }
                if(WIFSIGNALED(status))
                {
                    printf("子进程是被这个信号杀死的: %d\n", WTERMSIG(status));
                }
            }
            else
            {
                printf("回收失败, 或者是已经没有子进程了...\n");
                break;
            }
            printf("我是父进程, pid=%d\n", getpid());
        }
    }
    else if(pid == 0)
    {
        // 子进程, 执行这句代码之后, 子进程退出了
        printf("===我是子进程, pid=%d, 父进程ID: %d\n", getpid(), getppid());
    }
    return 0;
}

下面代码演示了如何通过 waitpid()非阻塞回收多个子进程资源:

// 非阻塞处理
#include <sys/wait.h>

int main()
{
    pid_t pid;
    // 创建子进程
    for(int i=0; i<5; ++i)
    {
        pid = fork();
        if(pid == 0)
        {
            break;
        }
    }

    // 父进程
    if(pid > 0)
    {
        // 需要保证父进程一直在运行
        while(1)
        {
            // 回收子进程的资源
            // 子进程由多个, 需要循环回收子进程资源
            // 子进程退出了就回收, 
            // 没退出就不回收, 返回0
            int status;
            pid_t ret = waitpid(-1, &status, WNOHANG);  // 非阻塞
            if(ret > 0)
            {
                printf("成功回收了子进程资源, 子进程PID: %d\n", ret);
                // 判断进程是不是正常退出
                if(WIFEXITED(status))
                {
                    printf("子进程退出时候的状态码: %d\n", WEXITSTATUS(status));
                }
                if(WIFSIGNALED(status))
                {
                    printf("子进程是被这个信号杀死的: %d\n", WTERMSIG(status));
                }
            }
            else if(ret == 0)
            {
                printf("子进程还没有退出, 不做任何处理...\n");
            }
            else
            {
                printf("回收失败, 或者是已经没有子进程了...\n");
                break;
            }
            printf("我是父进程, pid=%d\n", getpid());
        }
    }
    else if(pid == 0)
    {
        // 子进程, 执行这句代码之后, 子进程退出了
        printf("===我是子进程, pid=%d, 父进程ID: %d\n", getpid(), getppid());
    }
    return 0;
}
image
EchoEcho官方
无论前方如何,请不要后悔与我相遇。
1377
发布数
439
关注者
2222587
累计阅读

热门教程文档

Golang
23小节
MyBatis
19小节
C#
57小节
QT
33小节
PHP
52小节