进程等待

一、进程的结果

在现实生活中找别人帮忙办事,别人同意帮忙之后,会反馈给自己的结果无非就是三种:

  1. 别人把事办完了,结果是自己想要的
  2. 别人把事办完了,由于办事的方法错误,导致结果并不是自己想要的
  3. 别人办事时遇到阻碍了,没办法继续完成这件事情

同样的,进程创建出来也是用来帮忙做事的,进程运行完成后,进程的结果有三种情况:

  • 进程运行正常,结果正确
  • 进程运行正常,结果错误
  • 进程运行异常

对于进程运行正常而言,我们可以通过进程的退出码,来判断进程的运行结果是否正常

进程可以通过 main 函数中 return int 或者 任意代码处调用的 exit(int) 函数 正常退出,其中 return 返回的值和 exit 函数的参数,即为进程的退出码

echo $? 可以查看最近一次运行的程序的退出码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    // 计算
    printf("begin ...\n");
    int sum = 0;
    for (int i = 1; i <= 100; ++i) sum += i;
    sleep(1);
    printf("end, result: %d\n", sum);

    exit(11);  // 或者 return 11 表示运行正常,结果错误
    // return 0; // 或者 exit(0) 表示运行正常,结果正确
}

进程的退出码为 0 表示结果正确
在这里插入图片描述

进程的退出码非 0 表示结果错误
在这里插入图片描述

打印错误码对应的错误信息

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    for (int i = 0; i < 255; ++i)
        printf("errno %d : %s\n", i, strerror(i));
    
    return 0;
}

这里只截取了一部分,感兴趣的读者可以自行运行查看
在这里插入图片描述

系统调用 _exit(int) 也可以结束进程,与库函数 exit(int) 的关系是:库函数 exit(int) 会先进行关闭文件流,冲刷缓冲区等操作,然后再调用 _exit(int) 结束进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

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

    // 系统调用 _exit(int) 不会关闭文件流,冲刷缓冲区等,直接结束进程
    _exit(0);

    // 库函数 exit(int) 会关闭文件流,冲刷缓冲区等,在结束进程
    // exit(0);
}

exit(int) 函数会冲刷缓冲区,所以打印了 hello world
在这里插入图片描述

_exit(int) 函数直接结束进程,不会打印 hello world
在这里插入图片描述

对于进程运行异常,是由于进程收到了操作系统的信号,参考

二、进程等待

当子进程先于父进程退出时,子进程会处于僵尸状态,为了回收子进程的资源,防止内存泄漏,以及可以选择性的接收子进程的结果,父进程需要等待子进程

系统调用 wait / waitpid,头文件 sys/types.h 和 sys/wait.h

  • pid_t wait(int* status),阻塞式等待任意一个子进程(如果没有子进程退出,父进程会阻塞在 wait 函数)

返回值:等待成功返回子进程的 pid,出错返回 -1,并且 errno 被设置为相应的出错信息

参数:status 为输出型参数,用于存储子进程的退出码,如果不关心子进程的退出码,可以设置为 NULL

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    // 创建子进程
    pid_t id = fork();
    assert(id != -1);
    if (id == 0)
    {
        // 子进程
        int cnt = 5;
        while (cnt)
        {
            printf("我是子进程,我的 pid 是: %d,我的 ppid 是: %d\n我还剩余 %d s\n", getpid(), getppid(), cnt--);
            sleep(1);
        }

        exit(0);
    }
    
	// slepp(10); 模拟父进程的任务

    // 等待子进程
    printf("我是父进程,我的 pid 是: %d, 开始等待子进程 %d ...\n", getpid(), id);
    pid_t cid = wait(NULL);
    assert(cid != -1);
    printf("我是父进程,我的 pid 是: %d, 等待子进程 %d 成功\n", getpid(), cid);

    return 0; 
}

父进程阻塞在 wait 函数处
在这里插入图片描述

加上 sleep(10) 之后,子进程会先进入僵尸状态,父进程调用 wait 回收资源后,子进程的僵尸状态随之消失
在这里插入图片描述

  • pid_t waitpid(pid_t pid, int* status, int options)

返回值:等待成功返回子进程的 pid,出错返回 -1

参数:

  • pid 如果设置为 -1,表示等待任意一个子进程,pid 如果大于 0,则表示等待指定 pid 的子进程
  • status 为输出型参数,用于存储子进程的退出码,如果不关心子进程的退出码,可以设置为 NULL
  • options 如果设置为 0,表示阻塞式等待,如果设置为 WNOHANG,则表示非阻塞等待(调用时子进程还未退出,waitpid 函数会返回 0)

进程的结果有三种情况,整数 status 是采用如下位图的方式来存储进程的结果

  • 正常终止:0 ~ 7 位为 0,8 ~ 15 位表示退出码

  • 异常终止:0 ~ 6 位表示收到的信号,第 7 位表示是否为核心转储,8 ~ 15 位没有使用
    在这里插入图片描述

  • 宏 WIFEXITED(status),用于判断 status 是否收到信号,即子进程是否运行正常

  • 宏 WEXITSTATUS(status),当子进程运行正常时,用于获取子进程的退出码

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    pid_t id = fork();
    assert(id != -1);
    if (id == 0)
    {
        // 子进程
        int cnt = 5;
        while (cnt)
        {
            printf("我是子进程,我的 pid 是: %d,我的 ppid 是: %d,我还剩余 %ds\n", getpid(), getppid(), cnt--);
            sleep(1);
        }

        exit(0);
    }
    
    // 父进程
    int status = 0;
    while (1)
    {
        pid_t ret_id = waitpid(id, &status, WNOHANG);
        assert(ret_id != -1);
        if (ret_id > 0)
        {
            // 等待成功
            printf("我是父进程,我的 pid 是: %d,等待子进程 %d 成功\n", getpid(), ret_id);

            // 判断是否是信号导致
            if (WIFEXITED(status))
                printf("子进程 %d 运行正常,退出码: %d\n", ret_id, WEXITSTATUS(status));
            else 
                printf("子进程 %d 运行异常", ret_id);

            break;
        }

        // 父进程的任务
        printf("我是父进程,我的 pid 是: %d, 正在执行任务中 ...\n", getpid());
        sleep(1);
    }

    return 0;
}

父进程非阻塞等待,子进程进入僵尸状态,父进程调用 waitpid 回收资源后,子进程的僵尸状态随之消失
在这里插入图片描述

wait / waitpid 是如何得到子进程的结果呢?其实就是通过子进程的 task_struct 中的属性得到的

  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
Linux中的进程等待(Process Waiting)是指一个进程在执行时需要等待某些条件满足后才能继续执行的情况。 在Linux中,进程等待通常有以下几种情况: 1. I/O等待:当一个进程需要进行输入输出操作时,比如读写文件或者网络通信,由于这些操作是相对慢速的,进程需要等待数据的读取或写入完成才能继续执行。 2. 锁等待:多个进程访问临界资源时,为了避免竞态条件,需要使用锁来实现同步。当一个进程试图获取已经被其他进程占用的锁时,它会被阻塞,并等待锁被释放。 3. 睡眠等待:当一个进程调用了sleep()或wait()等系统调用后,它会主动释放CPU资源,并进入睡眠状态,等待指定的时间或者某个事件发生后才会被唤醒。 4. 信号等待:当一个进程正在等待某个信号的到来时,它会进入阻塞状态,直到该信号被发送给该进程进程才会被唤醒并继续执行。 针对进程等待的情况,Linux提供了一些机制来管理这些等待进程,比如使用信号量、条件变量、管道等方式来实现进程间的同步与通信。此外,Linux还提供了一些工具和命令来查看进程等待的状态,比如top命令可以查看每个进程等待时间,ps命令可以查看进程的状态等。 总之,Linux中的进程等待是一个重要的概念,合理管理进程等待可以提高系统的性能和资源利用率。进程等待是多任务操作系统中的常见现象,对于了解和掌握Linux进程管理至关重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值