Linux 谁来清理,进程还是内核?
很长一段时间以来,我认为清理是内核所做的工作。然而,这并不完全正确。进程本身也进行了部分清理。但让我们深入了解细节..
终止程序的方法
有一些方法可以从进程本身终止进程(有时也称为“正常终止”):
- exit()
- _exit()
- return
(不能使用break,因为它必须在 switch 或 loop 内。)
还有其他方法可以通过发送信号从进程外部终止进程。可以通过键入从终端发送信号
kill [processID]
或者在运行程序后按CTRL+C。这将向进程发送一个 SIGINT 信号。还有一些方法可以从进程中发送信号,例如使用函数abort()。abort() 将发送一个 SIGABRT 并因此终止进程(如果使用了 SIGABRT 的默认处理程序)。然而,由于用户可以明确地(从终端)向进程外发送相同的信号,我们可以很容易地将这种终止进程的方式与其他“异常”的终止方式放在同一个篮子中。
在退出时做一些事情( atexit )
这与清理没有绝对的联系,但我提到它是为了深入了解进程终止时会发生什么。
在进程的任何正常终止时,我们都可以做一些特定的工作。也许我希望每次进程正常终止时在屏幕上打印一些东西:
void onexit(void){
puts("Process terminated normally.");
}
int main() {
atexit(onexit);
/* do stuff here */
return 0;
}
现在每次 main 返回时,我们的 onexit() 函数都会被调用。注意即使是“return 1;” 会起作用,因为它仍然被认为是进程的正常终止。请记住,“正常终止”与返回的值无关,而是与导致进程终止的原因有关(它是外部信号还是只是调用了退出函数?)。
使用 atexit() 的一个更复杂的例子是在退出程序后释放一些内存:
#include
#include
void *a; //we need global scope
void* allocSomeMemory(void* a) {
return malloc(10);
}
void freeSomeMemory(void) {
free(a);
}
int main() {
a=allocSomeMemory(a);
atexit(freeSomeMemory);
/* do stuff with variable a */
return 0;
}
这将为全局变量 a 分配 10 个字节。然后我们添加一个处理程序(一个处理某些事情的函数)以在我们退出进程时释放分配的内存。
现在这将起作用,但是,如果这是一件实际的事情,那就是主观的事情。内核无论如何都会为进程释放所有资源,所以我们只是通过在退出时显式释放内存来增加更多的延迟到终止。然而,在某些情况下,这可能是调试的好习惯。
什么是进程清理?
清理只是将内核资源恢复到运行程序之前的状态。这意味着释放内存、刷新缓冲区、关闭文件、从内核中的进程表中删除进程 ID、减少打开文件的计数器、删除内核计时器、向进程的父进程发送信号等等。
在清理方面有两个主要参与者:
- 内核
- 过程
我们将从进程清理开始,它在某种程度上更难以描述。
从过程中清理
进程清理可以在正常和异常终止情况下发生。在正常终止时,清理发生在exit() 或从 main 返回发生。事实上,从 main 返回将自动调用 exit() 。进程本身有一个开销(它在我们编译它时得到),它告诉它要清理什么以及如何清理。清理过程被认为是这些事情:
- 如果使用 atexit() 注册,则做一些最后的工作
- 刷新所有未写入的缓冲数据
- 关闭打开的流
- 删除由函数 tmpfile() 创建的所有文件
- 将退出状态和控制权返回给内核
就我个人而言,我不认为第 1 点是更广泛意义上的清理,但它为程序员提供了在每个正常终止时添加一些默认行为的机会,在许多情况下,这将是整理东西。
现在这一切都发生在调用 exit() 时。有一个退出函数可以绕过所有前 4 步,直接进入第 5 步。该函数是_exit()。所以调用 _exit() 而不是 exit() 实际上是将控制权直接交给内核。
在大多数情况下 exit() 是要走的路。然而,有时您不希望相同的东西“清洗两次”。一个例子是使用fork()。使用 fork() 创建子进程。子进程继承了父进程的很多东西,其中包括父进程的缓冲区。如果从子进程调用 exit(),则将刷新继承的缓冲区。稍后当父级也退出时,它也会刷新其缓冲区。在这种情况下,我们将获得双倍输出。
在子进程中使用 _exit(),我们绕过了子进程的刷新,因此我们不会得到不必要的副作用(如双重输出)。
从内核清理
无论使用exit()还是_exit(),最终内核都是最大的收割者。我们不会太深入excactly内核做什么,但一个少数在清理例程点是:
- 销毁为进程创建的内核结构
- 为进程分配的内存被释放
- 递减打开的文件
- 向父进程发送信号
此时进程已死,即未加载到内存中。只有在父进程可能感兴趣的情况下,内核中仍然存在很少的结构。这就是我们所说的僵尸进程。
为了销毁这些最后的结构,父进程必须为子进程wait()。一旦发生这种情况,僵尸进程就会消失,所有与死进程相关的资源都将被释放。
- 点赞
- 收藏
- 关注作者
评论(0)