目录
- 一、概述
- 二、编程规则流程
- 三、出错处理
一、概述
- 守护进程( daemon)是生存期长的一种进程。
- 它们常常在系统引导装入时启动,仅在系统关闭时才终止。
- 由于在 Linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时相应的进程都会自动关闭。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才会退出。如果想让某个进程不因为用户或终端或其他的变化而受到影响,那么就必须把这个进程变成一个守护进程。可见,守护进程是非常重要的
- 像Linux的大多数服务器就是用守护进程的方式实现的。如web服务器进程http等。
-
ps -axj
查看守护进程
二、编程规则流程
(1) 重设文件权限掩码
(2) fork() 创建子进程,父进程退出
(3) 在子进程中创建新会话
(4) 改变当前目录为根目录
(5) 关闭文件描述符
(1) 重设文件权限掩码
- 文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有一个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。
- 由于从父进程那继承而来的文件权限(mode),子进程在创建屏蔽字时可能会被设置为拒绝某些权限。就会给子进程在使用文件时造成诸多麻烦。比如,守护进程要创建可读、可写的文件,而继承而来的文件权限创建屏蔽恰好拒绝了可读可写权限中的一种,使守护进程无法发挥相应的作用。所以需要重设屏蔽字,通常为0,可读可写可执行,这样增强了守护进程对文件权限的灵活性。
(2) fork 创建子进程,父进程退出
- 由于守护进程是脱离控制终端的,因此,完成第一步后就会在 Shell终端里造成一程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shel终端里则可以执行其他的命令,从而在形式上做到了与控制终端的脱离。
#include <unistd.h>
pid_t fork(void);
(3) 在子进程中创建新会话
- 这个步骤是创建守护进程中最重要的一步,虽然它的实现非常简单,但它的意义却非常重大。在这里使用的是系统函数 setid,在具体介绍 setid之前,读者首先要了解两个概念进程组和会话期。
① 进程组:进程组是一个或多个进程的集合。进程组由进程组ID来惟一标识。除了进程号(PID)之外,进程组ID也一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID。且该进程ID不会因组长进程的退出而受到影响。
② 会话期:会话组是一个或多个进程组的集合。通常,个会话开始于用户登录,终止于用户退出在此期间该用户运行的所有进程都属于这个会话期,它们之间的关系如下图所示。 - setid函数作用setid函数用于创建一个新的会话,并担任该会话组的组长。调用 setid有下面的3个作用。
① 让进程摆脱原会话的控制
② 让进程摆脱原进程组的控制
③ 让进程摆脱原控制终端的控制
#include <unistd.h>
//返回进程组ID,失败 -1
pid_t setsid(void);
- 第二步子进程依然继承着父进程的会话期、进程组、控制终端,并没有真正意义上的独立出来,第三步才算是独立出来
(4) 改变当前目录为根目录
- 从父进程处继承过来的当前工作目录可能在一个挂载的文件系统中。因为守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当前工作目录在一个挂载文件系统中,那么该文件系统就不能被卸载。
- 或者,某些守护进程还可能会把当前工作目录更改到某个指定位置,并在此位置进行它们的全部工作,如/tmp目录
#include <unistd.h>
int chdir(const char *path);
int fchdir(int fd);
(5) 关闭文件描述符
- 同文件权限掩码一样,用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读或写,但它们一样消耗系统资源,而可能导致所在的文件系统无法卸下。
- 在创建会话期后,进程就已经与终端失去联系,因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如 printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2的3个文件(常说的输入、输出和报错这3个文件)已经失去了存在的价值,也应被关闭。通常按如下方式关闭文件描述符
for(i=0: i<MAXFILE; i++)
close(i);
(6) 示例
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <time.h>
#define MAXFILE 65535
int main()
{
printf("pid = %d\n", getpid());
pid_t pid;
// 第一步
umask(0);
// 第二步
pid = fork();
if(pid < 0) {
perror("fork error!");
exit(1);
} else if(pid > 0) {
exit(0);
}
// 第三步
setsid();
// 第四步
chdir("/");
// 第五步
//for (i = 0; i < MAXFILE; ++i) {
for (i = 0; i < 3; i++) {
close(i);
}
for (;;) {
/* .... */
}
}
三、出错处理
- 由于守护进程完全脱离了控制终端,因此,不能像其他进程的程序一样通过输出错误信息到控制终端来通知程序员即使使用gdb也无法正常调试。那么,守护进程的进程要如何调试呢?一种通用的办法是使用 syslog服务,,将程序中的出错信息输入到“/var/log/messages”系统日志文件中,从而可以直观地看到程序的问题所在。
- syslog是 Linux中的系统日志管理服务,通过守护进程 syslogd来维护。该守护进程在启动时会读一个配置文件“/etc/ syslog. conf”。该文件决定了不同种类的消息会发送向何处。例如,紧急消息可被送向系统管理员并在控制台上显示,而警告消息则可记录到一个文件中。该机制提供了3个 syslog函数,分别为 openlog、 syslog和 closelog。
#include <syslog.h>
/* @function 打开一个系统日志服务
* @param[in] ident 向每个log消息加入的字符串,通常是程序名称
* @param[in] option 操作选项 查看下图
* @param[in] facility 查看下图
*/
void openlog(const char *ident, int option, int facility);
/* @function 打开一个系统日志服务
* @param[in] priority 即为消息的重要性,查看下图的level
*/
void syslog(int priority, const char *format, ...);
/* @function 关闭一个系统日志服务
*/
void closelog(void);
示例
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <syslog.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAXFILE 65535
int main()
{
printf("pid = %d\n", getpid());
int i;
int fd;
pid_t pid;
/* signal(SIGINT, SIG_IGN);// 终端中断
signal(SIGHUP, SIG_IGN);// 连接挂断
signal(SIGQUIT, SIG_IGN);// 终端退出
signal(SIGPIPE, SIG_IGN);// 向无读进程的管道写数据
signal(SIGTTOU, SIG_IGN);// 后台程序尝试写操作
signal(SIGTTIN, SIG_IGN);// 后台程序尝试读操作
signal(SIGTERM, SIG_IGN);// 终止 */
// 第一步
umask(0);
// 第二步
pid = fork();
if(pid < 0) {
perror("fork error!");
exit(1);
} else if(pid > 0) {
exit(0);
}
// 打开系统日志服务
openlog("daemon", LOG_PID, LOG_DAEMON);
// 第三步
setsid();
// 第四步
chdir("/");
// 第五步
for (i = 0; i < MAXFILE; ++i) {
close(i);
}
signal(SIGCHLD,SIG_IGN);
char *buf = "This is a daemon\r\n";
int len = strlen(buf);
for (;;) {
fd = open("/tmp/deamon.log", O_CREAT|O_WRONLY|O_APPEND, 0666);
if (fd < 0) {
syslog(LOG_ERR, "open");
exit(1);
}
write(fd, buf, len+1);
close(fd);
sleep(10);
}
closelog();
}