一、守护进程介绍
守护进程是在后台运行且不与任何控制终端相关联的进程。通常由系统初始化脚本启动,当然也可以在shell提示符下用命令行启动,不过这种守护进程必须亲自脱离于控制终端的关联。
守护进程的启动方法有:
1、系统初始化阶段,由系统初始化脚本启动。这些脚本通常位于/etc、/etc/rc开头的某个目录中。由这些脚本启动的守护进程从一开始就有root特权。例如:inetd超级服务器、Web服务器、邮件服务器、syslogd守护进程等都用这种方式启动。
注:系统初始化的时候,系统内运行着一些守护进程(syslogd inetd ......)
2、靠inetd超级服务器启动。inetd超级服务器监听网络请求,每当有一个请求到达时,启动相应的实际服务器。
3、corn守护进程按照规则定期执行一些程序,由它启动执行的程序同样作为守护进程运行。Corn自身由第一种方式启动。
4、at命令用于指定将来某个时刻的程序执行。
5、从用户终端或者后台启动。这么做往往是为了测试守护程序或重启因某种原因而终止的守护进程。
二、syslogd守护进程
syslogd守护进程由系统初始化脚本启动,在系统工作期间一直运行。步骤如下:
1、读取配置文件;
2、创建一个数据报套接字,绑定/var/run/log
3、创建一个UDP套接字,绑定端口514
4、打开路径/dev/klog。
此后便一直运行,调用select等待它的3个描述符之一变成可读,然后读入日志消息,按照配置文件进行处理。(收到SIGHUP信号,重新读取配置文件)。
注:其他的进程(例如由inetd创建的守护进程)将自己所产生的输出通过调用syslog函数发送给syslogd进程,这些消息有lever和facility,系统管理员根据严重级别,做出不同的消息处理方式。
由于守护进程没有终端,所以它的消息用fprintf到stderr上,从守护进程登记消息使用syslog函数:
void syslog(int priority,const char *message,…);
三、如何把一个普通进程转变成守护进程?
使用daemon函数或者daemon_init函数。
编程要点:
1. 在后台运行
为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。
if(pid=fork()) exit(0); //是父进程,结束父进程,子进程继续
2. 脱离控制终端,登录会话和进程组
有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们 ,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长:
setsid();
说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
3. 禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:
if(pid=fork()) exit(0); //结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
4. 关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们:
for(i=0;i 关闭打开的文件描述符close(i);
5. 改变当前工作目录
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录
改变到根目录 。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如/tmp
chdir("/")
6. 重设文件创建掩模
进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:
umask(0);
7. 处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie )从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。
signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。
四、inetd守护进程
典型的unix系统可能存在许多服务器,它们只是等待客户请求的到达,如FTP、Telnet、Rlogin、TFTP等。最开始,所有的这些服务都与一个进程相关联,这些进程在系统启动时开始运行,而且执行几乎相同的启动任务:创建一个套接口,把本服务的众所周知的端口捆绑到该套接口,等待一个连接(TCP)或是一个数据报(UDP),然后派生子进程。子进程为客户提供服务,父进程则等待下一个客户请求。这个模型存在两个问题:
1. 所有这些守护进程含有几乎相同的启动代码(套接口的创建以及成为守护进程)。
2. 每个守护进程在进程表中占据一项,并且大部分时间处于睡眠状态。
inetd超级服务器使上述的问题得到简化:
1. 通过inetd处理普通进程的大部分细节以简化守护程序的编写。
2. 单个进程(inetd)就能为多个服务等待外来的客户请求,以此取代每个服务一个进程的做法,减少了系统中的进程数。
initd守护进程的工作流程:
1,在启动阶段,读入/etc/inetd.conf文件并给该文件中指定的每个服务创建一个适当类型的套接口。
2,为每个套接口调用bind,指定捆绑相应服务器的众所周知端口和通配IP地址。
3,对于每个TCP套接口,调用listen以接受外来的连接请求。对于数据报套接口则不执行本步骤。
4,创建完毕所有套接口后,调用select等待其中任何一个套接口变为可读。
5,当select返回指出某个套接口已可读之后,如果该套接口是一个TCP套接口,而且其服务器的wait-flag值为nowait,那就调用accept接受这个新连接。
6,inetd守护进程调用fork派生进程,并由子进程处理服务请求。
7,如果第5步中select返回的是一个字节流套接口,那么父进程必须关闭接受了的已连接套接口,父进程再次调用select,等待下一个变为可读的套接口。
如果5)中,其服务器的wait-flag值为wait的话,对于数据报服务,父进程应该禁止相应的套接字(通过关闭select描述符集合中对应的位),防止select再次返回可读条件。这就意味着子进程接管了这个套接字直到自身终止为止。一旦子进程自身终止后,父进程就会被通知一个sigchld信号,而父进程此时就会获得子进程的pid。父进程通过再次打开相应的套接字的位,使得该套接字再次成为select的候选套接字。
那么,为什么要这么做呢?
原因其实是,数据报服务器只有一个套接字,它不像tcp有监听套接字也有连接套接字。如果inetd不关闭对于某个数据报套接字的可检查条件,而且父进程有又是先于子进程执行,那么引发本次fork的那个数据报仍然在套接字接收缓冲区,导致select再次返回可读条件,导致inetd进程再次fork另一个不必要的子进程。(UNP p297页)