1、什么是守护进程

守护进程也称为精灵进程,是运行在后台进程的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
(1)脱离于控制终端并且在后台运行
(2)不受用户登录注销的影响,它们一直在运行着
Linux大多数服务器就是用守护进程实现的,例如:Internet服务器inetd,Web服务器httpd

查看系统中的守护进程:使用命令 ps axj
凡是TPGID一栏中写着-1的都是没有控制终端的进程,也就是守护进程。
在COMMAND一列中用 [ ]括起来的名字表示内核线程,这些线程在内核里创建,没有用户空间代码,通常采用K开头的,表示Kernel。
这里写图片描述

2、实现一个守护进程

(1)屏蔽一些控制终端操作的信号
这是为了防止守护进行在没有运行起来前,控制终端受到干扰退出或挂起。
这里写图片描述

(2)调用fork,父进程退出
保证子进程不是一个组长进程,方法是在进程中调用 fork() 使父进程终止, 让守护进程在子进程中后台执行。
这里写图片描述

(3) setsid创建一个新会话
这里写图片描述
该函数返回值:调用成功返回新创建的Session的id(就是当且进程的id),出错返回-1。
注意:调用该函数之前,当前进程不允许是进程组的组长,否则返回-1
所以:要保证当前进程不是进程组的组长,先fork创建一个子进程,这样保证了子进程不可能是该组进程的第一个进程,再调用setsid。
成功调用setsid函数的结果

  • 创建一个新的Session,当前进程成为Session Leader,当前进程id就是Session的id
  • 创建一个新的进程组,当前进程成为进程组的 Leader,当前进程id就是进程组的id
  • 如果当前进程原本有一个控制终端,则它失去控制终端,成为一个没有控制终端的进程。

(4) 禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端,采用的方法是再次创建一个子进程,示例代码如下:

if( pid=fork() ){ // 父进程  
    exit(0);      // 结束第一子进程,第二子进程继续(第二子进程不再是会话组长)   
} 

(5)关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。文件描述符其实就是 标准输入0,标准输出1,错误输出。

close(0);
close(1);
close(2);

(6) 改变当前工作目录
使用fork创建的子进程是继承了父进程的当前工作目录,由于在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后使用会造成很多的麻烦。因此通常的做法是让“/”作为守护进程的当前目录,当然也可以指定其他的别的目录来作为守护进程的工作目录。
这里写图片描述

(7)重设文件创建掩模
进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取权限。为防止这一点,将文件创建掩模清除:

umask(0);

(8)处理 SIGCHLD 信号
但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在 Linux 下可以简单地将 SIGCHLD 信号的操作设为 SIG_IGN

signal(SIGCHLD,SIG_IGN);

这样,内核在子进程结束时不会产生僵尸进程。

创建daemon整体代码如下:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int Create_Daemon()
{
    int pid;
    //屏蔽一些控制终端信号    
    signal(SIGTTOU,SIG_IGN);   
    signal(SIGTTIN,SIG_IGN);   
    signal(SIGTSTP,SIG_IGN);   
    signal(SIGHUP ,SIG_IGN); 
    //设置文件掩码
    umask(0);
    //调用fork函数,父进程退出
    pid = fork();
    if(pid < 0 )
    {
        printf("error fork");
        return -1;
    }
    else if(pid > 0)
    {//father
        exit(0);
    }
    //设置新会话
    setsid();
    //处理SIGCHlD信号
    signal(SIGCHLD,SIG_IGN);

    //禁止进程重新打开控制终端
    if(pid = fork())
    {//father
        exit(0);
    }
    else if(pid <0)
    {
        perror("fork");
        exit(-1);
    }

    //关闭打开的文件描述符
    close(0);close(1);close(2);

    //改变当前的工作目录
    chdir("/");

    return 0;
}

int main()
{
    Create_Daemon();
    while(1);
    return 0;
}

测试结果:
这里写图片描述

3、daemon函数

这里写图片描述

参数:
- nochdir:当 nochdir为零时,当前目录变为根目录,否则不变
- noclose:当 noclose为零时,标准输入、标准输出和错误输出重导向为/dev/null,也就是不输出任何信 息,否则照样输出。

返回值:deamon()调用了fork(),如果fork成功,那么父进程就调用_exit(2)退出,所以看到的错误信息 全部是子进程产生的。如果成功函数返回0,否则返回-1并设置errno。

Logo

更多推荐