【转载】对守护进程的一点理解

守护进程的相关知识Linux C编程实战上只简略地用了2页书的内容就过了,但是根据我的实际情况和一些了解,我认为无论是从它的重要性,还是我自己的理解程度来说,书上的2页可是远 远不够啊!于是乎,在搜罗了一些资料以后,我觉得还是写篇博客总结归纳一下比较好,也比较有助于以后的学习!


在介绍守护进程之前,我们首先了解一下以下几个概念
0.终端(terminal):每一个 用户 与 系统 进行交流的界面.(也就是在linux中按 ctrl+alt+t 弹出的字符界面那个框框)
1.进程组:是一个或多个进程的集合.(以进程为最小组成单位的集合)
2.组长进程:进程组中 进程号 等于 进程组ID 的进程. 组长进程的退出不影响进程组ID !!
3.会话期:是一个或多个进程组的集合.(以进程组为最小单位的集合,注意与进程组的概念相结合理解)


零.什么是守护进程
守护进程,也叫Daemon进程,它是Linux系统中的一种后台服务进程,通常 独立于控制终端并且周期性的执行某种任务或者等待处理某些发生的事件.
类似于Windows中的系统服务,也就是在桌面和任务栏看不到,但是打开任务管理器中又显示正在运行的程序.(在linux中可以在终端输入ps -ef查看)


一.为何创建守护进程
在Linux中,有一些需要自 开机启动系统至关机关闭系统一直运行 或 收到特定指令才关闭,以此来完成某些任务的程序,这些程序就是守护进程.
说白了,创建守护进程就是为了系统能正常的完成某些任务,但是又不占用多余的资源.


二.如何创建守护进程
0.创建子进程,退出父进程
首先,守护进程是脱离终端的(因为它要在后台运行,操作系统有那么多守护进程,不可能从开机到关机一直有一个或多个终端处于开启状态吧!).所以创建的时候 必须是当前进程的子进程(如果是父进程的话,退出终端程序就结束了),而且必须保证 只有子进程运行.
所以具体代码如下:

点击(此处)折叠或打开

  1. pid = fork();
  2. if (pid > 0) //退出父进程,使得子进程成为后台进程
  3.     exit(0);
  4. else if (pid < 0) //创建失败,返回-1
  5.     return -1;



此时,该子进程就成为一个孤儿进程,由1号进程(init)收养,它就成为init进程的子进程.


1.在子进程中创建新会话
虽然第一步已经使子进程脱离的父进程,但是创建子进程时调用了fork函数,子进程全盘拷贝了父进程的会话期,进程组,控制终端等,虽然父进程退出了,但 是会话期,进程组等并未改变,因此子进程并没有真正的独立!所以现在要使用setsid来使进程完全独立,摆脱其他进程的控制.
setsid函数用于创建一个新会话,并担任该会话组的组长,具体作用有一下几个方面:
0).让进程摆脱原会话的控制;
1).让进程摆脱原进程组的控制;
2).让进程摆脱原控制终端的控制.
(关于以上功能如何实现,笔者未做深入了解)
实现代码:
setsid();   //建立新的进程组,使该子进程成为进程组的首进程,从而脱离所有终端
注意:当进程是会话组长时,调用setsid会失败,但是第0步已经保证该进程不会是会话组长(因为该进程的父进程已是会话组长),调用setsid后,该进程会成为新的会话组长和进程组长,并与原来的会话组的进程组脱离.


2.禁止进程重新打开控制终端
经过以上步骤,该进程已经成为一个无终端的会话组长,但是该进程仅仅只是脱离了父进程运行的那个控制终端,它自己还可以打开另外的终端,为避免这种情况,可以使该进程不再是会话组长来实现,再次使用fork创建子进程,并使调用fork的进程退出.
实现代码同第1步


3.改变当前目录为根目录
同样的,使用fork创建子进程的时候,子进程拷贝了父进程的工作目录,但是守护进程工作时,应该在一个相对较稳定的目录下运行,不然若是该目录被卸载,守护进程就不能正常工作了!(比如在使用Windows的时候,强行把C盘里的系统文件删除一些,下次再进入系统的时候还能正常进入吗?而且貌似系统文件是删不掉的!)
实现代码:
chdir("/"); //改变工作目录,使进程不与任何文件系统联系 (当然也可以改变为其他一些比较稳定的目录,不一定非是根目录)


4.重设文件屏蔽字
由于使用fork,子进程拷贝了父进程的文件屏蔽字,这给子进程的使用带来的诸多麻烦.所以,把该子进程的文件屏蔽字设为0可以大大增强该守护进程的灵活性.
实现代码:
umask(0); //将文件屏蔽字设置为0


5.关闭文件描述符 (这一步可以通俗的称为:父进程摆下的烂摊子,子进程来收拾!呵呵)
同屏蔽字一样,子进程从父进程那里继承了一些已经打开的文件,这些文件可能永远不会被子进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸载.
由于第2步已经使守护进程脱离了终端,所以该守护进程的一些需要终端来完成的功能就完成不了了,比如用printf()输出一行字符,已经不能实现了!所以文件描述符为0,1和2的三个文件(输入,输出和报错)已经失去价值了,应该被关闭!
实现代码:
for (i = 0; i < NOFILE; close(i++))


6.守护进程的退出处理
前面的步骤已经基本实现了守护进程的功能,但只是我们 需要守护进程的时候 那样做,如果我们需要把守护进程结束应该怎么办呢?
此时,我们就需要使用kill命令停止守护进程.
实现代码:

点击(此处)折叠或打开

  1. signal(SIGTERM, sigterm_handler);


  2. void sigterm_handler(int arg)
  3. {
  4.        _running = 0;
  5. }





三.完整代码实现daemon进程
综合前面的所有步骤,我们就可以得到一个简单的守护进程:(功能为每隔10秒在系统日志中写入当前的系统时间)

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <sys/param.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. #include <time.h>
  7. #include <syslog.h>
  8. #include <sys/stat.h>

  9. volatile sig_atomic_t _running = 1;

  10. void sigterm_handler(int arg)
  11. {
  12.     _running = 0;
  13. }

  14. int init_daemon(void)
  15. {
  16.     pid_t pid;
  17.     int i;

  18.     pid = fork();
  19.     if (pid > 0)
  20.         exit(0);
  21.     else if (pid < 0)
  22.         return -1;         //第一步

  23.     setsid();             //第二步

  24.     pid = fork();
  25.     if (pid > 0)
  26.         exit(0);
  27.     else if (pid < 0)
  28.         return -1;         //第三步

  29.     chdir("/");             //第四步

  30.     umask(0);             //第五步

  31.     for (i = 0; i < NOFILE; close(i++));//第六步

  32.     return 0;
  33. }

  34. int main()
  35. {
  36.     time_t now;
  37.     init_daemon();

  38.     syslog(LOG_USER | LOG_INFO, "测试守护进程!\n");
  39.         
  40.     signal(SIGTERM, sigterm_handler); //第七步

  41.     while(_running)
  42.         {
  43.         sleep(10);
  44.         time(&now);
  45.         syslog(LOG_USER | LOG_INFO, "系统时间: \t%s\t\t\n", ctime(&now));
  46.     }

  47.     return 0;
  48. }

 

posted @ 2013-08-05 19:44  摩斯电码  阅读(414)  评论(0编辑  收藏  举报