孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
僵尸进程没有被父进程处理的后果
unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
wait 和 waitpid 区别
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *staus);
参数status是用来保存子进程退出时的一些状态信息。如果对这些信息没有兴趣,直接可以使用NULL替代。
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程, wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
waitpid方法更具有灵活性。
参数pid>0时,只等待进程ID等于pid的子进程,只要指定的子进程没有结束,waitpid就会一直等待
参数pid=0时,等待同一个进程组中的任一子进程,如果子进程已经加入别的进程组,waitpid不会对其进行处理
参数pid=-1,等待任何一个子进程退出,与wait作用一样
参数pid<-1,等待一个指定进程组中的任何子进程,这个进程组的id等于pid的绝对值
options
WHOHANG,设置了一个值后,即使没有子进程退出,它也会返回,不想wait一直阻塞
不想使用可以设置为0
守护进程
Linux系统中的守护进程是一种运行在后台的进程。而守护进程,也就是通常说的Daemon进程。它通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。linux大多数服务器进程就是用守护进程实现的,例如web服务。守护进程常常在系统引导装入时启动,在系统关闭时终止。守护进程最大的特点是运行在后台,与终端无连接,除非特殊情况下,用户不能操作守护进程。
守护进程创建过程
1 创建子进程,父进程退出
这是创建守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成一程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离。
2 在子进程中创建新会话
Linux是一个多用户多任务系统,每个进程都有一个进程ID,同时每个进程还都属于某一个进程组,而每个进程组都有一个组长进程,组长进程的标识ID等于进程组的ID,且该进程组ID不会因组长进程的退出而受到影响。会话期是一个或多个进程组的集合,通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。一个会话期可以有一个单独的控制终端,只有其前台进程才可以拥有控制终端,实现与用户的交互。从shell中启动的每一个进程将继承一个与之相结合的终端,以便进程与用户交互,但是守护进程不需要这些,子进程继承父进程的会话期和进程组ID,子进程会受到发送给该会话期的信号的影响,所以守护进程应该创建一个新的会话期,这个步骤是创建守护进程中最重要的一步,虽然它的实现非常简单,但它的意义却非常重大。在这里使用的是系统函数setsid来实现的。
setsid函数用于创建一个新的会话,并担任该会话组的组长。调用setsid有下面的3个作用:
让进程摆脱原会话的控制
让进程摆脱原进程组的控制
让进程摆脱原控制终端的控制**
由于创建守护进程的第一步调用了fork函数来创建子进程,再将父进程退出。在调用fork函数时,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,还还不是真正意义上的独立开来,而setsid函数能够使进程完全独立出来,从而摆脱其他进程的控制。
3 改变当前目录为根目录
使用fork创建的子进程继承了父进程的当前工作目录。守护进程不应当使用父进程的工作目录,应该设置自己的工作目录,通常可以通过chdir()来完成,一般可以将其设置为根目录,不过有些守护进程需要将它设置到自己特定的工作目录,但此时必须保证所设置的工作目录处于一个不能卸载的文件系统中,因为守护进程通常在系统引导后是一直存在的。
4 重设文件权限掩码
守护进程从父进程继承来的文件创建方式掩码可能会拒绝设置某些许可权限,文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。由于使用fork函数新建的子进程继承了父进程的文件权限掩码,可能使守护进程的执行出现问题,因此,把文件权限掩码设置为0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是umask。在这里,通常的使用方法为umask(0)。
5 关闭文件描述符
一般情况下,进程启动时都会自动打开终端文件,但是守护进程已经与终端脱离,所以终端描述符应该关闭。用fork函数新建的子进程也会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。
6 忽略SIGCHLD信号
这一步只对需要创建子进程的守护进程才有必要,很多服务器守护进程设计成通过派生子进程来处理客户端的请求,如果父进程不对SIGCHLD信号进行处理的话,子进程在终止后变成僵尸进程,通过将信号SIGCHLD的处理方式设置为SIG_IGN可以避免这种情况发生。
7 用日志系统记录出错信息
因为守护进程没有控制终端,当进程出现错误时无法写入到标准输出上,可以通过调用syslog将出错信息写入到指定的文件中。
8 守护进程退出处理
当用户需要外部停止守护进程运行时,往往会使用 kill命令停止该守护进程。所以,守护进程中需要编码来实现kill发出的signal信号处理,达到进程的正常退出。
本文参考资料如下:
http://my.oschina.net/guol/blog/121865
http://www.cnblogs.com/LUO77/p/5728828.html