让我们对这段关于linux进程创建和终止的文章进行伪原创处理,同时保持原意不变,并保留图片的原始位置和格式:
- 进程创建语言:JavaScript运行次数:0运行复制
#include <unistd.h> pid_t fork(void);
返回值:子进程返回0,父进程返回子进程ID,错误时返回-1
当进程调用fork函数时,控制权会转移到内核中的fork代码。内核会执行以下操作:
- 为子进程分配新的内存块和内核数据结构
- 将父进程的部分数据结构内容复制到子进程
- 将子进程添加到系统进程列表中
- fork返回后,开始调度器的调度
写时拷贝
02. 进程终止首先需要明确,终止的目的是什么:
- 释放曾经占用的代码和数据空间
- 释放内核数据结构
进程退出场景:
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确(可以通过进程退出码判断这两种情况)
- 代码异常终止
上面的代码中,进程11258是父进程bash,执行echo $?命令,父进程bash可以获取最近一个子进程的退出码。我们之前提到,echo是一个内建命令,打印的是bash内部的变量数据。
父进程bash为什么需要获取子进程的退出码呢?这是为了了解子进程的退出情况(成功,失败,以及失败的原因)。
进程结束时,可以通过return语句(在函数中)或exit()函数(直接从程序中)指定一个退出码。这个退出码是一个整数,传递给父进程,用于表示子进程的终止状态。
常见的惯例:
- 0(EXIT_SUCCESS):通常表示成功。程序执行完成且没有错误。
- 非0(EXIT_FaiLURE):通常表示有错误发生。具体的非零值可以用来指示不同类型的错误。
异常终止在操作系统中,进程的异常终止通常是由于一些错误或意外情况导致程序无法正常运行到结束。以下是一些典型的异常终止情况:
- 程序错误
- 资源问题
- 内存耗尽:程序请求更多内存时,如果系统无法分配(如堆内存耗尽),可能会导致程序异常终止。
- 文件描述符耗尽:程序打开太多文件而没有关闭,达到系统限制,可能导致系统函数失败,影响程序继续运行。
- 信号
- 致命信号:
- SigsEGV(段错误信号):最常见的程序崩溃原因,通常是由于访问违法的内存地址。
- SIGABRT(中止信号):通常是由于程序内部发生严重错误或调用 abort() 函数而触发。
- SIGFPE(浮点异常信号):执行了一个无效的算术运算,比如除以零。
- SIGKILL:无条件终止程序运行的信号,无法捕获或忽略。
- SIGTERM:请求终止程序的信号,比 SIGKILL 更温和,允许程序进行清理(关闭文件、释放资源等)操作后退出。
- 非致命信号(如 SIGINT、SIGHUP 等),如果没有被程序正确处理,也可能导致程序终止。
- 致命信号:
- 操作系统干预
- 死锁检测:操作系统可能终止处于死锁状态的进程以解锁系统资源。
- 资源超额:操作系统对程序使用的资源(如 CPU 时间、内存使用量)有限制,如果程序超出这些限制,如超过了设定的 CPU 时间,操作系统可能终止这个进程。
- 运行时异常
- 未捕获的异常:在一些高级语言中(如 Java、python),如果程序中发生了异常而没有被捕获和处理,这通常会导致程序异常终止。例如,Python 中未被捕获的 ValueError 或 IndexError。
一旦出现异常,退出码就没有意义了!进程出异常,本质是因为进程收到了操作系统发给进程的信号!
段错误,操作系统提前终止进程
我们可以通过查看进程退出的信号来判断进程为什么会异常!
首先要确认是否是异常,不是异常的情况下,进程一定是运行完毕了,这时只需查看退出码即可。衡量一个进程的退出情况,我们只需要两个数字:退出码和退出信号!
如何终止进程?正常退出:
- main函数return,表示进程终止(非main函数的return,仅表示函数结束)
- 代码任意位置调用exit函数,注意:exit函数会导致进程立即退出
- _exit(),系统调用
异常退出:
- 通过Ctrl + C触发信号终止
在unix和类Unix系统中,_exit()和exit()都用于终止进程,但它们在功能和使用场景上有重要的区别。理解这些区别有助于正确地管理程序的终止过程,特别是在涉及资源清理和子进程管理时。
exit()函数由C标准库提供,用于结束程序。它执行几个重要的清理操作,然后调用底层的_exit()或exit_group()系统调用来终止进程。
特点和操作:
- 刷新缓冲区:exit()会自动刷新所有stdio的缓冲区,将缓冲区内的数据写入文件。这确保了所有挂起的输出(例如,使用printf()产生的输出)都被正确地写出。
- 执行atexit()注册的函数:如果程序中使用了atexit()注册了任何终止时执行的函数,exit()会在实际终止进程前按注册的逆序调用这些函数。这可以用于执行一些如关闭文件描述符、释放分配的内存等清理工作。
- 关闭stdio库:关闭所有使用标准I/O库打开的文件等资源。
使用场景主要是普通的应用程序,在需要确保输出数据完整性和执行特定的清理操作时使用。
_exit()函数由POSIX标准指定,直接调用系统级别的退出操作,用于立即结束程序,不执行标准I/O的清理操作和不调用atexit()或者c++的全局对象的析构函数。
特点和操作:
- 不刷新缓冲区:不处理stdio的缓冲区,如果缓冲区内有未写入的数据,这些数据将丢失。
- 不执行atexit()注册的函数:任何通过atexit()注册的函数都不会被执行。
- 立即终止:提供一种确保程序能迅速终止的方式,通常用于子进程退出或者在错误处理中需要立即终止程序时使用。
使用场景主要是在创建子进程后,子进程完成任务立即退出时,或者在程序遇到无法恢复的错误需要立即终止时使用。
- 使用exit()当你需要正常终止程序,并且需要清理资源(如关闭文件、保存状态等)。
- 使用_exit()在需要快速退出且不关心资源清理的场景下,比如在子进程中执行了某个任务后,或者在出现严重错误时安全退出。
选择合适的函数可以避免数据丢失和资源泄漏,确保程序的稳定和安全。
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做exit的参数。