无论是在什么系统中,当进程终止之后,系统都需要释放进程占有的资源。否则,系统资源会被耗尽。下面将详细说明Linux系统中,进程终止的过程。
进程终止方式
linux的进程终止方式有8种,其中5种是正常终止,分别是:
- 从main函数返回。
- 调用exit函数。
- 调用_exit或_Exit。
- 最后一个线程从其启动例程返回。
- 最后一个线程调用pthread_exit。
异常终止有3种,分别是:
- 调用abort函数。
- 接收到信号并终止。
- 最后一个线程对取消请求做出响应。
进程终止过程
Linux中,对于资源的释放,采取的是“谁申请谁释放”的原则。比如,进程自身申请的信号量、文件描述符等,需要进程自己释放。而进程描述符、内核栈这些资源则需要父进程来回收。
进程自身资源释放
不管进程以何种方式终止,最终都会调用do_exit()函数。do_exit()函数是进程的析构函数。借用一张网上的图,表示如下:
do_exit()函数主要完成下列工作:
- 将task_struct(内核中表示进程的数据结构)中的标志成员设置为PF_EXITING。
- 调用del_timer_sync()删除任一内核定时器。
- 如果BSD的进程记账功能是开启的,调用acct_update_integrals()输出记账信息。
- 调用exit_mm()函数,释放进程占有的mm_struct。
- 调用sem_exit()函数,释放进程的信号量。
- 调用exit_files()和exit_fs(),分别递减文件描述符和文件系统数据的引用计数。
- 把存放task_struct的exit_code成员中的任务退出码置为exit()提供的退出码。
- 调用exit_notify()向父进程发信号,给子进程重新找父进程,新的父进程为进程组中其他进程或者init进程。并把进程状态置为EXIT_ZOMBIE。
- do_exit()调用schedule()切换到新的进程。因为处于EXIT_ZOMBIE状态的进程不会再被调度,所以这是进程执行的最后一段代码。
执行完上述操作之后,进程相关联的资源都被释放掉,并处于EXIT_ZOMBIE状态。但是进程的内核栈、thread_info(指向task_struct的数据结构)和task_struct。此时进程存在的唯一目的就是向父进程提供信息,父进程释放子进程占用的剩余内存。
释放进程剩余的资源
当一个进程终止之后,内核会向其父进程发送SIGCHLD信号(何时发?)。父进程在SIGCHLD的信号处理函数中调用wait()函数,获得已终结的子进程信息后,调用release_task()函数,释放其占用的剩余资源。
如果父进程创建了子进程,但是又不想负责回收子进程占用的资源,可以使用两次fork的方法:
父进程创建一个子进程,子进程再创建孙进程执行需要执行的操作,然后子进程退出。
由于子进程退出了,那么孙进程将会被init进程托管,所以其资源的回收也将由系统来负责。