exit()的系统调用实现

8

我写了一个简单的C程序,只调用exit()函数,然而strace显示二进制文件实际上在调用 exit_group(),那么 exit() 是 exit_group() 的包装器吗?这两个函数是否等效?如果是,为什么编译器会选择 exit_group() 而不是 exit()?


2
{btsdaf} - David C. Rankin
1
{btsdaf} - Martin Rosenau
2
{btsdaf} - Matteo Italia
1
@MartinRosenau,你可能是对的,assembly标签让我有些困惑。(因为汇编语言确实有exitexit_group系统调用。) - David C. Rankin
1
{btsdaf} - Peter Cordes
显示剩余7条评论
1个回答

16

Linux和glibc的手册记录了所有这些内容(特别是NOTES部分中的“C库/内核差异”)。

  • _exit(2): 在glibc 2.3及更高版本中,此包装函数实际上使用Linux的SYS_exit_group系统调用退出所有线程。在glibc2.3之前,它是SYS_exit的包装器,仅退出当前线程。

  • exit_group(2): SYS_exit_group的glibc包装器,用于退出所有线程。

  • exit(3): ISO C89函数,刷新缓冲区然后退出整个进程。(它总是使用exit_group(),因为检查进程是否单线程并决定使用SYS_exit vs. SYS_exit_group没有任何好处)。正如@Matteo指出的那样, 最近的ISO C / POSIX标准都支持线程,并且其中一个或两个可能需要此行为。

    但显然,exit(3)本身不是线程安全的(在C库清理部分),因此我猜不要同时从多个线程调用它。

  • syscall / int 0x80SYS_exit:仅终止当前线程,使其他线程保持运行状态。据我所知,现代glibc没有此Linux系统调用的薄包装函数,但我认为如果这不是最后一个线程,则pthread_exit()会使用它。(否则exit(3) -> exit_group(2)。)

只有exit(),而不是_exit()exit_group(),会刷新stdout,导致新手汇编程序在写入管道(使stdout变为全缓冲而不是行缓冲),或者忘记格式字符串中的\n时出现“printf不打印任何内容”的问题。例如,为什么使用_exit(0)(通过系统调用退出)会阻止我接收任何stdout内容?如果您使用任何缓冲I/O函数、at_exit或类似的东西,通常最好直接调用libc的exit(3)函数,而不是直接调用系统调用。但当然,在调用SYS_exit_group之前可以调用fflush

(另外相关的:在x64 Linux上,syscall、int 0x80和ret退出程序有什么区别?-从main返回ret等效于调用exit(3)


当你包含头文件并写入read(fd, buf, 123)exit(1)时,编译器只看到一个普通的函数调用,而选择的不是编译器本身,而是libc。

一些C库(例如musl,但不包括glibc)可能使用内联汇编将syscall指令内联到您的二进制文件中,但头文件仍然是C库的一部分,而不是编译器。


{btsdaf} - David C. Rankin
4
@DavidC.Rankin: 我刚才不必查阅这个内容,我对它很感兴趣多年了。:P 我通过阅读Linux手册页和使用“strace”查看各种程序所执行的操作,学到了很多系统编程知识。 - Peter Cordes
@PeterCordes:这就是方法。随着时间的推移,Linux系统上线程的演变以及系统调用机制即使在找到更好的机制后仍然保留在API中,复杂性得到了体现。我说“系统”是为了强调内核、libc和编译器之间的相互作用。 - artless noise

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接