一、基本概念
1、守护进程也成为精灵进程,是生存周期较长的一种进程。它们常常在系统自举时启动,在系统关闭时才终止。因为没有控制终端,所以说它们是在后台运行的。
2、父进程ID为0的各进程通常是内核进程,它们作为系统自举过程都得一部分而启动。
3、大多数守护进程都以超级用户(用户ID为0)特权运行。没有一个守护进程具有控制终端,其终端名设置为问号(?),终端前台进程组ID设置为-1。内核守护进程以无控制终端方式启动。用户层守护进程缺少控制终端可能是守护进程调用了setsid的结果。所有用户层守护进程都是进程组的组长进程以及会话的首进程,而且是这些进程组和会话中的唯一进程。最后,注意,大多数守护进程的父进程是init进程。
注:
二、编程规则:
(1)首先要做的是调用umask将文件模式创建屏蔽字设置为0。由继承得来的文件模式创建屏蔽字可能会拒绝设置某些权限。
例如,若守护进程要创建一组可读、可写的文件,而继承的文件模式创建屏蔽字可能屏蔽了这两种权限,于是所要求的组可读、写就不能起作用。
(2)调用fork,然后使父进程退出(exit)。这样做实现了以下几点:第一:如果守护进程是作为一条简单shell命令启动的,但具有一个新的ID,这就保证了子进程不是一个进程组的组长进程。这对于下面就要做的setsid调用是必要的前提条件。
(3)调用setsid以创建一个新会话。于是执行三个操作:
使得调用进程完成以下动作:
--称为新会话的首进程
--称为一个新进程组的组长进程
--没有控制终端
(4)将当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个装配文件 系统中。因为守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当前工作目录在一个装配文件系统中,那么文件系统就不能被拆卸。这与装配文件系统的原意不符。
另外,某些守护进程可能会把当前工作目录更改到某个指定位置,在那里做它们的工作。
(5)关闭不再需要的文件描述符。这使得守护进程不再持有从其父进程继承来的某些文件描述符(父进程可能是shell进程或者其他某个进程)。
(6)某些守护进程打开/dev/null使其具有文件描述符0、1和2,这样,任何一个试图读标准输入、写标准输出或标准出错的库例程都不会产生任何效果。
因为守护进程并不与终端设备相关联,所以不能再终端设备上显示其输出,也无处从交互式用户那里接受输入。
即使守护进程是从交互式会话启动的,但因为守护进程是后台运行的,所以登录会话的终止并不影响守护进程。如果其他用户在同一终端设备上登录,我们也不会见到守护进程的输出,用户也不希望他们在终端上的输入会由守护进程读取。
注:某些时候可能需要处理SIGCHLD信号:
虽然处理SIGCHLD并不是必须的,但是对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程 的负担,影响服务进程的并发性能。
在Linux下可以简单的将SIGCHLD信号的操作设为SIG_IGN。
signal(SIGCHLD, SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程。
转:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/param.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #include <signal.h> void my_daemon() { int pid, fd; // 1.转变为后台进程 if ((pid = fork()) == -1) exit(1); if (pid != 0) exit(0); // 父进程(前台进程)退出 // 2.离开原先的进程组,会话 if (setsid() == -1) exit(1); // 开启一个新会话 // 3.禁止再次打开控制终端 if ((pid = fork()) == -1) exit(1); if (pid != 0) exit(0); // 父进程(会话领头进程)退出 // 4.关闭打开的文件描述符,避免浪费系统资源 for (int i = 0; i < NOFILE; i++) close(i); // 5.改变当前的工作目录,避免卸载不了文件系统 if (chdir("/") == -1) exit(1); // 6.重设文件掩码,防止某些属性被父进程屏蔽 if (umask(0) == -1) exit(1); // 7.重定向标准输入,输出,错误流,因为守护进程没有控制终端 if ((fd = open("/dev/null", O_RDWR)) == -1) exit(1); // 打开一个指向/dev/null的文件描述符 dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); close(fd); // 8.本守护进程的子进程若不需要返回信息,那么交给init进程回收,避免产生僵尸进程 if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) exit(1); } #define INTERVAL 2 int main(int argc, char *argv[]) { my_daemon(); // 首先使之成为守护进程 int t = 0; FILE *fp = fopen("/root/tmp.txt", "a"); fprintf(fp, "ppid = %d, pid = %d, sid = %d, pgrp = %d\n", getppid(), getpid(), getsid(0), getpgrp()); fflush(fp); do { // 测试此后台进程,每INTERVAL秒打印当前时间t,30秒后退出此后台进程 fprintf(fp, "%d\n", t); fflush(fp); // 输出缓冲区内容到文件中 sleep(INTERVAL); t += INTERVAL; } while(t < 30); fclose(fp); return 0; } 保存为daemon.c 编译命令 gcc daemon.c 运行 ./a.out 查看tmp.txt文件内容 cat /root/tmp.txt
三、出错记录
由于守护进程没有控制终端,所以需要有一个集中的守护进程出错记录设施。
四、守护进程惯例
在UNIX系统中,守护进程遵循以下公共惯例:
1、若守护进程使用锁文件,那么该文件通常存放在/var/run目录中,注意,守护进程可能需要具有超级用户权限才能在此目录下创建文件。锁文件的名字通常是name.pid,其中,name是该守护进程或服务的名字。例如cron守护进程锁文件的的名字是/var/run/cron.pid
2、若守护进程支持配置选项,那么配置文件通常存放在/etc目录中。配置文件的名字通常是name.conf,其中,name是该守护进程或服务的名字。syslogd守护进程的配置文件是/etc/syslog.conf
3、守护进程可用命令行启动,但通常它们是由系统初始化脚本之一(/etc/rc* 或 /etc/init.d/* )启动的。如果在守护进程终止时,应当自动重启它,则我们可以再/etc/inittab中为该守护进程包括_respawn记录项,这样,init就将重启该守护进程。
4、若一个守护进程有一个配置文件,那么当该守护进程启动时,它读该文件,但在此之后一般不会再查看它。