linux C 守护进程及其创建

来源:互联网 发布:制图软件 编辑:程序博客网 时间:2024/05/06 14:55
涉及的概念:
守护进程:在linux或者unix操作系统中在系统引导的时候会开启很多服务,这些服务就叫做守护进程。为了增加灵活性,root可以选择系统开启的模式,这些模式叫做运行级别,每一种运行级别以一定的方式配置系统。守护进程是脱离于终端并且在后台运行的进程。守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导装入时启动,在系统关闭时终止。Linux系统有很多守护进程,大多数服务都是通过守护进程实现的,同时,守护进程还能完成许多系统任务,例如,作业规划进程crond、打印进程lqd等(这里的结尾字母d就是Daemon的意思)。由于在Linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才退出。如果想让某个进程不因为用户或终端或其他地变化而受到影响,那么就必须把这个进程变成一个守护进程。
进程组:是一个或多个进程的集合。进程组有进程组ID来唯一标识。除了进程号(PID)之外,进程组ID也是一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID。且该进程组ID不会因组长进程的退出而受到影响。
会话周期:会话期是一个或多个进程组的集合。通常,一个会话开始与用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。

创建守护进程的步骤:
1、让程序在后台运行,创建子进程,父进程退出

   由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成一程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离。在Linux中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程时,就会自动由init进程收养,这样,原先的子进程就会变成init进程的子进程。
2、在子进程中创建新会话,即调用setsid创建一个新的对话期
      首先具体介绍setsid的相关内容:
      (1)setsid函数作用:

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

       让进程摆脱原会话的控制

       让进程摆脱原进程组的控制

  让进程摆脱原控制终端的控制

那么,在创建守护进程时为什么要调用setsid函数呢?由于创建守护进程的第一步调用了fork函数来创建子进程,再将父进程退出。由于在调用了fork函数时,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,还还不是真正意义上的独立开来,而setsid函数能够使进程完全独立出来,函数调用成功后,进程成为新的对话组长和进程组长,并与原来的登陆对话和进程组脱离,由于对话进程对控制终端的独占性,进程与控制终端脱离。

3、禁止进程重新打开控制终端。

    以上步骤后,进程已经成为一个无终端的会话组长,但是它可以重新申请打开一个终端。为了避免这种情况的发生,可以通过使进程不再是会话组长来实现。再通过调用fork函数创建子进程,使调用fork函数的进程退出。

4、关闭不再需要的文件描述符。

    创建的子进程从父进程继承无法打开的文件描述符。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源。如不关闭将会浪费系统资源,造成系统所在的文件系统无法卸下以及无法预料的错误。先得到最高的文件文件描述符值,然后循环关闭0到最高文件描述值的所有文件描述符。

如下:

for(i=0;i<MAXFILE;close(i++)){      ;}

5、改变当前目录为根目录

    使用fork创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当守护进程当前工作目录是在一个装配文件系统中时,(如“/mnt/usb”)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入用户模式)。因此,通常的做法是让"/"作为守护进程的当前工作目录,这样就可以避免上述的问题,当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数式chdir。
6、重设文件权限掩码,将文件创建时使用的屏蔽字设置为0。
   文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。由于使用fork函数新建的子进程继承了父进程的文件权限掩码,可能会拒绝某些许可权。因此,把文件权限掩码设置为0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是umask。在这里,通常的使用方法为umask(0)。
7、处理SIGCHLD信号。
    这一步并不是必须的,但对于某些进程,特别是服务器进程往往是在请求到来时生成子进程处理请求。如父进程不等待子进程结束,子进程将成为僵尸进程,从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器性能的并发性能。在Linux下可以简单的将SIGCHLD信号的操作设为SIG_IGN。这样,子进程结束时不会产生僵尸进程。
  

这样,一个简单的守护进程就建立起来了。                                     

   ps:参考《linux C 编程实战》
实例:

=====================================================================  /* * ===================================================================================== * *       Filename:  daemon2.c * *    Description:   * *        Version:  1.0 *        Created:  2013年07月24日 10时21分31秒 *       Revision:  none *       Compiler:  gcc * *         Author:  gaoyuan,  *        Company:  Class 1204 of Computer Science and Technology * * ===================================================================================== */#include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <sys/types.h>#include <unistd.h>#include <sys/wait.h>#include <syslog.h>#define MAXFILE 65535int main(int argc, char * argv[]){pid_t pc;int i, fd, len;/*忽略终端I/O信号,STOP信号*/signal(SIGTTOU, SIG_IGN);signal(SIGTTIN, SIG_IGN);signal(SIGTSTP, SIG_IGN);signal(SIGHUP, SIG_IGN);pc = fork(); if (pc < 0){printf("error fork\n");exit(1);}else if(pc > 0){exit (0);    //结束父进程,使子子进程成为后台进程}/*新建一个进程组,在新的进程组中,子进程成为这个进程组的首进程,使该进程脱离所有终端*/setsid();/*再次新建一个进程, 退出父进程, 保证该进程不是进程组长, 同时让该进程无法打开一个终端*/pc = fork(); if (pc < 0){printf("error fork\n");exit(1);}else if(pc > 0){exit (0);    }/*关闭所有文件描述符*/for(i = 0;i < MAXFILE;close(i++)) {;}chdir("/"); //改变工作目录,使进程不与任何文件系统联系*/umask(0); //文件屏蔽字设置为0signal(SIGCHLD, SIG_IGN);   //忽略SIGCHLD信号/*每隔10s写一句话*/while(1){sleep(10);syslog(LOG_USER|LOG_INFO, "测试守护进程\n");}}