Linux 谁来清理,进程还是内核?

举报
Tiamo_T 发表于 2021/11/25 13:48:01 2021/11/25
【摘要】 很长一段时间以来,我认为清理是内核所做的工作。然而,这并不完全正确。进程本身也进行了部分清理。但让我们深入了解细节..终止程序的方法有一些方法可以从进程本身终止进程(有时也称为“正常终止”):exit()_exit()return(不能使用break,因为它必须在 switch 或 loop 内。)还有其他方法可以通过发送信号从进程外部终止进程。可以通过键入从终端发送信号kill [proc...

很长一段时间以来,我认为清理是内核所做的工作。然而,这并不完全正确。进程本身也进行了部分清理。但让我们深入了解细节..

终止程序的方法

有一些方法可以从进程本身终止进程(有时也称为“正常终止”):

  • 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() 。进程本身有一个开销(它在我们编译它时得到),它告诉它要清理什么以及如何清理。清理过程被认为是这些事情:

  1. 如果使用 atexit() 注册,则做一些最后的工作
  2. 刷新所有未写入的缓冲数据
  3. 关闭打开的流
  4. 删除由函数 tmpfile() 创建的所有文件
  5. 将退出状态和控制权返回给内核

就我个人而言,我不认为第 1 点是更广泛意义上的清理,但它为程序员提供了在每个正常终止时添加一些默认行为的机会,在许多情况下,这将是整理东西。

现在这一切都发生在调用 exit() 时。有一个退出函数可以绕过所有前 4 步,直接进入第 5 步。该函数是_exit()。所以调用 _exit() 而不是 exit() 实际上是将控制权直接交给内核。

在大多数情况下 exit() 是要走的路。然而,有时您不希望相同的东西“清洗两次”。一个例子是使用fork()。使用 fork() 创建子进程。子进程继承了父进程的很多东西,其中包括父进程的缓冲区。如果从子进程调用 exit(),则将刷新继承的缓冲区。稍后当父级也退出时,它也会刷新其缓冲区。在这种情况下,我们将获得双倍输出。

在子进程中使用 _exit(),我们绕过了子进程的刷新,因此我们不会得到不必要的副作用(如双重输出)。

从内核清理

无论使用exit()还是_exit(),最终内核都是最大的收割者。我们不会太深入excactly内核做什么,但一个少数在清理例程点是:

  • 销毁为进程创建的内核结构
  • 为进程分配的内存被释放
  • 递减打开的文件
  • 向父进程发送信号

此时进程已死,即未加载到内存中。只有在父进程可能感兴趣的情况下,内核中仍然存在很少的结构。这就是我们所说的僵尸进程

为了销毁这些最后的结构,父进程必须为子进程wait()。一旦发生这种情况,僵尸进程就会消失,所有与死进程相关的资源都将被释放。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。