守护进程分析

守护进程在Linux/Unix系统中有着广泛的应用。有时,开发人员也想把自己的程序变成守护进程。在创建一个守护进程的时候,要接触到子进程、进程组、会晤期、信号机制、文件、目录和控制终端等多个概念。因此守护进程还是比较复杂的,在这里详细地讨论Linux/Unix的守护进程的编写,总结出八条经验,并给出应用范例。

    编程要点

    1.屏蔽一些有关控制终端操作的信号。防止在守护进程没有正常运转起来时,控制终端受到干扰退出或挂起。示例如下:

     
     signal(SIGTTOU,SIG_IGN); 
signal(SIGTTIN,SIG_IGN); 
signal(SIGTSTP,SIG_IGN); 
signal(SIGHUP ,SIG_IGN);

    所有的信号都有自己的名字。这些名字都以“SIG”开头,只是后面有所不同。开发人员可以通过这些名字了解到系统中发生了什么事。当信号出现时,开发人员可以要求系统进行以下三种操作:
    ◆ 忽略信号。大多数信号都是采取这种方式进行处理的,这里就采用了这种用法。但值得注意的是对SIGKILLSIGSTOP信号不能做忽略处理。
    ◆ 捕捉信号。最常见的情况就是,如果捕捉到SIGCHID信号,则表示子进程已经终止。然后可在此信号的捕捉函数中调用waitpid()函数取得该子进程的进程ID和它的终止状态。另外,如果进程创建了临时文件,那么就要为进程终止信号SIGTERM编写一个信号捕捉函数来清除这些临时文件。
    ◆ 执行系统的默认动作。对绝大多数信号而言,系统的默认动作都是终止该进程。

    对这些有关终端的信号,一般采用忽略处理,从而保障了终端免受干扰。

    这类信号分别是,SIGTTOU(表示后台进程写控制终端)、SIGTTIN(表示后台进程读控制终端)、SIGTSTP(表示终端挂起)和SIGHUP(进程组长退出时向所有会议成员发出的)。

    2.将程序进入后台执行。由于守护进程最终脱离控制终端,到后台去运行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。这就是常说的“脱壳”。子进程继续函数fork()的定义如下:

     
     #include <sys/types.h>
#include <unistd.h>
 pid_t fork(void);

    该函数是Linux/Unix编程中非常重要的函数。它被调用一次,但返回两次。这两次返回的区别是子进程的返回值为“0”,而父进程的返回值为子进程的ID。如果出错则返回“-1”。

    3.脱离控制终端、登录会话和进程组。开发人员如果要摆脱它们,不受它们的影响,一般使用 setsid() 设置新会话的领头进程,并与原来的登录会话和进程组脱离。这只是其中的一种方法,也有如下处理的办法:

代码来自于内核 modified by zhouch

asmlinkage long sys_setsid(void)
{
     struct task_struct *group_leader = current->group_leader;
     pid_t session;
     int err = -EPERM;

 mutex_lock(&tty_mutex);
         write_lock_irq(&tasklist_lock);

 /* Fail if I am already a session leader */
        if (group_leader->signal->leader)
          goto out;

 session = group_leader->pid;   会话sid就等于进程的pid
        /* Fail if a process group id already exists that equals the
       * proposed session id.
        *
        * Don't check if session id == 1 because kernel threads use this
         * session id and so the check will always fail and make it so
           * init cannot successfully call setsid.
        */
 if (session > 1 && find_task_by_pid_type(PIDTYPE_PGID, session))
  goto out;

 group_leader->signal->leader = 1;     init 进程
 __set_special_pids(session, session);
 group_leader->signal->tty = NULL; 控制终端置为空,但是此进程组组长是否还可以重新申请打开控制终端现在不得而知???????
 group_leader->signal->tty_old_pgrp = 0;
 err = process_group(group_leader);  即return group_leader->signal->pgrp
out:
 write_unlock_irq(&tasklist_lock);
 mutex_unlock(&tty_mutex);
 return err;
}

 

     
     if  ((fd = open("/dev/tty",O_RDWR)) >= 0) { 
ioctl(fd,TIOCNOTTY,NULL); 
close(fd); 
}

    其中/dev/tty是一个流设备,也是终端映射,调用close()函数将终端关闭。

    4.禁止进程重新打开控制终端。进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。开发人员可以通过不再让进程成为会话组长的方式来禁止进程重新打开控制终端,需要再次调用fork函数。
    上面的程序代码表示结束第一子进程,第二子进程继续(第二子进程不再是会话组长)。

    5. 关闭打开的文件描述符,并重定向标准输入、标准输出和标准错误输出的文件描述符。进程从创建它的父进程那里继承了打开的文件描述符。如果不关闭,将会浪费系统资源,引起无法预料的错误。关闭三者的代码如下:

     
     for (fd = 0, fdtablesize = getdtablesize(); 
 fd < fdtablesize; fd++) 
  close(fd);

    但标准输入、标准输出和标准错误输出的重定向是可选的。也许有的程序想保留标准输入(0)、标准输出(1)和标准错误输出(2),那么循环应绕过这三者。代码如下:

     
     for (fd =3, fdtablesize = getdtablesize();
fd < fdtablesize; fd++) 
  close(fd);

    有的程序有些特殊的需求,还需要将这三者重新定向。示例如下:

     
     error=open("/tmp/error",O_WRONLY|O_CREAT,
0600);
  dup2(error,2);
 close(error);
 in=open("/tmp/in",O_RDONLY|O_CREAT,0600);
 if(dup2(in,0)==-1)  perror("in");
 close(in);
out=open("/tmp/out",O_WRONLY|O_CREAT,0600);
 if(dup2(out,1)==-1) perror("out");
 close(out);

    6.改变工作目录到根目录或特定目录进程活动时,其工作目录所在的文件系统不能卸下。

    一般需要将工作目录改变到根目录或特定目录,注意用户对此目录需要有读写权。防止超级用户卸载设备时系统报告设备忙。

    7.处理SIGCHLD信号。SIGCHLD信号是子进程结束时,向内核发送的信号。

如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。因此需要对SIGCHLD信号做出处理,回收僵尸进程的资源,避免造成不必要的资源浪费。可以用如下语句:
    signal(SIGCHLD,(void *)reap_status);

    捕捉信号SIGCHLD,用下面的函数进行处理:

     
     void reap_status() 
 { int pid; 
   union wait status; 
   while ((pid = wait3(&status,WNOHANG,NULL)) > 0) 
  …… }

    8.在Linux/Unix下有个syslogd的守护进程,向用户提供了syslog()系统调用。任何程序都可以通过syslog记录事件。

    由于syslog非常好用和易配置,所以很多程序都使用syslog来发送它们的记录信息。一般守护进程也使用syslog向系统输出信息。syslog有三个函数,一般只需要用syslog(...)函数,openlog()/closelog()可有可无。syslog()在shslog.h定义如下:

     
     #include <syslog.h>
void syslog(int priority,char *format,...);

    其中参数priority指明了进程要写入信息的等级和用途。第二个参数是一个格式串,指定了记录输出的格式。在这个串的最后需要指定一个%m,对应errno错误码。

    应用范例

    下面给出Linux下编程的守护进程的应用范例,在UNIX中,不同版本实现的细节可能不一致,但其实现的原则是与Linux一致的。

     
     #include <stdio.h> 
#include <signal.h> 
#include <sys/file.h> 
main(int argc,char **argv)
{
  time_t now;
  int childpid,fd,fdtablesize;
  int error,in,out;
  /* 忽略终端 I/O信号,STOP信号 */
 signal(SIGTTOU,SIG_IGN);
 signal(SIGTTIN,SIG_IGN);
  signal(SIGTSTP,SIG_IGN); 
  signal(SIGHUP ,SIG_IGN);
  /* 父进程退出,程序进入后台运行 */
  if(fork()!=0) exit(1);
   if(setsid()<0)exit(1);/* 创建一个新的会议组 */ 
  /* 子进程退出,孙进程没有控制终端了 */  
  if(fork()!=0) exit(1);
  if(chdir("/tmp")==-1)exit(1);
/* 关闭打开的文件描述符,包括标准输入、标准输出和标准错误输出 */ 
 for (fd = 0, fdtablesize = getdtablesize(); fd < fdtablesize; fd++) 
   close(fd);
   umask(0);/*重设文件创建掩模 */ 
   signal(SIGCHLD,SIG_IGN);/* 忽略SIGCHLD信号 */ 
/*打开log系统*/
  syslog(LOG_USER|LOG_INFO,"守护进程测试!/n");  
   while(1)  
   {  
    time(&now);
   syslog(LOG_USER|LOG_INFO,"当前时间:/t%s/t/t/n",ctime(&now));
    sleep(6);
     }  
 }

    此程序在Turbo Linux 4.0下编译通过。这个程序比较简单,但基本体现了守护进程的编程要点。读者针对实际应用中不同的需要,还可以做相应的调整。( 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是一门linux下c++通讯架构实战课程,针对c/c++语言已经掌握的很熟并希望进一步深造以将来用c++在linux下从事网络通讯领域/网络服务器的开发和架构工作。这门课程学习难度颇高但也有着极其优渥的薪水(最少30K月薪,最高可达60-80K月薪),这门课程,会先从nginx源码的分析和讲解开始,逐步开始书写属于自己的高性能服务器框架代码,完善个人代码库,这些,将会是您日后能取得高薪的重要筹码。本课程原计划带着大家逐行写代码,但因为代码实在过于复杂和精细,带着写代码可能会造成每节课至少要4~5小时的超长时间,所以老师会在课前先写好代码,主要的时间花费在逐行讲解这些代码上,这一点望同学们周知。如果你觉得非要老师领着写代码才行的话,老师会觉得你当前可能学习本门课程会比较吃力,请不要购买本课程,以免听不懂课程并给老师差评,差评也会非常影响老师课程的销售并造成其他同学的误解。 这门课程要求您具备下面的技能:(1)对c/c++语言掌握的非常熟练,语言本身已经不是继续学习的障碍,并不要求您一定熟悉网络或者linux;(2)对网络通讯架构领域有兴趣、勇于挑战这个高难度的开发领域并期望用大量的付出换取高薪;在这门课程中,实现了一个完整的项目,其中包括通讯框架和业务逻辑框架,浓缩总结起来包括如下几点:(1)项目本身是一个极完整的多线程高并发的服务器程序;(2)按照包头包体格式正确的接收客户端发送过来的数据包, 完美解决收包时的数据粘包问题;(3)根据收到的包的不同来执行不同的业务处理逻辑;(4)把业务处理产生的结果数据包正确返回给客户端;本项目用到的主要开发技术和特色包括:(1)epoll高并发通讯技术,用到的触发模式是epoll中的水平触发模式【LT】;(2)自己写了一套线程池来处理业务逻辑,调用适当的业务逻辑处理函数处理业务并返回给客户端处理结果;(3)线程之间的同步技术包括互斥量,信号量等等;(4)连接池中连接的延迟回收技术,这是整个项目中的精华技术,极大程度上消除诸多导致服务器程序工作不稳定的因素;(5)专门处理数据发送的一整套数据发送逻辑以及对应的发送线程;(6)其他次要技术,包括信号、日志打印、fork()子进程守护进程等等;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值