守护进程

守护进程

首先我们先来认识一下什么是守护进程?

认识守护进程

    守护进程也称为精灵进程(Daemom),是运行在后台的一种特殊的进程。它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如ftp服务器,ssh服务器,Web服务器等。同时,守护进程完成许多系统任务。比如作业规划crond。

     Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户进行交互。其他进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但是系统服务进程(守护进程)不收用户登录和注销的影响,它们一直运行着。这种进程就叫做守护进程(Daemom)。

我对守护进程总结了以下三点:

1、守护进程本质其实是一个孤儿进程

2、守护进程自成进程组,自成会话

3、守护进程与终端无关

守护进程和后台进程的区别:

1、后台进程受用户登录和注销的影响,而守护进程不受用户登录和注销的影响

2、守护进程已经完全脱离终端控制台了,而后台程序并未完全脱离终端(在终端未关闭前还是会往终端输出结果)

3、守护进程的会话组和当前目录,文件描述符都是独立的。后台运行只是终端进行了一次fork,让程序在后台执行,这些都没改变

下面我们用ps axj命令查看一下系统中的进程。

参数:

    a表示不仅列出当前用户的进程,也列出所有其他用户进程。

    x表示不仅列出由控制终端的进程,也列出所有无终端控制的进程

    j表示列出与作业控制相关的信息


我们可以发现:

  • 所有的守护进程都没有控制终端,其终端名(TTY)设置为问号(?)。
  • 自成会话,自成进程组。不与其他会话或进程组相互关联和干扰。所以一般一个守护进程的进程ID,组ID,会话ID都相同。(自成进程组这点说的也不太严谨,若父进程是守护进程,父进程fork的子进程也是守护进程。这时父子进程属于同一进程组)
  • 守护进程不受用户登录注销的影响,当你注销或者重登后,守护进程一直在运行。
  • 生存期长,在系统引导装入时启动,仅在系统关闭时终止。
  • 在后台运行(原因可归结于没有控制终端)
  • 大多数的守护进程都以root权限运行。
  • 凡是TPGID一栏写着 -1 的都是没有控制终端的进程,也就是守护进程。
  • 在COMMAND一列用 [ ]括起来的名字表示内核线程,这些线程在内核创建,没有用户空间代码,因此没有程序文件名和命令行,通常采用以k开头的名字,表示Kernel
  • init进程我们已经了解过了,udevd负责维护/dev目录下的设备文件,acpid负责电源管理,syslogd负责维护/var/log下的日志文件
  • 守护进程通常采用以d结尾的名字,表示Daemom

列几个常见的:


创建守护进程

创建守护进程最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader

#include <unistd.h>
pid_t setsid(void);
    该函数调用成功时:返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。
注意:调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。要保证当前进程不是进程组的Leader也很容易,只要先fork再调用setsid就行了。fork创建的子进程和父进程在同一个进程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调用setsid就不会有问题
成功调用该函数的结果是:
        创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
        创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。

    如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件而不是控制终端了。

除了以上两种操作外,创建一个守护进程还需要其他几项工作。下面我将完全的步骤列出来:

  • 调用umask函数将当前文件模式创建屏蔽字为一个已知值(通常为0)。在上面提到过,我们要操作的进程是一个子进程。而子进程从父进程的PCB继承过来的文件模式创建屏蔽字可能会屏蔽某些权限。而加入我们要创建的守护进程正好需要这些权限的话就会造成很麻烦的问题。另一方面,如果守护进程调用了库函数创建了文件,那么文件模式创建屏蔽字应该设置为更强的。因为库函数不允许调用者通过一个显式的函数参数来设置权限。
  • fork子进程,并且结束父进程。
  • 调用setsid函数。创建一个会话,使当前进程称为一个会话的首进程,一个进程组的组长进程,一个没有控制终端的进程。
  • 调用函数chdir将当前进程的工作目录更改为根目录或者某个指定位置。因为子进程从父进程继承来的工作目录可能是在一个挂载的文件系统中,而守护进程在系统再次引导前是一直存在的,如果不更改,那么挂载的文件系统就一直卸载不了。
  • 调用fclose函数关闭不在需要的文件描述符(0,1,2)。使守护进程不再持有从父进程继承来的任何文件描述符。或者还可以将文件描述符重定向到文件(/dev/null)。这样相当于将当前进程的标准输入,标准输出,标准错误都失效。关闭文件描述符的原因是守护进程是与控制终端没有任何联系的,并且它是在后台运行。后台并没有接受它输入输出也无处显示,我们不希望在终端上简单守护进程的输出,用户也不想在终端上的输入被守护进程读取。

如果使我们自己创建的话,应该是这样:

第一步:调用umask将文件模式创建屏蔽字设置为0,

第二步:调用fork,然后使得父进程退出,

第三步:调用setsid创建新的Session,

第四步:忽略SIGCHLD信号

第五步:将当前工作目录改成根目录

第六步:关闭不再需要的文件描述符

更改工作目录函数

int chdir(const char * path);

现在我们来简单实现一下:

#include<stdio.h>  
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/stat.h>

void mydaemon()
{
    umask(0);//1、调用umask将文件模式创建屏蔽字设置为0
    //2、调用fork,父进程退出exit
    pid_t id = fork();
    if(id > 0 )
    {
        exit(1);
    }

    setsid(); //3、创建新会话
    
    signal(SIGCHLD,SIG_IGN);//4、忽略SIGCHLD信号
    
    if(chdir("/") < 0)//5、将工作目录设置为根目录
    {
        printf("child dir error\n");
        exit(0);
    } 

    close(0); //6、关闭文件描述符
    close(1);
    close(2);
}

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

       我们用 ps axj 命令查看当前进程:


    上面为我们创建守护进程的一种方式,其实Linux为我们提供了专门的函数接口来创建守护进程。

函数daemon():

#include <unistd.h>

int daemon(int nochdir, int noclose);

参数:

    nochdir:如果设置为0的话表示将工作目录改为根目录。

    noclose:如果设置为0的话就将文件描述符重定向到/dev/null文件。与上面原理相似。 

fd0 = open("/dev/null",O_RDWR);
dup2(fd0,1); //dup2的作用就是文件描述符重定向
dup2(fd0,2);

我们用dup2,文件描述符重定向重现来实现一下上边的代码:

#include<stdio.h>  
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/stat.h>

void mydaemon()
{
    int fd0;
    umask(0);//1、调用umask将文件模式创建屏蔽字设置为0
    //2、调用fork,父进程退出exit
    pid_t id = fork();
    if(id > 0 )
    {
        exit(1);
    }

    setsid(); //3、创建新会话
    
    signal(SIGCHLD,SIG_IGN);//4、忽略SIGCHLD信号
    
    if(chdir("/") < 0)//5、将工作目录设置为根目录
    {
        printf("child dir error\n");
        exit(0);
    }
    close(0); //6、关闭文件描述符,重定向到/dev/NULL 
    fd0 = open("/dev/NULL",O_RDWR); 
    dup2(fd0,1); 
    dup2(fd0,2);
    }
}

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


阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lu_1079776757/article/details/79978343
个人分类: Linux
上一篇进程间关系
下一篇C语言实现顺序表
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭