守护进程和inetd超级服务器
1.概述
守护进程(daemon)是后台运行且不与任何控制终端关联的进程。Unix系统通常有很多守护进程在后台运行(约20~50个量级),执行不同的管理任务。
守护进程的启动方法:
(1)系统启动阶段,由初始化脚本启动,通常这些脚本在/ect/rc目录下,这些守护进程都是有root权限的。
(2)许多网络服务器由inetd超级服务器启动。
(3)cron守护进程定期执行一些程序,自身由(1)中方法启动。
(4)at命令指定将来某个时刻的程序执行。当这些程序的执行时间到了由cron来启动。
(5)可以从用户终端前台和后台启动。
由于守护进程没有控制终端,通常使用syslog函数来输出消息,并通过syslogd守护进程接收消息。
2.syslogd守护进程
syslogd守护进程由某个系统初始化脚本启动,并且在系统工作期间一直运行。可以在终端输入ps -aux | grep syslog
查看。当syslog守护进程启动时一般执行下列步骤:
(1)读取配置文件。通常为
/etc/syslog.conf
指定syslogd守护进程可能接收到的各种日志消息(log message)和怎样处理。当这些消息被添加到/dev/console
中时,则消息打印到控制台。
(2)创建Unix域套接字,捆绑路径名/var/run/log,我的ubuntu14.04是/dev/log
(3)创建udp套接字,端口号为514,通过命令cat /etc/services | grep "syslog"查看
(4)打开路径名/dev/klog。内核的错误消息看着像是这个设备的输入。
之后syslogd守护进程select等待它的3个描述符(上述2,3和4步),直到其中之一变成可读,读入日志消息,并按照配置文件处理。如果守护进程收到SIGHUP信号,就重新读取配置文件。
3.syslog函数
守护进程没有控制终端,因此我们不能把消息fprintf到stderr上,那么就可以使用syslog函数:
#include <syslog.h>
void syslog(int priority , const char *message, ...);
第一个参数是级别和设施两者的组合,级别的范围从0~7,0表示级别最高,7表示级别最低。默认情况是5,这里定义的宏是LOG_NOTICE
;而设施选项默认是LOG_USER
,这里两个选项的宏定义可以通过man syslog
查看。
通过设施和级别允许在/etc/syslog.conf文件中同一配置来自同一给定设施的所有消息,或者统一配置具有相同级别的所有消息。
4.编写守护进程
通过daemon()
函数可以把一个普通进程转变为守护进程,书中自己写了一个daemon_init()
函数来创建守护进程,事实上,将普通进程变成守护进程是有一个套路的:
(1)在父进程中执行fork并exit退出;
(2)在子进程中调用setsid函数创建新的会话;
(3)在子进程中调用chdir函数,让根目录 ”/” 成为子进程的工作目录;
(4)在子进程中调用umask函数,设置进程的umask为0;
(5)在子进程中关闭任何不需要的文件描述符
~/unpv13e/inetd/daytimetcpsrv2.c
就是使用作者自己编写的daemon_init,直接编译并运行(需要sudo权限),在终端输入:
nc localhost daytime
tail /var/log/syslog
可以看到类似如下输出:
Jun 10 xx:xx:xx Linux daytimetcpsrv2[xxxx]: connection from 127.0.0.1:xxxx
5.inetd守护进程
许多服务器,他们只是等待客户请求的到达,比如FTP Telnet等等,以前这些服务都是与进程关联,这些进程都在系统初始化脚本中启动例如/etc/rc等,这些进程几乎执行相同的启动任务(这也跟之前学习的TCP/UDP服务器类似):创建套接字,绑定端口到套接字上,等待连接(tcp)或数据报(udp),派生子进程。子进程服务,父进程监听。
使用inetd守护进程可以使得基于TCP或者UDP的服务器都可以使用这个守护进程,书中给出两点原因:
(1)通过inetd处理普通守护进程的大部分启动细节简化守护进程编写。
(2)单个进程就能为多个服务等待外来客户请求,减少了系统中的进程总数。
/etc/inetd.conf配置文件可以配置指定本超级服务器处理哪些服务以及档一个服务请求到达时该怎么做,一般来说,一行包含的信息包括:
字段 | 说明 |
---|---|
service-name | /etc/service中需要对应 |
socket-type | tcp(stream)或udp(dgram) |
protocol | tcp或udp |
wait-flag | TCP一般为nowait UDP一般为wait |
login-name | 来自/etc/passwd的用户 |
server-program | 应用完整路径+应用名 |
server-program-arguments | 命令行参数 |
PS:在ubuntu中inetd.conf不存在,我们可以直接创建,并按照上表的规则填写:
mydaytime stream tcp nowait zhangxiao /home/zhangxiao/unixsocket/unpv13e/inetd/daytimetcpsrv3 daytimetcpsrv3
其中,我已经按照书中所说在/etc/service中添加mydaytime 9999/tcp
接着我们重启服务:
/etc/init.d/xinetd restart
此时用netstat命令来验证9999这个TCP监听套接字:
netstat -na | grep 9999
然后新开一个终端访问这个服务器:
telnet localhost 9999
查看日志信息:
tail /var/log/syslog
inetd工作流程
总结一下,就是为配置文件中的服务创建合适的套接字绑定监听,然后select这些描述符。select可读后,创建子进程处理服务请求。有点像标准的并发服务器。当派生出子进程后,由于子进程完成对父进程的copy,父进程close掉已连接描述符,而子进程close掉除了已连接描述符的所有描述符(因为也会拷贝其他监听套接字描述符),并将套接字描述符dup到0,1,2(标准输入,标准输出和错误)。这个dup的作用可以理解为对描述符的重定向(参考:Linux Dup)。最后就是修改用户并执行了。整个流程图见下: