1 守护进程是什么?

在Linux/UNIX系统引导的时候会开启很多服务,这些服务称为守护进程(Daemon进程)。守护进程是脱离于控制终端并且在后台周期性地执行某种任务或等待处理某些事件的进程,脱离终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的中断信息所终止。

2 创建守护进程步

2.1 创建子进程,退出父进程

为了脱离控制终端需要退出父进程,之后的工作都由子进程完成。在Linux中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程时,就会自动由1号进程(init)收养它,这样,原先的子进程就会变成init进程的子进程。

2.2 在子进程中创建新的会话

setsid函数作用:用于创建一个新的会话,并担任该会话组的组长。调用setsid有3个作用

  1. 让进程摆脱原会话的控制;
  2. 让进程摆脱原进程组的控制;
  3. 让进程摆脱原控制终端的控制;

使用setsid函数的目的:由于创建守护进程的第一步调用了fork函数来创建子进程再将父进程退出。由于在调用fork函数时,子进程拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,这还不是真正意义上的独立开了。使用setsid函数后,能够使进程完全独立出来,从而摆脱其他进程的控制。

2.3 改变当前目录为根目录

使用fork创建的子进程继承了父进程的当前的工作目录。由于在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后的使用会造成诸多的麻烦。因此,通常的做法是让根目录”/”作为守护进程的当前工作目录。这样就可以避免上述的问题。如有特殊的需求,也可以把当前工作目录换成其他的路径。改变工作目录的方法是使用chdir函数。

2.4 重设文件权限掩码

文件权限掩码:是指屏蔽掉文件权限中的对应位。例如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限(对应二进制为,rwx, 101)。由于fork函数创建的子进程继承了父进程的文件权限掩码,这就给子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0(即,不屏蔽任何权限),可以增强该守护进程的灵活性。设置文件权限掩码的函数是umask。通常的使用方法为umask(0)。

2.5 关闭文件描述符

用fork创建的子进程也会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸载。在使用setsid调用之后,守护进程已经与所属的控制终端失去了联系,因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1、2(即,标准输入、标准输出、标准错误输出)的三个文件已经失去了存在的价值,也应该关闭。

2.6 守护进程退出处理

当用户需要外部停止守护进程时,通常使用kill命令停止该守护进程。所以,守护进程中需要编码来实现kill发出的signal信号处理,达到进程正常退出。

3 demo

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>// open
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>

#define MAXFILE 65535

volatile sig_atomic_t _running = 1;
int fd;

// signal handler
void sigterm_handler(int arg)
{
 _running = 0;
}

int main()
{
 pid_t pid;
 char *buf = "This is a Daemon, wcdj\n";

 /* 屏蔽一些有关控制终端操作的信号
  * 防止在守护进程没有正常运转起来时,因控制终端受到干扰退出或挂起
  * */
 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);// 终止

 // test
 //sleep(20);// try cmd: ./test &; kill -s SIGTERM PID


 // [1] fork child process and exit father process
 pid = fork();
 if(pid < 0)
 {
  perror("fork error!");
  exit(1);
 }
 else if(pid > 0)
 {
  exit(0);
 }

 // [2] create a new session
 setsid();

 // [3] set current path
 char szPath[1024];
 if(getcwd(szPath, sizeof(szPath)) == NULL)
 {
  perror("getcwd");
  exit(1);
 }
 else
 {
  chdir(szPath);
  printf("set current path succ [%s]\n", szPath);
 }

 // [4] umask 0
 umask(0);

 // [5] close useless fd
 int i;
 //for (i = 0; i < MAXFILE; ++i)
 for (i = 3; i < MAXFILE; ++i)
 {
  close(i);
 }

 // [6] set termianl signal
 signal(SIGTERM, sigterm_handler);

 // open file and set rw limit
 if((fd = open("outfile", O_CREAT|O_WRONLY|O_APPEND, 0600)) < 0)
 {
  perror("open");
  exit(1);
 }

 printf("\nDaemon begin to work..., and use kill -9 PID to terminate\n");

 // do sth in loop
 while(_running)
 {
  if (write(fd, buf, strlen(buf)) != strlen(buf))
  {
   perror("write");
   close(fd);
   exit(1);
  }

  usleep(1000*1000);// 1 s
 }
 close(fd);


 // print data
 if((fd = open("outfile", O_RDONLY)) < 0)
 {
  perror("open");
  exit(1);
 }
 char szBuf[1024] = {0};
 if(read(fd, szBuf, sizeof(szBuf)) == -1)
 {
  perror("read");
  exit(1);
 }
 printf("read 1024 bytes:\n%s\n", szBuf);

 close(fd);

 return 0;
}

/*
   gcc -Wall -g -o test test.c
   ps ux | grep -v grep | grep test
   tail -f outfile
   kill -s SIGTERM PID
 */

 

登录发表评论 注册

反馈意见