linux Daemon(转载)

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

 

建立守护进程的通常步骤linux

 

(1) 建立子进程,退出父进程shell

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

ps –ef | grep ProcName          经过PID/PPID查看进程的父子关系服务器

 

(2) 在子进程中建立新的会话session

使用系统函数setsid来完成。并发

man 2 setsid    查看关于setsid函数的说明less

setsid – creates a session and sets theprocess group ID函数

#include <unistd.h>oop

pid_t setsid(void);

setsid() creates a new session if thecalling process is not a process group leader. The calling process is theleader of the new session, the process group leader of the new process group,and has no controlling tty. The process group ID and session ID of the callingprocess are set to the PID of the calling process. The calling process will bethe only process in this new process group and in this new session.

进程组:是一个或多个进程的集合。进程组有进程组ID来惟一标识。除了进程号PID以外,进程组ID也是一个进程的必备属性。每一个进程组都有一个组长进程,其组长进程的进程号等于进程组ID,且该进程组ID不会因组长进程的退出而受到影响。

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

(a) 让进程摆脱原会话的控制;

(b) 让进程摆脱原进程组的控制;

(c) 让进程摆脱原控制终端的控制;

使用setsid函数的目的:因为建立守护进程的第一步调用了fork函数来建立子进程再将父进程退出。因为在调用fork函数时,子进程拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并无改变,所以,这还不是真正意义上的独立开了。使用setsid函数后,可以使进程彻底独立出来,从而摆脱其余进程的控制。

 介绍一下Linux中的进程与控制终端,登陆会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号 (PID)。登陆会话能够包含多个进程组。这些进程组共享一个控制终端。这个控制终端一般是建立进程的登陆终端。 控制终端,登陆会话和进程组一般是从父进程继承下来的。咱们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使 进程成为会话组长.当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登陆会话和进程组脱离。因为会话过程对控制终端的独占性,进程同时与控制终端脱离。

(3) 改变当前目录为根目录

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

 

(4) 重设文件权限掩码

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

 

(5) 关闭文件描述符

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

 

(6) 守护进程退出处理

当用户须要外部中止守护进程时,一般使用kill命令中止该守护进程。因此,守护进程中须要编码来实现kill发出的signal信号处理,达到进程正常退出。

 这篇文章主要介绍了C语言编写Linux守护进程实例,本文讲解了守护进程及其特性、守护进程的编程要点、守护进程代码实例等内容,须要的朋友能够参考下

守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端而且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种颇有用的进 程。Linux的大多数服务器就是用守护进程实现的。好比,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任 务。好比,做业规划进程crond,打印进程lpd等。

守护进程的编程自己并不复杂,复杂的是各类版本的Unix的实现机制不尽相同,形成不一样Unix环境下守护进程的编程规则并不一致。这须要读者注意,照搬 某些书上的规则(特别是BSD4.3和低版本的System V)到Linux会出现错误的。下面将全面介绍Linux下守护进程的编程要点并给出详细实例。

一. 守护进程及其特性

守护进程最重要的特性是后台运行。在这一点上DOS下的常驻内存程序TSR与之类似。其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的 文件描述符,控制终端,会话和进程组,工做目录以及文件建立掩模等。这些环境一般是守护进程从执行它的父进程(特别是shell)中继承下来的。最后,守 护进程的启动方式有其特殊之处。它能够在Linux系统启动时从启动脚本/etc/rc.d中启动,能够由做业规划进程crond启动,还能够由用户终端 (一般是shell)执行。

总之,除开这些特殊性之外,守护进程与普通进程基本上没有什么区别。所以,编写守护进程其实是把一个普通进程按照上述的守护进程的特性改形成为守护进程。若是读者对进程有比较深刻的认识就更容易理解和编程了。

二. 守护进程的编程要点

前面讲过,不一样Unix环境下守护进程的编程规则并不一致。所幸的是守护进程的编程原则其实都同样,区别在于具体的实现细节不一样。这个原则就是要知足守护 进程的特性。同时,Linux是基于Syetem V的SVR4并遵循Posix标准,实现起来与BSD4相比更方便。编程要点以下;

1. 在后台运行。

为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。 
复制代码 代码以下:
if(pid=fork())

exit(0);//是父进程,结束父进程,子进程继续 

2. 脱离控制终端,登陆会话和进程组

有必要先介绍一下Linux中的进程与控制终端,登陆会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登陆会话能够包含多个进程组。这些进程组共享一个控制终端。这个控制终端一般是建立进程的登陆终端。

控制终端,登陆会话和进程组一般是从父进程继承下来的。咱们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长: 
setsid();

说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登陆会话和进程组脱离。因为会话过程对控制终端的独占性,进程同时与控制终端脱离。

3. 禁止进程从新打开控制终端

如今,进程已经成为无终端的会话组长。但它能够从新申请打开一个控制终端。能够经过使进程再也不成为会话组长来禁止进程从新打开控制终端: 
复制代码 代码以下:
if(pid=fork())

exit(0);//结束第一子进程,第二子进程继续(第二子进程再也不是会话组长) 

4. 关闭打开的文件描述符

进程从建立它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,形成进程所在的文件系统没法卸下以及引发没法预料的错误。按以下方法关闭它们:

复制代码 代码以下:
for(i=0;i 关闭打开的文件描述符close(i);> 

5. 改变当前工做目录

进程活动时,其工做目录所在的文件系统不能卸下。通常须要将工做目录改变到根目录。对于须要转储核心,写运行日志的进程将工做目录改变到特定目录如/tmpchdir(“/”)

6. 重设文件建立掩模

进程从建立它的父进程那里继承了文件建立掩模。它可能修改守护进程所建立的文件的存取位。为防止这一点,将文件建立掩模清除:umask(0);

7. 处理SIGCHLD信号

处理SIGCHLD信号并非必须的。但对于某些进程,特别是服务器进程每每在请求到来时生成子进程处理请求。若是父进程不等待子进程结束,子进程将成为 僵尸进程(zombie)从而占用系统资源。若是父进程等待子进程结束,将增长父进程的负担,影响服务器进程的并发性能。在Linux下能够简单地将 SIGCHLD信号的操做设为SIG_IGN。

signal(SIGCHLD,SIG_IGN);

这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不一样,BSD4下必须显式等待子进程结束才能释放僵尸进程。

下面是一个简单的实现:

 

[cpp]  view plain  copy
 
 print?在CODE上查看代码片派生到个人代码片
  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<string.h>  
  4. #include<fcntl.h>// open  
  5. #include<sys/types.h>  
  6. #include<sys/stat.h>  
  7. #include<unistd.h>  
  8. #include<sys/wait.h>  
  9. #include<signal.h>  
  10.   
  11. #define MAXFILE 65535  
  12.   
  13. volatile sig_atomic_t _running = 1;  
  14. int fd;  
  15.   
  16. // signal handler  
  17. void sigterm_handler(int arg)  
  18. {  
  19.     _running = 0;  
  20. }  
  21.   
  22. int main()  
  23. {  
  24.     pid_t pid;  
  25.     char *buf = "This is a Daemon, wcdj\n";  
  26.   
  27.     /* 屏蔽一些有关控制终端操做的信号 
  28.      * 防止在守护进程没有正常运转起来时,因控制终端受到干扰退出或挂起 
  29.      * */  
  30.     signal(SIGINT,  SIG_IGN);// 终端中断  
  31.     signal(SIGHUP,  SIG_IGN);// 链接挂断  
  32.     signal(SIGQUIT, SIG_IGN);// 终端退出  
  33.     signal(SIGPIPE, SIG_IGN);// 向无读进程的管道写数据  
  34.     signal(SIGTTOU, SIG_IGN);// 后台程序尝试写操做  
  35.     signal(SIGTTIN, SIG_IGN);// 后台程序尝试读操做  
  36.     signal(SIGTERM, SIG_IGN);// 终止  
  37.   
  38.     // test  
  39.     //sleep(20);// try cmd: ./test &; kill -s SIGTERM PID  
  40.   
  41.   
  42.     // [1] fork child process and exit father process  
  43.     pid = fork();  
  44.     if(pid < 0)  
  45.     {  
  46.         perror("fork error!");  
  47.         exit(1);  
  48.     }  
  49.     else if(pid > 0)  
  50.     {  
  51.         exit(0);  
  52.     }  
  53.   
  54.     // [2] create a new session  
  55.     setsid();  
  56.   
  57.     // [3] set current path  
  58.     char szPath[1024];  
  59.     if(getcwd(szPath, sizeof(szPath)) == NULL)  
  60.     {  
  61.         perror("getcwd");  
  62.         exit(1);  
  63.     }  
  64.     else  
  65.     {  
  66.         chdir(szPath);  
  67.         printf("set current path succ [%s]\n", szPath);  
  68.     }  
  69.   
  70.     // [4] umask 0  
  71.     umask(0);  
  72.   
  73.     // [5] close useless fd  
  74.     int i;  
  75.     //for (i = 0; i < MAXFILE; ++i)  
  76.     for (i = 3; i < MAXFILE; ++i)  
  77.     {  
  78.         close(i);  
  79.     }  
  80.   
  81.     // [6] set termianl signal  
  82.     signal(SIGTERM, sigterm_handler);  
  83.   
  84.     // open file and set rw limit  
  85.     if((fd = open("outfile", O_CREAT|O_WRONLY|O_APPEND, 0600)) < 0)  
  86.     {  
  87.         perror("open");  
  88.         exit(1);  
  89.     }  
  90.   
  91.     printf("\nDaemon begin to work..., and use kill -9 PID to terminate\n");  
  92.   
  93.     // do sth in loop  
  94.     while(_running)  
  95.     {  
  96.         if (write(fd, buf, strlen(buf)) != strlen(buf))  
  97.         {  
  98.             perror("write");  
  99.             close(fd);  
  100.             exit(1);  
  101.         }  
  102.   
  103.         usleep(1000*1000);// 1 s  
  104.     }  
  105.     close(fd);  
  106.   
  107.   
  108.     // print data  
  109.     if((fd = open("outfile", O_RDONLY)) < 0)  
  110.     {  
  111.         perror("open");  
  112.         exit(1);  
  113.     }  
  114.     char szBuf[1024] = {0};  
  115.     if(read(fd, szBuf, sizeof(szBuf)) == -1)  
  116.     {  
  117.         perror("read");  
  118.         exit(1);  
  119.     }  
  120.     printf("read 1024 bytes:\n%s\n", szBuf);  
  121.   
  122.     close(fd);  
  123.   
  124.     return 0;  
  125. }  
  126.   
  127. /* 
  128.    gcc -Wall -g -o test test.c 
  129.    ps ux | grep -v grep | grep test 
  130.    tail -f outfile 
  131.    kill -s SIGTERM PID 
  132.  */  



 

参考:

[1] 守护进程
[2] 编写Linux/Unix守护进程
[3] Linux 信号signal处理函数
[4] The usage of sig_atomic_t in linux signal mask function

相关文章
相关标签/搜索