守护进程(daemon)

来源:互联网 发布:淘宝商品详情模板设计 编辑:程序博客网 时间:2024/05/18 02:20

8.1  守护进程(daemon)介绍

守护进程,也称为精灵进程,是一种运行在后台的特殊进程,它不存在控制终端,并周期性地执行某项任务或等待处理某项任务。

图8.1所示为使用ps命令查看Linux系统进程的情况。从图中可以看到守护进程的一些特点:所有的守护进程都是以超级用户启动的(UID为0);没有控制终端(TTY为?);终端进程组ID为-1(TPGID表示终端进程组ID,该值表示与控制终端相关的前台进程组,如果未和任何终端相关,其值为−1)。

所有的守护进程的父进程都为init进程(PID为1的进程),如图8.2所示。可以使用pstree命令列出Linux系统中进程树的结构来进行验证。

 

 

 

图8.1  ps命令显示的系统进程                     

 

 

8.2  创建守护进程

由于守护进程的特点,编写守护进程程序必须遵守一定的规则。本节将阐述这些规则的要点,并给出相关代码。

8.2.1  实现守护进程的步骤
在Linux系统中,要编程实现一个守护进程必须遵守如下的步骤。

1.让init进程成为新产生进程的父进程。
调用fork函数创建子进程后,使父进程立即退出。这样,产生的子进程将变成孤儿进程,并被init进程接管,同时,所产生的新进程将变为在后台运行。

2.调用setsid函数
通过调用setsid函数,使得新创建的进程脱离控制终端,同时创建新的进程组,并成为该进程组的首进程。为了使读者更好地理解这一步骤,下面介绍进程组、会话(session)的基本概念。

在Linux系统中,所有的进程都属于各自的进程组。进程组是一个或多个进程的集合。打个比方,可以认为某个班级是一个进程组,而其中成员就是进程。一个班级至少有一个成员。当一个班级的最后一个成员不存在的时候,这个班级也就不存在了,也就是进程组消亡了。

每个进程组都有类似于进程号的标识,称为进程组ID。进程组ID是由领头进程的进程号决定的,每个进程组都存在一个领头进程。进程组的存在与否与领头进程是否存在没有关系。

会话是一个或多个进程组的集合。与进程组类似,每个会话都存在一个领头进程。Linux是一个多用户的操作系统,在同一时刻系统中会存在属于不同用户的多个进程。如果用户在某个终端上发送了某个信号,例如,按下“Ctrl+C”发送SIGINT信号,如何确保信号被正确地发送到对应的进程,同时不会影响使用其他终端的用户的进程?

会话和进程组是Linux内核用于管理多用户情况下用户进程的方法。每个进程都属于一个进程组,而进程组又属于某个会话。当用户从终端登录系统(不管是终端还是伪终端),系统会创建一个新的会话。在该终端上启动的进程都会被系统划归到会话的进程组中。

会话中的进程通过该会话中的领头进程(常称其为控制进程)与一个终端相连。该终端是会话的控制终端。一个会话只能有一个控制终端,反之一样。如果会话存在一个控制终端,则它必然拥有一个前台进程组。属于该组的进程可以从控制终端获得输入。这时,其他的进程组都为后台进程组。图8.3所示为会话、进程组、进程与控制终端之间的关系。

 

 

 

图8.3  会话、进程组、进程与控制终端的关系

由于守护进程没有控制终端,而使用fork函数创建的子进程继承了父进程的控制终端、会话和进程组,因此,必须创建新的会话,以脱离父进程的影响。Linux系统提供了setsid函数用于创建新的会话。setsid函数的信息如表8.1所示。

表8.1   setsid函数

头文件

<unistd.h>

函数形式

pid_t setsid(void);

返回值

成功

失败

是否设置errno

调用进程的会话ID

−1

 

setsid函数将创建新的会话,并使得调用setsid函数的进程成为新会话的领头进程。调用setsid函数的进程是新创建会话中的惟一的进程组,进程组ID为调用进程的进程号。setsid函数产生这一结果还有个条件,即调用进程不为一个进程的领头进程。由于在第一步中调用fork的父进程退出,使得子进程不可能是进程组的领头进程。该会话的领头进程没有控制终端与其相连。至此,满足了守护进程没有控制终端的要求。

3.更改当前工作目录
使用fork函数产生的子进程将继承父进程的当前工作目录。当进程没有结束时,其工作目录是不能被卸载的。为了防止这种问题发生,守护进程一般会将其工作目录更改到根目录下(/目录)。更改工作目录使用的函数是chdir。

4.关闭文件描述符,并重定向标准输入、输出和错误输出
新产生的进程从父进程继承了某些打开的文件描述符,如果不使用这些文件描述符,则需要关闭它们。守护进程是运行在系统后台的,不应该在终端有任何的输出信息。可以使用dup函数将标准输入、输出和错误输出重定向到/dev/null设备上(/dev/null是一个空设备,向其写入数据不会有任何输出)。下面给出具体的代码:

 …int fd;//将标准输入输出重定向到空设备fd = open ("/dev/null", O_RDWR, 0);if (fd != -1){dup2 (fd, STDIN_FILENO);dup2 (fd, STDOUT_FILENO);dup2 (fd, STDERR_FILENO);if (fd > 2)close (fd);}…

5.设置守护进程的文件权限创建掩码
很多情况下,守护进程会创建一些临时文件。出于安全性的考虑,往往不希望这些文件被别的用户查看。这时,可以使用umask函数修改文件权限,创建掩码的取值,以满足守护进程的要求。

8.2.2  守护进程具体实现

本节给出一个守护进程创建的实例。程序p8.1.c中定义了daemon函数,用于实现对守护进程的创建。其创建思想在8.2.1中有详细的介绍,程序的具体代码如下:

 

//p8.1.c 守护进程的实现#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>

/* daemon函数用于将调用函数的进程转化为守护进程 */intdaemon (int nochdir, int noclose){pid_t pid;

pid = fork ();

/* 如果创建进程失败 */if (pid < 0){perror ("fork");return -1;}

/* 父进程退出运行 */if (pid != 0)exit (0);

/* 成为会话领头进程 */pid = setsid();

if (pid < -1){perror ("setsid");return -1;}

/* 将工作目录修改成根目录 */if (! nochdir)chdir ("/");

/* 将标准输入输出重定向到空设备 */if (! noclose){int fd;

fd = open ("/dev/null", O_RDWR, 0);if (fd != -1){dup2 (fd, STDIN_FILENO);dup2 (fd, STDOUT_FILENO);dup2 (fd, STDERR_FILENO);if (fd > 2)close (fd);}}

umask (0027);

return 0;}

int main(void){daemon(0,0);sleep(1000);return 0;} 

使用gcc编译p8.1.c,得到名为p8.1的可执行文件。执行该程序,程序将以守护进程的状态运行,如图8.4所示。

 

8.3  守护进程的日志实现

由于守护进程并不拥有控制终端,因此无法将进程运行信息输出显示。但有时候需要根据进程提供的信息来进行系统管理和维护工作。为此,Linux系统提供了一种特殊的机制来解决守护进程的日志问题。syslogd守护进程通过接收其他守护进程的信息,并将这些信息记录在指定位置来解决日志记录问题。syslogd守护进程会根据消息级别来判断是将消息记录在日志文件,还是显示在用户终端上。本节将首先介绍syslogd的基本知识,然后介绍如何编程实现守护进程的日志。

8.3.1  syslogd守护进程

syslogd守护进程负责记录、发送系统或工具所产生的信息,其配置文件是/etc/syslog.conf。当系统内核或工具产生信息时,通过调用相关函数,将信息发送到syslogd守护进程。syslogd守护进程会根据/etc/syslog.conf中的配置信息,对消息的去向做出处理。例如,将消息记录在系统日志中、输出到控制台或转发给某个指定的用户,甚至可以让syslogd将信息通过网络转发给网络上其他主机的syslogd守护进程实现系统的集中管理。

8.3.2  syslogd守护进程配置文件说明

syslogd守护进程根据/etc/syslog.conf中的配置信息实现消息转发和处理。下面是该配置文件的具体信息:

 

# Log all kernel messages to the console.# Logging much else clutters up the screen.#kern.*                                                 /dev/console

# Log anything (except mail) of level info or higher.# Don't log private authentication messages!*.info;mail.none;authpriv.none;cron.none                /var/log/messages

# The authpriv file has restricted access.authpriv.*                                              /var/log/secure

# Log all the mail messages in one place.mail.*                                                  -/var/log/maillog

# Log cron stuffcron.*                                                  /var/log/cron

# Everybody gets emergency messages*.emerg                                                 *

# Save news errors of level crit and higher in a special file.uucp,news.crit                                          /var/log/spooler

# Save boot messages also to boot.loglocal7.*                                                /var/log/boot.log

在syslog.conf文件中以#开头的是注释行,配置行的语法格式为:[消息类型][TAB分隔符][处理方案]。

1.消息类型
消息类型包括facility和level两个部分,这两个部分以“.”隔开,如:
mail.*                                                  -/var/log/maillog

表示facility为mail,而level是所有级别。处理方案是将消息记录在/var/log/maillog日志文件中。表
8.2所示为facility可以使用的字段。
表8.2   facility字段

 

facility取值

说    明

kern

0

内核日志信息

user

1

用户日志信息

mail

2

邮件系统日志信息

daemon

3

系统守护进程日志信息

auth

4

安全管理日志信息

syslog

5

syslogd守护进程的日志信息

lpr

6

打印服务日志信息

news

7

新闻组服务日志信息

uucp

8

uucp系统日志信息

cron

9

守护进程cron的日志信息

authpriv

10

私有的安全管理日志信息

ftp

11

ftp守护进程

 

 

facility取值

说    明

 

12~15

系统保留

local0~local7

16~23

保留给系统本地使用

 

level给出的级别代表着信息的重要性,表8.3所示为level中的级别信息。

表8.3   level字段

level取值

说    明

emerg

0

系统不可用,出现这一情况的话,应该通知所有用户,因为这表示系统出现了严重的问题

alert

1

警告事件,必须立即采取行动纠正的事件

crit

2

关键事件,例如硬件出现故障

err

3

其他错误事件

warning

4

警告事件

notice

5

应该注意的事件,需要特别处理

info

6

通报信息

debug

7

调试程序时产生的信息

 

在syslog.conf中,可以使用*表示任何facility和任意的level。通过指定facility和level的取值,可以使facility在大于或等于该优先级的时候才记录相关日志信息。对于level,还可以使用none参数,表示不包含任何优先级别。例如,*.*表示任何facility的任何优先级的信息。*.emerg表示任何facility的大于或高于emerg优先级的信息。

2.处理方案
处理方案中指定的字段可以为如下的内容。
 
普通文件:使用文件的绝对路径来指明日志文件所在位置。
 
管道文件:在syslogd中支持使用管道文件(FIFO),在管道文件路径名前加“|”,表示将所得信息写入管道中。
 
终端设备:通过将信息写入/dev/console中,可以在终端显示相关信息。
 
其他主机:syslogd支持将信息发往网络中的其他主机的syslogd守护进程,以实现集中管理,格式为
“@hostname”。
 
用户列表:syslogd守护进程支持将日志信息发给某些指定的用户。
 
所有登录用户:可以用*表示所有登录的用户。在获得日志信息时,syslogd守护进程会自动将信息发送给登录用户。

下面选取前面给出的syslog.conf中的一条配置来解释其意义。具体配置如下:
cron.*                                                  /var/log/cron

配置中的facility为cron,表示记录守护进程cron的信息。level为*,表示记录所有优先级别的信息,并将日志信息记录到/var/log/cron文件中。在Linux系统中,往往把各种日志文件保存在/var/log目录中。

 

8.3.3  守护进程日志的实现

syslogd守护进程用于解决守护进程的日志记录问题,而日志信息保存的位置和记录的信息级别是在syslogd守护进程的配置文件中设定的。守护进程日志主要涉及3个函数,分别是openlog、syslog和closelog函数。表8.4所示为这3个函数的具体信息。

表8.4   openlog、syslog和closelog函数

头文件

<syslog.h>

函数形式

void openlog(const char *ident, int option, int facility);

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

void closelog(void);

返回值

成功

失败

是否设置errno

 

说明:openlog函数用于打开系统日志连接。只有在打开连接后,才能使用syslog函数向日志文件中添加日志信息。closelog函数用于关闭打开的系统日志连接,该函数的调用在实现中是可选择的。

openlog函数中的第1个参数为ident,该参数常用来表示信息的来源。ident指向的字符信息会被固定地添加在每行日志的前面。第2个参数option用于指定openlog函数和接下来调用的syslog函数的控制标志。option的取值情况如表8.5所示,可以单独取其中的某个值,也可以通过与运算来获得多种特性。第3个参数为facility,这个要与syslogd守护进程的配置文件对应,日志信息会写入syslog.conf文件指定的位置。

表8.5   openlong函数中的option取值表

参    数

说    明

LOG_CONS

如果将信息发送给syslogd守护进程时发生错误,直接将相关信息输出到终端

LOG_NDELAY

立即打开与系统日志的连接(通常情况下,只有在产生第一条日志信息的情况下才会打开与日志系统的连接)

LOG_NOWAIT

在记录日志信息时,不等待可能的子进程的创建

LOG_ODELAY

类似于LOG_NDELAY参数,与系统日志的连接只有在syslog函数调用时才会创建

参    数

说    明

LOG_PERROR

在将信息写入日志的同时,将信息发送到标准错误输出(POSIX.1-2001不支持该参数)

LOG_PID

每条日志信息中都包括进程号

 

参数facility的取值如表8.6所示。这些值与前面syslog.conf中的facility是存在对应关系的。

表8.6   openlog函数参数facility取值

 

facility参数

syslog.conf中对应的facility取值

LOG_KERN

kern

LOG_USER

user

LOG_MAIL

mail

LOG_DAEMON

daemon

LOG_AUTH

auth

LOG_SYSLOG

syslog

LOG_LPR

lpr

LOG_NEWS

news

LOG_UUCP

uucp

LOG_CRON

cron

LOG_AUTHPRIV

authpriv

LOG_FTP

ftp

LOG_LOCAL0~LOG_LOCAL7

local0~local7

 

syslog函数中的第一个参数priority表示消息的级别。与openlog函数中的facility参数类似,priority参数与level也存在对应的关系。priority取值和对应关系如表8.7所示。

表8.7   syslog函数参数priority取值

priority参数

syslog.conf中对应的level取值

LOG_EMERG

emerg

LOG_ALERT

alert

LOG_CRIT

crit

LOG_ERR

err

LOG_WARNING

warning

LOG_NOTICE

notice

LOG_INFO

info

LOG_DEBUG

debug

 

syslog函数的第二个参数为消息的格式,之后是格式对应的参数。函数的使用类似于printf函数。

实例演练:
在程序中添加日志功能需要遵循如图8.6所示的步骤。调用syslog函数的过程就是写入日志的过程(如果没有调用openlog函数,直接调用syslog函数的话,facility参数可以作为priority参数的一部分加以说明)。

 
图8.6  程序添加日志功能工作流程

程序p8.2.c给出了在程序中实现日志的简单实例。首先,程序调用openlog函数打开与系统日志间的连接;然后,通过调用syslog函数产生5条日志信息;最后,关闭与系统日志间的连接。

 

 

#include <stdio.h>#include <syslog.h>

int main(int argc,char* argv[]){openlog(argv[0],LOG_CONS | LOG_PID, LOG_USER);

int count=0;while(count<5){syslog(LOG_INFO,"%d, log info test...",count);count++;}

closelog();

return 0;}


使用gcc编译p8.2.c,执行编译出的可执行文件。

 

 [program@localhost charter8]$ gcc p8.2.c[program@localhost charter8]$ ./a.out

如何查看执行结果呢?

前面介绍了syslogd守护进程会根据syslog.conf文件中的配置,将信息写入指定的日志文件中。调用syslog函数时,指定的priority为LOG_INFO,对应于syslog.conf中的info优先级。而调用openlog函数的facility为LOG_USER,对应于syslog.conf中facility取user的情况。查找syslog.conf中的facility.level为user.info的规则,如图8.7所示。可知,日志文件为/var/log/message。

由于查看/var/log下的日志需要root权限,使用su切换到root用户。使用tail命令查看message文件中的日志信息,发现相关信息已经通过syslogd守护进程记录在了/var/log/message日志文件中,如图8.8所示。

通过程序p8.2.c可以清楚地了解如何在程序中实现日志功能。在守护进程中添加日志功能的流程与p8.2.c几乎是相同的,这里就不再给出实例。

 

 

 

图8.7  syslog.conf中对应的规则                          

 

8.4  Client/Server工作模式

由于守护进程的特点,守护进程往往作为服务器(Server)进程。例如,syslogd守护进程用来接收其他守护进程发送的消息。其他的守护进程是作为客户机(Client)存在的。

一般情况下,服务器是一个为客户机服务的进程。客户机给服务器发送服务请求,服务器相应这种请求。这一工作模式存在于很多场合,例如,常见的ftp服务器、telnet服务