Linux中的exit()与_exit()

_exit与exit作为进程退出的两种方式,在Linux系统中扮演重要角色。exit会在退出前进行缓冲区刷新及文件关闭等操作,而_exit则直接终止进程。本文详细解析两者之间的差异及其应用场景。
作为系统调用而言,_exit和exit是一对孪生兄弟,它们究竟相似到什么程度,我们可以从Linux的源码中找到答案:

#define __NR__exit __NR_exit /* 摘自文件include/asm-i386/unistd.h第334行 */


"__NR_"是在Linux的源码中为每个系统调用加上的前缀,请注意第一个exit前有2条下划线,第二个exit前只有1条下划线。

这时随便一个懂得C语言并且头脑清醒的人都会说,_exit和exit没有任何区别,但我们还要讲一下这两者之间的区别,这种区别主要体现在它们在函数库中的定义。_exit在Linux函数库中的原型是:

#i nclude<unistd.h>
 void _exit(int status);


和exit比较一下,exit()函数定义在stdlib.h中,而_exit()定义在unistd.h中,从名字上看,stdlib.h似乎比 unistd.h高级一点,那么,它们之间到底有什么区别呢?

_exit()函数的作用最为简单:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;exit() 函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序,也是因为这个原因,有些人认为exit已经不能算是纯粹的系统调用。

exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是"清理I/O缓冲"。

在Linux的标准函数库中,有一套称作"高级I/O"的函数,我们熟知的printf()、fopen()、fread()、fwrite()都在此列,它们也被称作"缓冲I/O(buffered I/O)",其特征是对应每一个打开的文件,在内存中都有一片缓冲区,每次读文件时,会多读出若干条记录,这样下次读文件时就可以直接从内存的缓冲区中读取,每次写文件的时候,也仅仅是写入内存中的缓冲区,等满足了一定的条件(达到一定数量,或遇到特定字符,如换行符和文件结束符EOF),再将缓冲区中的内容一次性写入文件,这样就大大增加了文件读写的速度,但也为我们编程带来了一点点麻烦。如果有一些数据,我们认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时我们用_exit()函数直接将进程关闭,缓冲区中的数据就会丢失,反之,如果想保证数据的完整性,就一定要使用exit()函数。

请看以下例程:

/* exit2.c */
#i nclude<stdlib.h>
main()
{
 printf("output begin");
 printf("content in buffer");
 exit(0);
}


编译并运行:

$gcc exit2.c -o exit2
$./exit2
output begin
content in buffer

/* _exit1.c */
#i nclude<unistd.h>
main()
{
 printf("output begin");
 printf("content in buffer");
 _exit(0);
}


编译并运行:

$gcc _exit1.c -o _exit1
$./_exit1
output begin


在Linux中,标准输入和标准输出都是作为文件处理的,虽然是一类特殊的文件,但从程序员的角度来看,它们和硬盘上存储数据的普通文件并没有任何区别。与所有其他文件一样,它们在打开后也有自己的缓冲区。

此外,另外一种解释:
简单的说,exit函数将终止调用进程。在退出程序之前,所有文件关闭,缓冲输出内容将刷新定义,并调用所有已刷新的“出口函数”(由atexit定义)。
_exit:该函数是由Posix定义的,不会运行exit handler和signal handler,在UNIX系统中不会flush标准I/O流。
简单的说,_exit终止调用进程,但不关闭文件,不清除输出缓存,也不调用出口函数。
共同:
不管进程是如何终止的,内核都会关闭进程打开的所有file descriptors,释放进程使用的memory!
note:
在由‘fork()’创建的子进程分支里,正常情况下使用‘exit()’是不正确的,这是 因为使用它会导致标准输入输出的缓冲区被清空两次,而且临时文件被出乎意料的删除(译者注:临时文件由tmpfile函数创建在系统临时目录下,文件名由系统随机生成)。
在C++程序中情况会更糟,因为静态目标(static objects)的析构函数(destructors)可以被错误地执行。
还有一些特殊情况,比如守护程序,它们的父进程需要调用‘_exit()’而不是子进程;适用于绝大多数情况的基本规则是,‘exit()’在每一次进入‘main’函数后只调用一次。

在由‘vfork()’创建的子进程分支里,‘exit()’的使用将更加危险,因为它将影响 父进程的状态

注:exit()就是退出,传入的参数是程序退出时的状态码,0表示正常退出,其他表示非正常退出,一般都用-1,标准C里有EXIT_SUCCESS和EXIT_FAILURE两个宏,用exit(EXIT_SUCCESS);可读性比较好一点。

<think> 我们讨论的是“__exit”在Linux环境下的含义、使用方法和作用。结合用户提供的引用和之前对话历史,这里需要注意两点: 1. 用户之前的问题是关于Python中的`__exit__`方法,但现在转向Linux环境下的"__exit"(注意这里是双下划线)。 2. 提供的引用中并没有直接提到“__exit”,但提到了`exit()`, `_exit()`函数以及环境变量等。 在Linux环境下,双下划线开头的标识符通常用于内核或系统内部。经过分析,没有直接名为`__exit`的公开系统调用或函数。然而,在Linux内核源码中,`__exit`是一个宏,用于标记退出处理相关的代码(如模块卸载函数)。此外,在系统编程中,用户空间常用的退出函数是`exit()`和`_exit()`。 因此,我们推测用户可能混淆了: - Python的`__exit__`方法(用户/编程语言层面) - Linux系统调用`_exit()`(系统层面) - 内核模块开发中的`__exit`宏(内核层面) 下面分三部分回答: ### 1. Linux内核模块中的`__exit`宏 在Linux内核模块开发中,`__exit`是一个宏,用于修饰模块的清理函数(在模块卸载时执行)。通常`__init`宏配对使用: ```c #include <linux/init.h> #include <linux/module.h> static int __init my_module_init(void) { printk(KERN_INFO "Module loaded\n"); return 0; } static void __exit my_module_exit(void) { printk(KERN_INFO "Module unloaded\n"); } module_init(my_module_init); module_exit(my_module_exit); ``` - **作用**:`__exit`宏标记的函数会在模块卸载时自动调用,用于释放资源(如内存、设备号等)。 - **原理**:该宏告诉内核此函数仅用于卸载阶段。对于可动态加载的模块,它会被编译到特殊的ELF段中;对于静态编译进内核的模块,`__exit`函数会被丢弃(因为静态模块不需要卸载)[^1][^2]。 ### 2. 用户空间的`exit()``_exit()`函数 在Linux用户空间编程中,进程退出有两个主要函数: - **`exit(int status)`**:标准C库函数,执行以下操作: 1. 调用通过`atexit()`或`on_exit()`注册的清理函数(按注册顺序的逆序执行)。 2. 刷新所有标准I/O缓冲区(将缓冲数据写入文件)。 3. 关闭所有打开的文件描述符。 4. 最后调用`_exit()`系统调用终止进程。 示例: ```c #include <stdlib.h> void cleanup1() { printf("Cleanup 1\n"); } void cleanup2() { printf("Cleanup 2\n"); } int main() { atexit(cleanup1); atexit(cleanup2); printf("Main function\n"); exit(0); // 输出:Main function → Cleanup 2 → Cleanup 1 } ``` - **`_exit(int status)`**:系统调用(定义在`<unistd.h>`),直接终止进程: - 立即停止进程运行。 - 关闭所有文件描述符。 - **不**刷新I/O缓冲区(可能导致数据丢失)[^4]。 - **不**执行`atexit()`注册的函数。 示例: ```c #include <unistd.h> int main() { write(STDOUT_FILENO, "Hello", 5); // 系统调用,无缓冲,直接输出 printf("World\n"); // 标准I/O,有缓冲 _exit(0); // 不刷新缓冲区,"World\n"不会被打印 } ``` ### 3. 核心区别总结 | 特性 | Python `__exit__` | Linux `exit()` | Linux `_exit()` | 内核`__exit`宏 | |---------------------|---------------------------|----------------------------|--------------------------|----------------------| | **作用域** | Python上下文管理器 | 用户空间进程终止 | 用户空间进程终止 | 内核模块卸载函数 | | **清理行为** | 释放资源(如文件、锁) | 刷新缓冲区+执行清理函数 | 立即终止,无额外清理 | 释放内核资源 | | **是否处理异常** | 是 | 否 | 否 | 否 | | **使用场景** | `with`语句块退出时 | 程序正常退出 | 子进程退出或紧急终止 | 内核模块卸载 | ### 使用方法建议 1. **用户空间程序**: - 优先使用`exit()`确保资源安全释放。 - 仅在子进程(`fork()`后)或需要立即终止时用`_exit()`,避免重复清理[^4]。 2. **内核模块开发**: - 用`__exit`宏声明卸载函数,必须`module_exit()`配对[^1][^2]。 3. **Python程序**: - `__exit__`仅用于自定义上下文管理器,Linux系统调用无关。 §§相关问题§§ 1. `exit()`和`_exit()`在子进程退出时有何区别? 2. Linux内核模块初始化函数为什么要用`__init`宏? 3. Python的`__exit__`方法能否调用`_exit()`系统调用? 4. 如何查看Linux中`_exit()`系统调用的源码实现? [^1]: 引用[1]:exit()函数_exit()函数都是用来终止进程的,exit()是一个标准 C 库函数,而_exit()和_Exit()是系统调用。 执行 exit()会执行一些清理工作,最后调用_exit()函数。 [^2]: 引用[2]:详解Linux下的_exit(),exit()和atexit()函数。一个进程可以登记若32个函数,这些函数由exit自动调用,这些函数被称为终止处理函数,atexit函数可以登记这些函数。exit调用终止处理函数的顺序和atexit登记的顺序相反,如果一个函数被多次登记,也会被多次调用。 [^4]: 引用[4]:linux进程退出返回值判断,linuxexit系统调用return、_exit分析。在linux中,标准输入输出(I/O)函数都是作为文件来处理。对应于打开的每个文件,在内存中都有对应的缓存,每次读取文件时,会多读一些记录到缓存中,这样在下次读文件时,就在缓存中读取;同样,在写文件时也是写在文件对应的缓存中,并不是直接写入硬盘的文件中,等满足了一定条件(如达到一定数量,遇到换行符\n或文件结束标志EOF)才将数据真正的写入文件。这样做的好处就是加快了文件读写的速度!但这样也带来了一些问题:比如有一些数据,我们认为已经写入了文件,但实际上没有满足一定条件而任然驻留在内存的缓存中,这样,如果我们直接用_exit()函数直接终止进程,将导致数据丢失!! </think> ### Linux环境下`__exit`的含义、使用方法及作用 在Linux环境中,`__exit`是一个特殊标识符,主要在内核模块开发中使用。它用户空间的`exit()`和`_exit()`函数有本质区别(这些函数用于进程终止)[^1][^2]。下面详细解释其在Linux系统中的角色和应用: #### 1. **核心含义** `__exit`是Linux内核源码中的**宏定义**,用于标记模块卸载函数(清理函数)。它位于`<linux/init.h>`头文件中,主要作用是: - 标记内核模块的**退出处理函数** - 声明此函数仅在模块卸载时执行(不可被静态编译进内核) - 在模块卸载时自动释放资源(如内存、设备号等) ```c #include <linux/init.h> #include <linux/module.h> static void __exit mymodule_exit(void) { // __exit宏修饰退出函数 printk(KERN_INFO "Module unloaded\n"); } module_exit(mymodule_exit); // 注册退出函数 ``` #### 2. **工作原理** 当内核模块被卸载时(通过`rmmod`命令),系统自动调用`__exit`标记的函数: $$ \text{rmmod command} \rightarrow \text{触发模块卸载} \rightarrow \text{执行} \verb|__exit| \text{函数} $$ - **内存管理**:释放`kmalloc`分配的内存 - **设备注销**:取消注册字符设备/块设备 - **中断处理**:释放中断请求线 - **其他资源**:关闭文件描述符、删除`/proc`文件等[^4] #### 3. **`exit()`/`_exit()`的区别** | 特性 | `__exit` (内核宏) | `exit()` (用户空间函数) | `_exit()` (系统调用) | |---------------|-----------------------------------|--------------------------------|------------------------------| | **作用域** | 内核模块开发 | 用户空间进程终止 | 用户空间进程终止 | | **清理操作** | 模块资源释放 | 刷新缓冲区+调用`atexit()`函数 | 直接终止进程,无缓冲刷新 | | **调用方式** | 自动由模块卸载触发 | 程序显式调用 | 系统调用 | | **数据安全** | 确保内核资源回收 | 避免数据丢失(刷新缓冲区)[^4] | 可能导致未写入数据丢失 | #### 4. **典型使用场景** 1. **字符设备驱动卸载**: ```c static void __exit mychrdev_exit(void) { unregister_chrdev_region(dev_num, 1); // 注销设备号 cdev_del(&my_cdev); // 删除字符设备 printk(KERN_INFO "Character device unregistered\n"); } ``` 2. **中断处理清理**: ```c static void __exit myirq_exit(void) { free_irq(irq_number, NULL); // 释放中断线 } ``` 3. **内存资源回收**: ```c static void __exit mymem_exit(void) { kfree(my_buffer); // 释放内核内存 } ``` #### 5. **关键注意事项** 1. **不可静态编译**: `__exit`函数不会被编译进静态内核(通过宏实现条件编译) ```c #define __exit __section(".exit.text") ``` 2. **配对使用**: 必须`__init`宏配对(初始化函数): ```c static int __init mymodule_init(void) { ... } // 初始化函数 static void __exit mymodule_exit(void) { ... } // 清理函数 ``` 3. **无返回值**: `__exit`函数必须是`void`类型,不返回任何值。 4. **避免阻塞操作**: 退出函数中不能包含可能导致阻塞的代码(如睡眠操作)[^2]。 > **示例流程**: > ```mermaid > graph LR > A[insmod加载模块] --> B[执行__init函数] > B --> C[模块运行] > C --> D[rmmod卸载模块] > D --> E[执行__exit函数] > E --> F[资源释放完成] > ``` ### 总结 `__exit`是Linux内核开发的基石级宏,它确保模块卸载时资源安全释放,防止内核资源泄漏。用户空间的`exit()`函数不同[^1][^4],它专为内核模块生命周期管理设计,体现了Linux内核“资源必须显式释放”的设计哲学。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值