《Unix环境高级编程》:单实例守护进程的实现

来源:互联网 发布:diy单片机与红外线 编辑:程序博客网 时间:2024/05/17 01:23

//《APUE》程序13-1:初始化一个守护进程
//《APUE》程序13-2:保证只运行某个守护进程的一个副本
//《APUE》程序14-5:在文件整体加锁

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <sys/resource.h>
#include <sys/syslog.h>
#include <sys/file.h>
#include <sys/stat.h>

//创建锁文件的路径
#define LOCKFILE "/var/run/daemon.pid"

//锁文件的打开模式
#define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

//输出错误信息并退出 
void error_quit(const char *str) 

 fprintf(stderr, "%s\n", str); 
 exit(1); 
}

//对文件fd加上记录锁
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);
}

//若程序已经运行,则返回1,否则返回0
int already_running(void)
{
 int fd;
 char buf[16];

 //打开放置记录锁的文件
 fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE);
 if( fd < 0 )
 {
  syslog(LOG_ERR, "can't open %s: %s", LOCKFILE, strerror(errno));
  exit(1);
 }
 //试图对文件fd加锁,
 //如果加锁失败的话

 if( lockfile(fd) < 0 )
 {
  //如果是因为权限不够或资源暂时不可用,则返回1
  if( EACCES == errno ||
   EAGAIN == errno )
  {
   close(fd);
   return 1;
  }
  //否则,程序出错,写入一条错误记录后直接退出
  syslog(LOG_ERR, "can't lock %s: %s", LOCKFILE, strerror(errno));
  exit(1);
 }

 //先将文件fd清空,然后再向其中写入当前的进程号
 ftruncate(fd, 0);
 sprintf(buf, "%ld", (long)getpid());
 write(fd, buf, strlen(buf)+1);
 return 0;
}

//将一个进程变为守护进程
void daemonize(void)
{
 int i, fd0, fd1, fd2;
 pid_t pid;
 struct rlimit rl;
 struct sigaction sa;

 //见注解1
 umask(0);

 //获取最大的文件描述号
 int temp;
 temp = getrlimit(RLIMIT_NOFILE, &rl);

 if( temp < 0 )
  error_quit("can't get file limit");

 //见注解2,
 pid = fork();
 if( pid < 0 )
  error_quit("can't fork");
 else if(pid != 0)
  exit(0);

 //见注解3
 setsid();
 sa.sa_handler = SIG_IGN;
 sigemptyset(&sa.sa_mask);
 sa.sa_flags = 0;
 temp = sigaction(SIGHUP, &sa, NULL);
 if( temp < 0 )
  error_quit("can't ignore SIGHUP");

 ////确保子进程不会有机会分配到一个控制终端
 pid = fork();
 if( pid < 0 )
  error_quit("can't fork");
 else if(pid != 0)
  exit(0);

 //见注解4
 temp = chdir("/");
 if( temp < 0 )
  error_quit("can't change directoy to /");

 //见注解5
 if( rl.rlim_max == RLIM_INFINITY )
  rl.rlim_max = 1024;
 for(i=0; i<rl.rlim_max; i++)
  close(i);

 //见注解6
 fd0 = open("/dev/null", O_RDWR);
 fd1 = dup(0);
 fd2 = dup(0);

 if( fd0 != 0 ||
  fd1 != 1 ||
  fd2 != 2 )
 {
  syslog(LOG_ERR, "unexpected file descriptors %d %d %d",
   fd0, fd1, fd2);
  exit(1);
 }
}

//该主函数是我原创的,呵呵
int main(void)
{
 //打开系统的日志文件
 openlog("my test log: ", LOG_CONS, LOG_DAEMON);
 daemonize();

 //如果程序已经运行,则向记录文件中写入一句话,然后退出
 if( already_running() )
 {
  syslog(LOG_ERR, "daemon alread running");
  closelog();
  return 1;
 }

 //向日志文件写入程序的开始(当前)时间,
 //过100秒后,再向记录文件写入结束时间,然后结束程序
 time_t tt = time(0);
 syslog(LOG_INFO, "the log program start at: %s",
  asctime(localtime(&tt)) );
 sleep(100);
 //pause()
 tt = time(0);
 syslog(LOG_INFO, "the log program end at: %s",
  asctime(localtime(&tt)) );

 //关闭日志文件
 //虽然不关也没事,但为了和openlog配对,还是将它写上去吧
 closelog();
 return 0;
}

0 0
原创粉丝点击