重点:守护进程的编写
1.守护进程
守护进程在系统引导装入时启动,仅在系统关闭时才终止,无控制终端,在后台运行。
通过ps -efj 命令查看守护进程,如下图所示
从结果可以看出守护进程没有控制终端,其终端名设置为?,init进程ID为1。系统进程依赖于操作系统实现,父进程ID为0的各进程通常是内核进程,它们作为系统自举的一部分而启动。内核进程以超级用户特权运行,无控制终端,无命令行。
大多数守护进程都以超级用户特权运行,所有的守护进程都没有控制终端,其终端名设置为问号。守护进程又分内核守护进程和用户守护进程,用户层守护进程的父进程是init进程。内核守护进程的名字出现在方括号中,用户守护进程的名字没有方括号
2.守护进程的编程规则
(1)调用umask将文件模式创建屏蔽字设置为0。因为进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:调用umask(0)。
(2)调用fork,然后使父进程退出。这样可避免挂起控制终端将Daemon放入后台执行。
(3)调用setsid以创建一个新会话。这样可以使得调用进程成为新会话的首进程,成为一个新进程组的组长进程,没有控制终端。
(4)将当前工作目录更改为根目录。进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如/tmpchdir("/") 。
(5)关闭不再需要的文件描述符。进程从父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。
(6)某些守护进程打开/dev/null使其具有文件描述符0、1和2。使得任何一个试图读标准输入、写标准输出或者标准出错的历程都不会产生任何效果。
(7)处理SIGCHLD信号 。处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。
程序练习:初始化一个守护进程
#include <signal.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/resource.h>
void daemonsize(const char *cmd)
{
int i, fd0, fd1, fd2;
pid_t pid;
struct rlimit rl;
struct sigaction sa;
//1.设置文件模式创建屏蔽字
umask(0);
//2.创建子进程以创建会话
pid = fork();
if (pid < 0)
exit(0);
else if (pid > 0)
exit(0);
setsid();//创建新会话
//3.创建子进程以避免获取终端
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
exit(0);
if ((pid = fork()) < 0)
exit(0);
else if (pid > 0)
exit(0);
//4.更改当前工作目录
chdir("/");
//5.关闭文件描述符
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)//获取最大文件描述符
exit(0);
if (rl.rlim_max = RLIM_INFINITY)
rl.rlim_max = 1024;
for (i = 0; i < rl.rlim_max; i++)
close(i);
//6.将文件描述符定向到/dev/null上
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);
//初始化日志文件
openlog(cmd, LOG_CONS, LOG_DAEMON);
if (fd0 != 0 || fd1 != 1 || fd2 != 2)
{
syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
exit(1);
}
}
int main()
{
daemonsize("ls");
sleep(30);
exit(0);
}
结果:
图中第二行为守护进程
分析:
1.第一次调用fork的目的是保证调用setsid的调用进程不是进程组长。(而setsid函数是实现与控制终端脱离的唯一方法);setsid函数使进程成为新会话的会话头和进程组长,并与控制终端断开连接;
2.第二次调用fork的目的是:即使守护进程将来打开一个终端设备,也不会自动获得控制终端。(因为在SVR4中,当没有控制终端的会话头进程打开终端设备时,如果这个终端不是其他会话的控制终端,该终端将自动成为这个会话的控制终端),这样可以保证这次生成的进程不再是一个会话头。
3.忽略SIGHUP信号的原因是,当第一次生成的子进程(会话头)终止时,该会话中的所有进程(第二次生成的子进程)都会收到该信号。
3.出错记录
守护进程存在的一个问题是如何处理出错记录,因为它无控制终端,而且若将每个守护进程出错记录写到一个单独文件中,则管理起来很麻烦。所以需要有一个几种的守护进程出错记录设施。即syslog设施,接口函数如下:
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
int setlogmask(int mask);
#include <stdarg.h>
void vsyslog(int priority, const char *format, va_list ap);
syslog()函数产生一个日志。上述函数的参数见书P378 P379
大多数syslog实现将使消息多时间处于队列中,如果在此时间中到达了重复消息,那么syslog守护进程将不把它写到日志记录中,而是打印输出重复消息。