一、常用的守护进程简单介绍:
- Keventd:为在内核中运行计划执行的函数提供进程上下文。
- kapmd:高级电源管理。
- kswapd:页面调出。(将脏页面以低速写到磁盘上从而使这些页面在需要时仍可回收使用)
- bdflush:将脏缓冲区从缓存池中冲洗到磁盘上。
- kupdated:每个一定时间间隔,这个守护进程将脏页面冲洗到磁盘上。
- portmap:端口映射提供将RPC程序号映射为网络端口号。
- syslogd:把系统消息记入日志。
- inetd:监听网络接口,以便取得来自网络的对各种网络服务进程的请求。
- cron:在指定的日期和时间执行指定的命令。
二、守护进程的编程规则
1. 重设文件创建掩模
进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);
2.在后头运行
调用fork,然后使父进程终止。这样做的目的是:第一,如果该守护进程是作为一条简单shell命令启动的,那么父进程终止使得shell认为这条命令已经执行完毕,那么子进程就自动在后台运行了。第二,子进程继承了父进程的进程组ID,但具有一个新的进程ID,这就保证了进程不是一个进程组的组长进程。这是接下去调用setsid的必要条件。
3.脱离控制终端,登录会话和进程组
调用setsid以创建一个新会话。使调用进程:(a)成为新会话的首进程,(b)成为一个新进程组的组长进程,(c)没有控制终端。
有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。
控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第2点的基础上,调用setsid()使进程成为会话组长。
说明:当进程是会话组长时setsid()调用失败。但第二点已经保证进程不是会话组长。setsid() 调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
4. 禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:
if(pid=fork())
exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
5. 改变当前工作目录
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。
chdir("/");
6.关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。
7.将文件描述符0、1、2重定向到/dev/null
#include <stdlib.h>
#include <stdio.h>
#include <syslog.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <errno.h>
#include <sys/stat.h>
#include <string.h>
#define LOCKDIR "/var/run/"
#define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
int daemonize(const char *cmd);
int already_running(const char *cmd);
int lockfile(int);
int main(int argc ,char **argv)
{
FILE *f;
time_t t;
if(daemonize(argv[0]) < 0){
printf("daemonize error.\n");
exit(1);
}
/* make sure only one cope of the daemon is running */
if(already_running(argv[0])){
syslog(LOG_ERR,"daemon already running");
exit(1);
}
while(1){
sleep(60);
if((f = fopen("test.log","a")) >= 0){
t=time(0);
fprintf(f,"I'm here at %s\n",asctime(localtime(&t)));
}
fclose(f);
}
return 0;
}
int daemonize(const char *cmd)
{
int i,fd0,fd1,fd2;
pid_t pid;
struct rlimit rl;
struct sigaction sa;
/* clear file creation mask */
umask(0);
/* get maximum number of file descriptors */
if(getrlimit(RLIMIT_NOFILE, &rl) < 0 )
exit(1);
/* become a session leader to lose controlling tty */
if((pid = fork()) < 0)
return -1;
else if(pid != 0 )
exit(0);
setsid();
/*ensure future opens won't allocate controlling TTYs */
sa.sa_handler=SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags=0;
if(sigaction(SIGHUP,&sa,NULL) < 0 )
return -1;
if((pid = fork()) < 0 )
return -1;
else if(pid != 0 )
exit(0);
/* change the current working directory to the root */
if(chdir("/") < 0 )
return -1;
/* close all open file descriptors */
if(rl.rlim_max == RLIM_INFINITY)
rl.rlim_max=1024;
for(i=0; i < rl.rlim_max; i++)
close(i);
/* attach file descriptors 0,1,2 to /dev/null */
fd0=open("/dev/null",O_RDONLY);
fd1=open("/dev/null",O_RDWR);
fd2=open("/dev/null",O_RDWR);
/* initialize the log file */
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);
}
return 0;
}
int lockfile(int fd)
{
struct flock fl;
fl.l_type=F_WRLCK;
fl.l_start=0;
fl.l_whence=SEEK_SET;
fl.l_len=0;
return (fcntl(fd,F_SETLK,&fl));
}
int already_running(const char *cmd)
{
int fd;
char buf[16];
char lfile[32];
strcat(lfile,LOCKDIR);
strcat(lfile,cmd);
strcat(lfile,".pid");
if((fd = open(lfile, O_RDWR|O_CREAT, LOCKMODE)) < 0 ){
syslog(LOG_ERR, "can't open %s: %m",lfile);
exit(1);
}
if(lockfile(fd) < 0 ){
if(errno == EACCES || errno == EAGAIN){
close(fd);
return 1;
}
syslog(LOG_ERR, "can't lock %s: %m",lfile);
exit(1);
}
ftruncate(fd,0);
sprintf(buf, "%ld",(long)getpid());
write(fd,buf,strlen(buf)+1);
return 0;
}