进程退出return、exit()、_exit()、进程等待(接儿子放学咯)。

创建

Linux 系统中,用户创建子进程的唯一方法就是使用 fork 系统调用。

大致流程,图片来源于网络

退出

一个程序(进程)退出了,无非有一下几种情况:

  • 代码运行完毕,结果正确
  • 代码执行完毕,结果不正确
  • 代码异常终止

之前看GitHub上别人代码里用 exit(1) 这样的方式写,虽然知道他就是结束程序,但并不知道具体的意思。查下资料吧。

常见退出方法

  • 正常终止
    • main 函数 return
    • 调用 exit
    • 调用 _exit 系统调用
  • 异常终止
    • ctrl + c、ctrl + d之类的
    • 程序异常(比如除数为0等)

_exit 系统调用

1
2
3
4
5
6
7
# man _exit
NAME
_exit -- terminate the calling process

SYNOPSIS
#include <unistd.h>
void _exit(int status);

status 定义了进程的终止状态,父进程通过 wait 来获取该值,在 bash 中使用 $? 来看上一个进程(程序)的退出码。虽然 status 是 int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值
是255。

exit 库函数

1
2
3
SYNOPSIS
#include <unistd.h>
void exit(int status);

这是一个 C 的库函数,本质上调用了 _exit,有一些区别:

  1. 关闭了文件流并刷新了缓冲区,通过简单的代码即可实验验证

  2. exit 比 _exit 多调用了一个结束处理函数( atexit/on_exit 函数,参数是一个函数指针,这个好比vue中的生命周期钩子函数)

    1
    2
    3
    4
    5
    6
    7
    # man atexit
    NAME
    atexit -- register a function to be called on exit
    SYNOPSIS
    #include <stdlib.h>
    int atexit(void (*function)(void));
    int atexit_b(void (^function)(void));

return

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

等待

进程状态,子进程退出,父进程不管不顾就可能造成”僵尸🧟‍♀️”进程,造成内存泄漏。交给子进程的任务也不知道他完成的怎么样了,父进程为了获取子进程完成任务的情况以及回收资源,可以通过进程等待的方式进行。

wait

1
2
3
4
5
6
7
8
9
# man 2 wait
SYNOPSIS
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
返回:
成功,返回被等待进程pid。失败,返回-1。
参数:
status 输出型参数,获取子进程退出的状态码,不关心可以传为 NULL

wait 是一个阻塞式函数。

wait 的调用次数必须和子进程的个数一致,少了导致僵尸进程,多了出错。

如果有多个子进程,任何一个子进程的结束都会触发 wait。

waitpid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SYNOPSIS
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
返回:
当正常返回的时候,返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误;
参数:
pid:
pid=-1,等待任一个子进程。与wait等效。
pid>0.等待其进程ID与pid相等的子进程。
status:
# 具体看 man 2 waitpid
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
# 目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的PID。

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。

如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞

如果不存在该子进程,则立即出错返回。

注意 status

wait 和 waitpid 的参数 status 不关心可以填 NULL,但如果关心,还得注意一下。

这个status是由操作系统填充的,他并不真的、简单的当做一个 int 类型。status 的低 16 位是真正我们关心的。

status

代码实践

阻塞式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sched.h> // pid_t

int main() {
pid_t ret = fork();
if (ret < 0) {
perror("fork");
} else if (ret == 0) {
sleep(20);
exit(1);
} else {
int status;
int t = wait(&status);
if (t > 0 && (status & 0x7f) == 0) { // 正常退出
printf("child exit code : %d\n", (status>>8)&0xff);
} else if (t > 0) {
printf("sig code : %d\n", status & 0x7f);
}
}

return 0;
}

测试:

阻塞式测试

非阻塞轮询


参考:
https://blog.csdn.net/zhangxiao93/article/details/72859312
https://www.cnblogs.com/LUO77/p/5804436.html