进程与子进程
一、子进程
1.fork()创建子进程
一个现有的进程可以调用 fork()函数创建一个新的进程,调用 fork()函数的进程称为父进程,由 fork()函
数创建出来的进程被称为子进程(child process)。(使用该函数需要包含头文件<unistd.h>)
2.创建的子进程为新的独立的进程,与父进程地位相等。但父子进程之间也有些相同点,子进程拷贝了父进程的数据段、堆、栈以及继承了父进程打开的文件描述符。对于程序来说,子进程和父进程共享代码段,在内存中只存在一份代码段数据。
3.子进程和父进程之间存在竞争关系,可以通过信号来指定它们的执行顺序。fork()函数调用完成之后,父进程、子进程会各自继续执行 fork()之后的指令,最终父进程会执行到 exit()结束进程,而子进程则会通过_exit()结束进程。
二、进程的诞生与终止
事实上,Linux系统下的所有进程都是由其父进程创建而来,譬如在 shell 终端通过命令的方式执行一个程序./app,那么 app进程就是由 shell 终端进程创建出来的,shell 终端就是该进程的父进程。
进程号为 1 的进程便是所有进程的父进程,通常称为 init 进程,它是 Linux 系统启动之后运行的第一个进程,它管理着系统上所有其它进程,init 进程是由内核启动,因此理论上说它没有父进程。
三、监视子进程
在很多应用程序的设计中,父进程需要知道子进程于何时被终止,并且需要知道子进程的终止状态信息,是正常终止、还是异常终止亦或者被信号终止等,意味着父进程会对子进程进行监视。
wait()函数可以等待进程的任一子进程终止,同时获取子进程的终止状态信息。
四、僵尸进程与孤儿进程
1.孤儿进程
父进程先于子进程结束,init进程会自动成为孤儿进程的父进程。
2.僵尸进程
如果子进程先于父进程结束,此时父进程还未来得及给子进程“收尸”或者没有收尸的操作,那么此时子进程就变成了一个僵尸进程。
需要注意的是,僵尸进程是无法通过信号将其杀死的,即使是“一击必杀”信号 SIGKILL 也无法将其杀死,那么这种情况下,只能杀死僵尸进程的父进程(或等待其父进程终止),这样 init 进程将会接管这些僵尸进程,从而将它们从系统中清理掉!所以,在我们的一个程序设计中,一定要监视子进程的状态变化,如果子进程终止了,要调用 wait()将其回收,避免僵尸进程。
3.SIGCHLD 信号
当子进程终止时和因收到信号而停止或恢复时,父进程会收到SIGCHLD信号。我们要捕获它、绑定信号处理函数,在信号处理函数中调用 wait()收回子进程,回收完毕之后再回到父进程自己的工作流程中。
注意:父进程一次只能捕获一个SIGCHLD信号,如果有多个子进程终止发出信号,而父进程来不及为子进程收拾或者错过了就会有僵尸进程成为“漏网之鱼”。解决办法:在 SIGCHLD 信号处理函数中循环以非阻塞方式来调用 waitpid(),直至再无其它终止的子进程需要处理为止。
4.子进程执行新程序
当子进程不再执行父进程的代码段,而是运行新的程序代码,此时会通过系统调用execve函数来实现。
通常将调用这些 exec 函数加载一个外部新程序的过程称为 exec 操作。