Zach的博客

系统服务——daemon

daemon

什么是daemon

简单来说,系统为了某些功能必须提供一些服务,这个服务我们称之为service,但是service的提供需要进程的运行,所以实现这个service的程序我们称之为daemon。我们不必
区分daemon与service,或者说可以将他们视为等同的。因为没有daemon在后台运行就不会有这个serivce。daemon没有控制终端,所以当有事发生时,它们需要有消息输出的方法可用。
syslog函数是输出这些消息的标准方法,它把这些消息发送给syslogd守护进程。

syslogd的函数原型为:

1
2
3
#include <syslog.h>

void syslog(int priority, const char *message, ...);

priority由level和facility组成,具体可以查看UNP P288。

daemon的主要分类

如果按照启动与管理方式分类:

  • stand alone:这种类型的daemon不必通过其他机制来管理,可以自行启动,一旦启动就常驻内存。其最大的优点是由于一直在内存内持续的提供服务,因此对于客户的请求响应较快。常见的
    stand alone的daemon有ftp、httpd等。
  • super daemon:一个特殊的daemon来统一管理其他的daemon。这一种服务的启动方式通过同一个daemon来负责唤起服务。这个特殊的daemon被称为super daemon。早期的super daemon是inetd,现在
    Linux下是xinetd。当客户没有请求时,对应的服务未启动,只有当客户有对应的请求来到时,super daemon才会唤醒相应的服务,当请求完成之后,被唤醒的这个服务也会关闭并释放资源。该机制的优点在于:1)super daemon
    可以具有安全管控机制;2)服务在请求结束后就关闭,不会一直占用资源。缺点在于对于请求的响应较慢。

如果按照工作形态分类:

  • signal-control:这种daemon通过信号来管理,只要有任何客户端的请求进来,它就会立即启动取处理。
  • interval-control:这种daemon每隔一段时间就主动取执行某项工作。

将一个程序作为daemon运行

Linux提供了daemon函数将一个普通进程转变为守护进程运行。它的原理和这里给出的daemon_init函数大同小异。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int daemon_init(const char *pname, int facility) {
pid_t pid;

if ((pid = Fork()) < 0)
return -1;
else if (pid > 0)
_exit(0);

/* child continues */

/* become session leader */
if (setsid() < 0)
return -1;

/* 必须忽略SIGHUP,否则当会话头进程终止时,会话中的所有进程都收到SIGHUP信号 */
Signal(SIGHUP, SIG_IGN);
/* 再次Fork,使得进程不再是会话头 */
if ((pid = Fork()) < 0)
return -1;
else if (pid)
_exit(0);

daemon_proc = 1;

chdir("/");

for (int i=0; i<MAXFD; i++)
close(i);

/* redirec stdin, stdout and stderr to '/dev/null' */
open("/dev/null", O_RDONLY);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);

openlog(pname, LOG_PID, facility);

return 0;
}
  1. 首先是fork,在fork结束之后,父进程终止,子进程自动在后台运行。另外,由于子进程继承了父进程的进程组ID,这保证了子进程不是一个进程组的头进程,这为下面的setsid做了准备。
  2. setsid,其用于创建一个新的会话,当前进程变为新的会话的会话头进程以及新进程组的进程组头进程,从而不再有控制终端。
  3. 忽略SIGHUP信号并再次fork,再次fork保证本守护进程不会是会话头进程,那么即使将来打开了一个终端设备,也不会自动获得控制终端。因为当没有控制终端的会话头进程打开一个终端设备时,该终端会自动成为
    这个会话头进程的控制终端。忽略SIGHUP信号是因为当会话头进程终止时,其会话中的所有进程(再次fork产生的子进程)都收到SIGHUP信号。
  4. 将stdin、stdout以及stderr重定向到/dev/null,打开这些描述符的理由在于,守护进程调用的那些假设能从这三个描述符读写的库函数不会因为这些描述符未打开而失败。
  5. 调用syslog处理函数。

既然守护进程在没有控制终端的环境下运行,那么它绝对不会收到来自内核的SIGHUP信号,许多守护进程因此把这个信号作为来自系统管理员的一个通知,表示其配置文件已经发生变化,守护进程应该重新读入配置文件。

Linux下daemon的启动脚本和启动方式

配置文件位置

通常daemon的启动脚本放在/etc/init.d/目录下,启动脚本可以进程环境检测、配置文件分析、PID文件放置以及相关重要交换文件的锁操作。

super daemon的配置文件放置在/etc/xinetd.d/目录和/etc/xinetd.conf中。

在/etc/目录下还有各自服务的配置文件

/var/lib/目录下是一些会产生数据的服务放置产生的数据库的位置,数据库管理系统MySQL的数据库默认写入/var/lib/mysql。

/var/run/目录下存放各服务程序的PID记录。

对于stand alone的daemon,我们即可执行/etc/init.d/目录下的脚本来启动它,

1
➜  ~ /etc/init.d/mysql start

也可以利用service命令(实际上它也是一个stand alone的服务)

1
➜  ~ service mysql start

super daemon配置文件

Linux下super daemon由xinetd这个进程实现,它不仅可以启动其他daemon,还可以进行安全性或者其他管理机制的控制。xinetd的默认配置存放在/etc/xinetd.conf中,由xinetd启动的服务程序的配置放置在/etc/xinetd.d目录下。
如果一个服务的配置文件没有xinetd.conf中指定的参数,那么该服务对应的参数就以xinetd.conf中的为准。具体的参数说明可以参加《鸟哥——基础篇》P559。一个简单的daytime服务配置如下:

1
service mydaytime
{
        disable         = no
        socket_type     = stream
        wait            = no
        user            = root
        server          = /home/zach41/Desktop/unp/unpv13e/inetd/daytimetcpsrv3
        log_on_failure  = USERID
}

这之后我们还需要修改/etc/services文件,分配一个端口来提供daytime服务。具体就是添加如下一行:

1
mydaytime    9999/tcp

这样配置之后,我们重启xinetd服务

1
➜  ~ service xinetd restart
➜  ~ sudo netstat -tnlp | grep 9999
[sudo] password for zach41: 
tcp        0      0 0.0.0.0:9999            0.0.0.0:*               LISTEN      15914/xinetd

可以看到xinetd进程在监听9999端口。

我们运行一个daytime客户端程序

1
➜  names git:(master) ✗ ./daytimetcpcli2 0.0.0.0 9999
trying 0.0.0.0:9999
Mon Sep 19 18:43:04 2016

daytime daemon程序

最后这里给出daytimeserver的代码,这是由xinetd作为守护进程启动的时间获取服务器程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include	"unp.h"
#include <time.h>

int
main(int argc, char **argv)
{

socklen_t len;
struct sockaddr *cliaddr;
char buff[MAXLINE];
time_t ticks;

daemon_inetd(argv[0], 0);

cliaddr = Malloc(sizeof(struct sockaddr_storage));
len = sizeof(struct sockaddr_storage);
Getpeername(0, cliaddr, &len);
err_msg("connection from %s", Sock_ntop(cliaddr, len));

ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(0, buff, strlen(buff));

Close(0); /* close TCP connection */
exit(0);
}

可以看到所有套接字的创建代码(tcp_listen和accept的调用)都不见了,这些步骤都有xinetd执行,我们使用描述符0指代已由xinetd接受的TCP连接(套接字描述符被复制到描述符0, 1, 2)。daemon_init只是负责设置daemon_proc
标志以及调用openlog,从而发送日志信息给syslogd守护进程。