守护进程的编程规则(学fork必看)

来源:互联网 发布:淘宝店头像在线制作 编辑:程序博客网 时间:2024/05/17 02:19

来源:www.36ji.net/article/0150/21006.html

守护进程的编程规则:
1、屏蔽一些有关控制终端操作的信号
防止在守护进程没有正常运转起来时,控制终端受到干扰退出或挂起。
2、脱离控制终端,登录会话和进程组
登录会话可以包含多个进程组,这些进程组共享一个控制终端,这个控制终端通常是创建进程的登录终端。控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。
其方法是在fork()的基础上,调用setsid()使进程成为会话组长。调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离,由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
setsid()实现了以下效果:
(a) 成为新对话期的首进程
(b) 成为一个新进程组的首进程
(c) 没有控制终端。
3、禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端,再fork()一次。
4、关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在地文件系统无法卸下以及无法预料的错误。一般来说,必要的是关闭0、1、2三个文件描述符,即标准输入、标准输出、标准错误。因为我们一般希望守护进程自己有一套信息输出、输入的体系,而不是把所有的东西都发送到终端屏幕上。
5、改变当前工作目录
将当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个装配的文件系统中。因为守护进程通常在系统重启之前是一直存在的,所以如果守护进程的当前工作目录在一个装配文件系统中,那么该文件系统就不能被拆卸。
另外,某些守护进程可能会把当前工作目录更改到某个指定位置,在此位置做它们的工作。例如,行式打印机假脱机守护进程常常将其工作目录更改到它们的spool目录上。
6、重设文件创建掩码
将文件方式创建屏蔽字设置为0:umask(0)。 由继承得来的文件方式创建的屏蔽字可能会拒绝设置某些许可权。例如,若守护进程要创建一个组可读、写的文件,而继承的文件方式创建屏蔽字,屏蔽了这两种许可权,则所要求的组可读、写就不能起作用。
7、处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时fork子进程出来处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)而仍占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在系统V下可以简单地将SIGCHLD信号的操作设为SIG_IGN,即忽略掉。这样,内核在子进程结束时不会产生僵尸进程,这一点与BSD4不同,在BSD4下必须显示等待子进程结束才能释放僵尸进程。
8、记录信息
在Linux/Unix下有个syslogd的守护进程,向用户提供了syslog()系统调用。任何程序都可以通过syslog记录事件。
源码实现及分析:
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
    time_t now;
    int childpid,fd,fdtablesize;
//     int error,in,out;
    int fp; 
//屏蔽一些有关控制终端操作的信号。防止在守护进程没有正常运转起来时,控制终端受到
//干扰 退出或挂起,此处忽略了终端I/O信号、STOP信号
    signal(SIGTTOU,SIG_IGN);
    signal(SIGTTIN,SIG_IGN);
    signal(SIGTSTP,SIG_IGN);
    signal(SIGHUP,SIG_IGN);
//由于子进程会继承父进程的某些特性,如控制终端、登录会话、进程组等,而守护进程
//最终要脱离控制终端到后台去运行,所以必须把父进程杀掉,以确保子进程不是进程组长,
//这也是成功调用setsid()所要求的。
    if (fork() != 0)
    {
      exit(1);
    }
//脱离了控制终端还要脱离登录会话和进程组,这里可以调用setsid()函数,调用成功后
//进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离,由于会话过程
//对控制终端的独占性,进程同时与控制终端脱离。
/*
    if (setsid()    {
      exit(1);
    }
*/
    //要达到setsid()函数的功能也可以用如下处理方法。"/dev/tty"是一个流设备,也是终端映射,调用close()函数将终端关闭。
    if ((fp=open("/dev/tty",O_RDWR)) >= 0)
    {
      ioctl(fp,TIOCNOTTY,NULL);
      close(fp);
    }
    //进程已经成为无终端的会话组长,但它可以重新申请打开一个新的控制终端。可以通过不再让进程成为会话组长的方式来禁止进程重新打开控制终端,需要再次调用fork函数。
    if (fork() != 0)
    {
      exit(1);
    }

    //从父进程继承过来的当前工作目录可能在一个装配的文件系统中。因为守护进程通常在系统重启之前是一直存在的,所以如果守护进程的当前工作目录在一个装配文件系统中,那么该文件系统就不能被卸载。比如说从父进程继承的当前目录是/mnt下面的一个被挂载的目录。
    if (chdir("/tmp") == -1)
    {
      exit(1);
    }
    //关闭打开的文件描述符,或重定向标准输入、标准输出和标准错误输出的文件描述符。进程从创建它的父进程那里继承了打开的文件描述符。如果不关闭,将会浪费系统资源,引起无法预料的错误。getdtablesize()返回某个进程所能打开的最大的文件数。
    for (fd=0,fdtablesize=getdtablesize();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);
*/
    //由继承得来的文件方式创建的屏蔽字可能会拒绝设置某些权限,所以要重新赋于所有权限。
    umask(0);
    //如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源,如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。因此需要对SIGCHLD信号做出处理,回收僵尸进程的资源,避免造成不必要的资源浪费。
    signal(SIGCHLD,SIG_IGN);
    //守护进程不属于任何终端,所以当需要输出某些信息时,它无法像一般程序那样将信息直接输出到终端,可以使用linux中自带的syslogd守护进程,它向用户提供了syslog()系统调用函数。信息都保存在/var/log/syslog文件中。
    syslog(LOG_USER|LOG_INFO,"守护进程测试!\n");
    //每隔60秒钟就向/var/log/syslog日志文件里写入一次syslog()发出的信息。
    while (1)
    {
      //time(time_t *t)返回从1970年1月1日0时0分0秒到现在的所有秒数。
      time(&now);
         
      //获得父进程ID和子进程ID。
      syslog(LOG_USER|LOG_INFO,"PPID: %d PID: %d\n",getppid(),getpid());
      //ctime(const time_t *)返回当前时间的一个char型指针。
      syslog(LOG_USER|LOG_INFO,"当前时间:%s\n",ctime(&now));
      //睡眠60秒
      sleep(60);
    }
    return 0;
}














服务器编程之fork并行模式

 

摘要:网络编程本身并不复杂或多么困难,复杂或困难的是在不稳定的网络中做到稳定的服务器与通信,此时并行技术是实现的稳定的服务器的技术之一,当然也不是服务器端使用的技术。fork是POSIX系统中产生新进程的唯一方法,可以实现进程并行编程模式。

在介绍服务器编程的fork模式之前,我们首先来说说fork自身的问题,对于深知fork的读者可以跳过本段内容。fork通常作为一个系统调用来存在,或者作为一个系统调用的简单封装,它对于内核来说只做一件事:把当前进程的内存镜像复制一份,以在系统中产生一个新进程,并在两个进程中各返回一次,新产生的进程与原本的进程完全相同,它们共享着文件描述符,如此在两个进程中可以对同一文件进行操作,套接字也如此。

服务器编程的fork并行模式的基本的思想是:一个进程监听一相应的端口,当取得一个客户连接的时候,它使用fork产生一个新进程,由于新进程是原进程的复本,所以它可以与客户完成通信,而原来的父进程仍然继续监听那个端口,下面是一个典型的代码片断:

#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
        //如同串行的程序,这之前完成许多初始工作
        //下面代码假设sockfd是一个监听套接字

        while(1)
        {
                //如果想知道客户地址,后面两个参数可以指定相应的结构
                int cfd=accpet(sockfd, NULLNULL);
                int pid=fork();
                if(0>pid)
                {
                        //创建进程失败,我们直接退出服务器
                        //这只是一个示例程序
                        return -1;
                }
                else if(0<pid)
                {
                        close(cfd);
                        continue;
                }

                //此处代码我意在让新进程运行,此时pid==0是明显的
                close(sockfd);
                
                //新进程使用cfd与客户进程进行通信
                //在通信结束之后,新进程也许就直接退出了
        }
}

上述的代码片断只是一个示例性的,以简单明了为基础,但它却直接而不明白反应了fork并行模式的基本思想。在实际中的程序还可以需要对信号进行处理,尤其当新进程比父进程早退出的时候,父进程会收到一个SIGCHILD信号,如果父进程不wait或waitpid的话,它将变成一个僵死进程,显然这是不希望的。


0 0
原创粉丝点击