孤儿进程、僵尸进程和守护进程
维基百科的解释中:
在操作系统领域中,孤儿进程指的是在其父进程执行完成或被终止 后仍继续运行的一类进程。
在类UNIX系统中,僵尸进程是指完成执行(通过 exit 系统调用,或运行时发生致命错误或收到终止信号所致)但在操作系统的进程表中仍然有一个表项(进程控制块PCB),处于"终止状态 "的进程。
在一個多工的電腦作業系統中,守护进程(英语:daemon,英语发音:/ˈdiːmən/或英语发音:/ˈdeɪmən/)是一種在后台执行的电脑程序。 此类程序会被以进程的形式初始化。 守护进程程序的名称通常以字母“d”结尾:例如,syslogd就是指管理系统日志的守护进程。
个人理解:
1、一般情况下,子进程是由父进程创建,而子进程和父进程的退出是无顺序的,两者之间都不知道谁先退出。正常情况下父进程先结束会调用 wait 或者 waitpid 函数等待子进程完成再退出,而一旦父进程不等待直接退出,则剩下的子进程会被init(pid=1)进程接收,成会孤儿进程。(进程树中除了init都会有父进程)。
2、如果子进程先退出了,父进程还未结束并且没有调用 wait 或者 waitpid 函数获取子进程的状态信息,则子进程残留的状态信息( task_struct 结构和少量资源信息)会变成僵尸进程。
3、守护进程( daemon) 是指在后台运行,没有控制终端与之相连的进程。它独立于控制终端,通常周期性地执行某种任务 。 守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断 。
危害:
孤儿进程结束后会被 init 进程善后,并没有危害,而僵尸进程则会一直占着进程号,操作系统的进程数量有限则会受影响。
解决:
一般僵尸进程的产生都是因为父进程的原因,则可以通过 kill 父进程解决,这时候僵尸进程就变成了孤儿进程,被 init 进程接收
守护进程的编写:
在不同Unix环境下,守护进程的具体编程细节并不一致。但所幸的是,守护进程的编程原则其实都一样,区别仅在于具体的实现细节不同,这个原则就是要满足守护进程的特性。编程规则如下:
1、在后台运行
为避免挂起控制终端,要将 daemon 放入后台执行,其方法是,在进程中调用fork使父进程终止,让daemon在子进程中后台执行。具体就是调用 fork ,然后使父进程 exit 。这样做实现了下面几点:
第一,如果该精灵进程是由一条简单s h e l l 命令起动的,那么使父进程终止使得s h e l l 认为这条命令已经执行完成。
第二,子进程继承了父进程的进程组I D ,但具有一个新的进程I D ,这就保证了子进程不是一个进程组的首进程。这对于下面就要做的s e t s i d 调用是必要的前提条件。
2、脱离控制终端,登录会话和进程组
登录会话可以包含多个进程组,这些进程组共享一个控制终端,这个控制终端通常是创建进程的登录终端、控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。
其方法是在第一点的基础上,调用setsid()使进程成为会话组长:
需要说明的是,当进程是会话组长时,setsid()调用会失败,但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离,由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
具体是操作就是:
(a )成为新对话期的首进程
(b )成为一个新进程组的首进程
(c )没有控制终端。
3、禁止进程重新打开控制终端(fork第二次的原理)
现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:
4、关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在地文件系统无法卸下以及无法预料的错误。一般来说,必要的是关闭0、1、2三个文件描述符,即标准输入、标准输出、标准错误。因为我们一般希望守护进程自己有一套信息输出、输入的体系,而不是把所有的东西都发送到终端屏幕上。调用fclose();
5、改变当前工作目录
将当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个装配的文件系统中。因为精灵进程通常在系统再引导之前是一直存在的,所以如果精灵进程的当前工作目录在一个装配文件系统中,那么该文件系统就不能被拆卸。
另外,某些精灵进程可能会把当前工作目录更改到某个指定位置,在此位置做它们的工作。例如,行式打印机假脱机精灵进程常常将其工作目录更改到它们的s p o o l 目录上。
可以调用chdir(“目录”);
6、重设文件创建掩码
将文件方式创建屏蔽字设置为0 。由继承得来的文件方式创建屏蔽字可能会拒绝设置某些许可权。例如,若精灵进程要创建一个组可读、写的文件,而继承的文件方式创建屏蔽字,屏蔽了这两种许可权,则所要求的组可读、写就不能起作用。
7、处理SIGCHLD 信号
处理SIGCHLD信号并不是必需的。但对于某些进程,特别是服务器进程往往在请求到来时生产子进程出来请求。如果父进程不等待子进程结束,子进程将成为僵尸进程,(zombie)而仍占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在系统V下可以简单的将SIGCHLD信号的操作设为SIG-IGN:
signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程,这一点与BSD4不同,在BSD4下必须显示等 待子进程结束才能释放僵尸进程。
python 版本代码
def initDaemon(stdoutfd, stderrfd=None, basePath=None): """ 初始化成daemon进程 """ basePath = '/' if basePath is None else basePath curTimeStr = time.strftime("%Y-%m-%d %H:%M:%S") try: stdoutfd.write("Start on %s, " % curTimeStr) stdoutfd.flush() except: raise try: if os.fork() > 0: os._exit(0) except OSError: raise OSError("fork error") os.chdir(basePath) os.setsid() sys.stdout = stdoutfd sys.stderr = stdoutfd if stderrfd is None else stderrfd sys.stdin = open("/dev/null", 'r') try: if os.fork() > 0: os._exit(0) except OSError: raise OSError("fork 2 error") pid = int(os.getpid()) stdoutfd.write("pid=%s\n" % pid) stdoutfd.flush() return