进程控制——进程等待

✅<1>主页::我的代码爱吃辣
<2>知识讲解:Linux——进程等待
☂️<3>开发环境:Centos7
<4>前言:之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。父进程通过进程等待的方式,回收子进程资源。

目录

一.进程退出场景

二.进程常见的退出方法

1. 从main返回

2. 调用exit

3.系统调用 _exit 

4.对比exit()和_exit()

5.ctrl + c,信号终止

三.进程等待

 1.进程等待的必要性

 2.进程等待的方法wait方法

 3.进程等待的方法wait方法waitpid方法

四.获取子进程的status

 1.代码正常终止

 2.代码异常终止


一.进程退出场景

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止

 见一见这三种情况:

测试代码程序结束结果正确:

正常终止(可以通过 echo $? 查看进程退出码)

#include 

int main()
{
    printf("***********begin**********\n");
    int sum = 0;
    for (int i = 1; i <= 100; i++)
        sum += i;
    printf("***********end**********\n");
    
    if (sum == 5050)
        return 0;
    else
        return 1;
}

makefile文件:

proc:proc.c
	gcc -o $@ $^ -std=c99

.PHONY:clean
clean:
	rm -rf proc

测试结果:

进程控制——进程等待_第1张图片

测试代码代码运行完毕,结果不正确:

#include 

int main()
{
    printf("***********begin**********\n");
    int sum = 0;
    for (int i = 1; i < 100; i++)
        sum += i;
    printf("***********end**********\n");

    if (sum == 5050)
        return 0;
    else
        return 1;
}

测试结果:

进程控制——进程等待_第2张图片

 测试代码程序异常终止:

  • 程序异常终止的时候,代码的结果是否正确已经不重要了。
  • 崩溃的本质:进程因为某些原因,导致进程收到了来自操作系统的信号(kill -9)。

注意:进程的退出码,只有八个比特位表示,如果我们返回的是-1,查询的推出吗是255.

二.进程常见的退出方法

1. 从main返回

我们上述展示的:测试代码程序结束结果正确和测试代码代码运行完毕,结果不正确,都是从main返回的示例。

2. 调用exit

测试代码:

#include 
#include 

int main()
{
    printf("***********begin**********\n");
    int sum = 0;
    for (int i = 1; i < 100; i++)
        sum += i;

    exit(100);

    printf("***********end**********\n");

    if (sum == 5050)
        return 0;
    else
        return 1;
}

测试结果:

进程控制——进程等待_第3张图片

3.系统调用 _exit 

进程控制——进程等待_第4张图片

测试代码:

#include 
#include 

int main()
{
    printf("***********begin**********\n");
    int sum = 0;
    for (int i = 1; i <= 100; i++)
        sum += i;

    _exit(1000);

    printf("***********end**********\n");

    if (sum == 5050)
        return -1;
    else
        return 1;
}

测试结果:

进程控制——进程等待_第5张图片

4.对比exit()和_exit()

 测试代码:

#include 
#include 
#include 

int main()
{
    printf("hello world");
    exit(0);

    return 0;
}

 我们将exit换成_exit:

#include 
#include 
#include 

int main()
{
    printf("hello world");
    _exit(0);

    return 0;
}

测试结果:

注意:

  1. exit在程序退出时,会刷新程序的缓冲区。
  2. _exit在程序推出时,就是单纯的直接退出,不会做任何处理。

进程控制——进程等待_第6张图片

注意:

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

 5.ctrl + c,信号终止

 通过信号的方式直接终止一个进程。

ctrl + c  就是使用 kill -9 信号直接终止进程。

测试代码:

#include 
#include 
#include 

int main()
{
    while (1)
    {
        printf("hello world\n");
        sleep(1);
    }

    return 0;
}

进程控制——进程等待_第7张图片

三.进程等待

 1.进程等待的必要性

  1. 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  2. 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  3. 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  4. 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

 2.进程等待的方法wait方法

#include
#include

pid_t wait(int*status);

返回值:

  • 成功返回被等待进程pid,失败返回-1。

参数:

  • 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

见一见僵尸状态:

测试代码:

#include 
#include 
#include 
#include 
#include 

int main()
{
    int id = fork();

    if (id == 0) // 子进程
    {
        int n = 5;
        while (n--)
        {
            printf("我是一个子进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
            sleep(1);
        }
        return 0;
    }
    else if (id > 0) // 父进程
    {
        int n = 10;
        while (n--)
        {
            printf("我是一个父进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
            sleep(1);
        }
    }
    wait(NULL);
    return 0;
}

测试结果:

进程控制——进程等待_第8张图片

进程控制——进程等待_第9张图片

 在子进程退出以后,出现了僵尸状态。

 回收僵尸状态:

#include 
#include 
#include 
#include 
#include 

int main()
{
    int id = fork();

    if (id == 0) // 子进程
    {
        int n = 5;
        while (n--)
        {
            printf("我是一个子进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
            sleep(1);
        }
        return 0;
    }
    else if (id > 0) // 父进程
    {
        wait(NULL);//子进程一旦退出就立马回收
        int n = 10;
        while (n--)
        {
            printf("我是一个父进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
            sleep(1);
        }
    }
    return 0;
}

测试结果:

进程控制——进程等待_第10张图片

进程控制——进程等待_第11张图片

说明:

  1. 子进程一旦退出,其僵尸状态就会被回收。
  2. 子进程没有退出的时候,父进程仍然可以继续执行自己的代码,即非阻塞。

 3.进程等待的方法wait方法waitpid方法

 pid_ t waitpid(pid_t pid, int *status, int options);

返回值:

  1. 当正常返回的时候waitpid返回收集到的子进程的进程ID;
  2. 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  3. 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:
pid:

  1. Pid=-1,等待任一个子进程。与wait等效。
  2. Pid>0.等待其进程ID与pid相等的子进程。

status:

  1. WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  2. WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

options:

  1. WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
  2. 0:当子进程还没有退出的时候,父进程阻塞在waitpid()函数处,等待子进程退出。

阻塞时等待任意一个子进程:

#include 
#include 
#include 
#include 
#include 

int main()
{
    int status;
    int id = fork();

    if (id == 0) // 子进程
    {
        int n = 5;
        while (n--)
        {
            printf("我是一个子进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
            sleep(1);
        }
        return 0;
    }
    else if (id > 0) // 父进程
    {
        waitpid(-1, &status, 0);
        int n = 10;
        while (n--)
        {
            printf("我是一个父进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
            sleep(1);
        }
    }
    return 0;
}

测试结果:

进程控制——进程等待_第12张图片

进程控制——进程等待_第13张图片

 非阻塞式等待任意一个子进程:

#include 
#include 
#include 
#include 
#include 

int main()
{
    int status;
    int id = fork();

    if (id == 0) // 子进程
    {
        int n = 5;
        while (n--)
        {
            printf("我是一个子进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
            sleep(1);
        }
        return 0;
    }
    else if (id > 0) // 父进程
    {
        int n = 10;
        while (n--)
        {
            pid_t pid = waitpid(-1, &status, WNOHANG);
            if (pid == 0)
            {
                printf("子进程还未结束\n");
            }
            else
            {
                printf("子进程已经结束!!!\n");
            }
            printf("我是一个父进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
            sleep(1);
        }
    }
    return 0;
}

测试结果:

进程控制——进程等待_第14张图片

进程控制——进程等待_第15张图片

说明:子进程还未结束时,父进程仍然可以自己执行,不受任何影响,如果父进程等待到子进程已经退出,就会回收子进程的僵尸状态。

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

四.获取子进程的status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

进程控制——进程等待_第16张图片

 注意:

  1. 如果进程正常终止,status 后面七位应该是0,代表没有收到信号,前面八位时退出码。
  2. 如果进程异常终止,前面七位不用处,中间一位core dump 标志位,最后七位时收到的信号。

 1.代码正常终止

#include 
#include 
#include 
#include 
#include 

int main()
{
    int status;
    pid_t id = fork();

    if (id == 0) // 子进程
    {
        int n = 5;
        while (n--)
        {
            printf("我是一个子进程,我的pid:%d,我的ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        return 100;
    }
    else if (id > 0) // 父进程
    {
        int n = 10;
        while (n--)
        {
            pid_t ret = waitpid(-1, &status, WNOHANG);
            if (ret == 0)
            {
                printf("子进程还未结束\n");
            }
            else // ret > 0
            {
                if ((status & 0x7F) == 0) // 后七位结果为0,没有收到信号,正常退出。
                {
                    printf("子进程正常结束!!!子进程pid:%d,退出码:%d\n", ret, (status >> 8) & 0x7F);
                }
                else
                {
                    printf("子进程异常退出子进程pid:%d,收到信号:%d\n", ret, (status) & 0x3F);
                }
                sleep(1);
                exit(0);
            }
            sleep(1);
            printf("我是一个父进程,我的pid:%d,我的ppid:%d\n", getpid(), getppid());
        }
    }
    return 0;
}

测试结果:

进程控制——进程等待_第17张图片

进程控制——进程等待_第18张图片

 2.代码异常终止

#include 
#include 
#include 
#include 
#include 

int main()
{
    int status;
    pid_t id = fork();

    if (id == 0) // 子进程
    {
        int n = 15;
        while (n--)
        {
            printf("我是一个子进程,我的pid:%d,我的ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        return 100;
    }
    else if (id > 0) // 父进程
    {
        int n = 10;
        while (n--)
        {
            pid_t ret = waitpid(-1, &status, WNOHANG);
            if (ret == 0)
            {
                printf("子进程还未结束\n");
            }
            else // ret > 0
            {
                if ((status & 0x7F) == 0) // 后七位结果为0,没有收到信号,正常退出。
                {
                    printf("子进程正常结束!!!子进程pid:%d,退出码:%d\n", ret, (status >> 8) & 0x7F);
                }
                else
                {
                    printf("子进程异常退出子进程pid:%d,收到信号:%d\n", ret, (status) & 0x3F);
                }
                sleep(1);
                exit(0);
            }
            sleep(1);
            printf("我是一个父进程,我的pid:%d,我的ppid:%d\n", getpid(), getppid());
        }
    }
    return 0;
}

进程控制——进程等待_第19张图片

 注意:如果我们在提取退出码时,不想使用位运算,我们可以直接使用:

  1. WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  2. WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

 

你可能感兴趣的:(linux,linux)