终止进程

终止进程

POSIX和C89都定义了一个标准函数,可以终止当前进程:

1
2
#include <stdlib.h>
void exit (int status);

对exit()的调用通常会执行一些基本的关闭步骤,然后通知内核终止这个进程。这个函数无法返回错误值——实际上他也从不返回。因此在exit()之后执行任何指令都没有意义

参数status用于标识进程的退出状态。EXIT_SUCCESS和EXIT_FAILURE这两个宏分别表示成功和失败,而且时可移植的。在Linux中,0通常表示成功,非0值表示失败

在终止进程之前,C库会按顺序执行以下关闭进程的步骤:

  1. 调用任何由atexit()或on_exit()注册的函数,和在系统中注册时顺序相反
  2. 清空所有已打开的标准I/O流
  3. 删除由tmpfile()函数创建的所有临时文件

这些步骤完成了用户空间所需要做的所有工作,最后exit()会调用系统调用_exit(),内核可以处理终止进程的剩余工作

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

当进程退出时,内核会清理进程所创建的、不再使用的所有资源。这包括但不局限于:分配村村、打开文件和System V的信号量。清理完成后,内核会摧毁进程,并告知父进程其子进程已经终止

应用可以直接调_exit(),但这通常不合适,绝大多数应用在完全退出前需要做一些清理工作

终止进程的其他方式

终止进程的典型方式不是通过显示系统调用,而是采用“直接跳到结束(falling off the end)”的方式。在C和C++中,当main()函数返回时会发生这种情况。然而,这种直接跳到结束的方式还是会执行系统调用:编译器会在最后关闭代码后插入隐式exit()调用。在main()函数返回时显式给出返回状态,或这调用exit()函数,这时一个良好的编程习惯

如果进程接收到一个信号,并且这个信号对应的处理函数是终止进程,进程也会终止,这样的信号包括SIGTERM和SIGKILL

最后一种进程终止方式是被内核强制终止。内核可以杀死执行非法指令、引起段错误、耗尽内存、消耗资源过多的任何进程

atexit()

系统调用atexit()是由POSIX 1003.1-2001所定义,Linux也实现了该函数。它是用来注册一些进程结束时需要调用的函数:

1
2
#include <stdlib.h>
int atexit (void (*function)(void));

atexit()调用成功时,会注册指定的函数作为终止函数,在程序正常结束时运行,如果进程调用了exec函数,会清空所注册的函数列表。如果进程是通过信号结束,就不会调用这些注册函数

指定函数必须是无参且没有返回值的函数

函数调用的顺序和函数注册的顺序相反。也就是说,这些函数是存储在栈中,以后进先出的方式调用。注册的函数不能调用exit(),否则会导致递归调用死循环。如果要提前结束进程,应该调用_exit()。一般不推荐这种行为,因为它会是的一些重要的关闭函数不会被调用到

POSIX标准要求atexit()至少支持注册ATEXIT_MAX个注册函数,而且这个值至少是32。具体的最大值可以通过sysconf()得到,参数是_SC_ATEXIT_MAX

成功时,atexit()返回0。错误时,返回-1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>

void out (void)
{
printf ("atexit() successed!\n");
}

int main (void)
{
if (atexit (out))
{
fprintf(stderr, "atexit() failed!\n");
}

return 0;
}

on_exit()

SunOS 4 自己定义了一个和atexit()等价的函数:on_exit()。Linux的glibc也支持该函数:

1
2
#include <stdlib.h>
int on_exit(void (*function)(int, void *), void *arg);

该函数的工作方式和atexit()函数一样,只是注册的函数形式不同:

1
void my_function (int status, void *arg);

参数status是传给exit()的值或者从main()函数返回的值。arg是传给on_exit()的第二个参数。需要注意的是,当调用该函数时,要保证arg所指向的内存地址必须是合法的

最新版的Solaris不再支持on_exit()函数了。因此,应该使用和标准兼容的atexit()

SIGCHILD

当一个进程终止时,内核会向其父进程发送SIGCHILD信号。默认情况下,父进程会忽略此信号量,也不会采取任何操作。但是进程可以通过signal()或sigaction()系统调用来处理这个信号

SIGCHILD信号可能会在任意时刻产生,并在任意时刻被传递给父进程,因为对于父进程而言,子进程的终止是异步的。通常情况下,父进程都希望能更多地了解到子进程的终止,或是显示等待子进程终止。这可以通过系统调用来实现


终止进程
https://carl-5535.github.io/2021/04/28/Linux系统编程/终止进程/
作者
Carl Chen
发布于
2021年4月28日
许可协议