linux 守护进程

Linux 系统中的守护进程是一种运行在后台的程序。而守护进程,也就是通常所说的 Daemon 进程。它通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。 Linux 大多数服务器进程就是用守护进程实现的,例如 Web 服务。守护进程常常在系统引导装入时启动,在系统关闭时终止。守护进程最大的特点是运行在后台,与终端无连接,除非特殊情况下,用户不能操作守护进程。

在 Linux 中, 每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才退出。如果想让某个进程不因为用户或终端或其他的变化而收到影响,那么就必须把这个进程变成一个守护进程。

Linux 创建守护进程的步骤如下:

  • 创建子进程,父进程退出
    这是创建守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在 Shell 终端里造成这个程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在 Shell 终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离。在 Linux 中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程时,就会自动由 1 号进程(init)收养它,这样,原先的子进程就会变成 init 进程的子进程。


  • 在进程中创建新会话
    Linux 是一个多用户多任务系统,每个进程都有一个进程 ID ,同时每个进程还都属于某一个进程组,而每个进程组都有一个组长进程,组长进程的标识 ID 等于进程租的 ID ,且该进程组 ID 不会因组长进程的退出而受到影响。会话期是一个或多个进程组的集合,通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。一个会话期可以有一个单独的控制终端,只有其前台进程才可以拥有控制终端,实现与用户的交互。从 Shell 中启动的每一个进程都将继承一个与之相结合的终端,以便进程与用户交互,但是守护进程不需要这些,子进程继承父进程的会话期和进程组 ID , 子进程会受到发送给该会话期的信号的影响,所以守护进程应该创建一个新的会话期,这个步骤是创建守护进程中最重要的一步,虽然它的实现非常简单,但是它的意义却非常重大。在这里使用的是系统调用函数 setsid 来实现的。
    setsid 函数用户创建一个新的会话,并担任该会话组的组长。调用 setsid 函数有下面的3个作用:
    让进程摆脱原会话的控制
    让进程摆脱原进程组的控制
    让进程摆脱控制终端的控制
    由于创建守护进程的第一步调用了 fork 函数来创建子进程,再将父进程退出。在调用 fork 函数时,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,还不是真正意义上的独立开来,而 setsid 函数能够使进程完全独立出来,从而摆脱其他进程的控制。


  • 禁止进程重新打开控制终端
    现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:


  • 改变当前目录为根目录
    使用 fork 创建的子进程继承了父进程的当前工作目录。守护进程不应当使用父进程的工作目录,应该设置自己的工作目录,通常可以通过 chdir() 来完成,一般可以将其设置为根目录,不过有些守护进程需要将它设置到自己特定的工作目录,但此时必须保证所设置的工作目录处于一个不能卸载的文件系统中,因此守护进程通常在系统引导后是一直存在的。


  • 重设文件权限掩码
    守护进程从父进程继承来的文件创建方式掩码可能会拒绝设置某些许可权限,文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是 050 ,它就屏蔽了文件组拥有者的可读与可执行权限。由于使用 fork 函数新建的子进程继承了父进程的文件权限掩码,可能使守护进程的执行出现问题,因此,把文件权限掩码设置为 0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是 umask 。在这里,通常的使用方法为 umask(0)。


  • 关闭文件描述符
    一般情况下,进程启动时都会自动打开终端文件,但是守护进程已经与终端脱离,所以终端描述符应该关闭。用 fork 函数新建的子进程也会从父进程哪里继承一些已经打开的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。


  • 忽略 SIGCHLD 信号
    这一步只对需要创建子进程的守护进程才有必要,很多服务器守护进程设计成通过派生子进程来处理客户端的请求,如果父进程不对 SIGCHLD 信号进程处理的话,子进程在终止后变成僵尸进程,通过将信号 SIGCHLD 的处理方式设置为 SIG_IGH 可以避免这种情况发生。


  • 用日志系统记录出错信息
    因为守护进程没有控制终端,当进程出现错误时无法写入到标准输出上,可以通过调用 syslog 将出错信息写入到指定的文件中。


  • 守护进程退出处理
    当用户需要外部停止守护进程运行时,往往会使用 kill 命令停止该守护进程。所以,守护进程中需要编码来实现 kill 发出的 signal 信号处理,达到进程的正常退出。


示例程序:
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <time.h>

//创建守护进程
void init_dameon(){
    int pid;
    int i;
    struct rlimit r1;       //存储获取进程资源限制结构体

    if(getrlimit(RLIMIT_NOFILE,&r1) < 0) //获取进程最多文件数
        printf(":can't get file limit");

    if(pid = fork())    //父进程退出
        exit(0);
    else if(pid < 0)    //开辟进程失败,退出并关闭所有进程
        exit(1);
    // 子进程继续执行
    setsid();    //创建新的会话组,子进程成为组长,并与控制中终端分离

    /* 防止子进程(组长)获取控制终端*/
    if(pid = fork())
        exit(0);
    else if(pid < 0)
        exit(1);
    /*第二子进程继续执行,第二子进程不再是会话组组长*/

    //关闭打开的文件描述符
    if(r1.rlim_max == RLIM_INFINITY) // RLIM——INFINITY 是一个无穷量的限制
        r1.rlim_max = 1024;

    for(i=0;i<r1.rlim_max;++i)
        close(i);

    chdir("/tmp"); //切换工作目录
    umask(0);       //重设文件创建掩码
}

int main(int argc, char **argv){
    FILE *fp;
    time_t t;
    signal(SIGCGLD,SIG_IGN); //忽略子进程结束信号,防止出现僵尸进程
    init_dameon();   //初始化守护进程,也就是创建一个守护进程
    while(1){
        sleep(60);    // 60 秒执行一次
        if(fp=fopen("test_daemon.log","a"))
        {
            time(&t);
            fprintf(fp,"current time is:%s",asctime(localtime(&t)));  //转化为本地时间写入到文件
            fclose(fp);
        }
        else
            exit(1);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值