一. 守护进程

    守护进程又称为精灵(Daemon)进程,顾名思义,守护是一直会存在的,它是运行在后台的一种特殊的进程,独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。比如当Linux系统启动的时候会启动一些系统服务进程,因为这些进程没有控制终端因此不能直接和用户进行交互,它的生命周期随系统,而不是像用户登录或者运行程序才开始启动的而进程运行完毕或者用户注销后就终止,这种进程就称之为守护进程或者精灵进程

    可以在终端下查看系统中的守护进程,这些进程通常以精灵的单词Daemon的首字母d结尾:


wKioL1cx3APSbqWKAAAoOHiXxvs283.png


从上面可以看到,大多数进程的进程ID号、进程组ID和会话ID都是同一个ID号,而其控制终端TTY是没有的,因此,守护进程有如下特点:

  1. 守护进程自成进程组并且自成会话,也就是守护进程所在的进程组和会话中只有自身一个进程;

  2. 守护进程不与终端设备关联;


二. 创建守护进程

    我们可以自己利用守护进程的特点创建一个守护进程,而使用到的函数是setsid函数:

wKioL1cx4y2idsKEAABp5COahVg247.png

该函数的参数为空,返回值是pid_t也就是调用该函数的进程ID号;

setsid函数调用有如下三个特点:

  1. 新创建出一个会话并且调用函数进程为会话首进程,会话ID为进程ID;

  2. 新创建出一个进程组并且调用函数进程为进程组的组长进程,进程组ID为进程ID;

  3. 若调用函数进程和一个控制终端关联,则失去这个终端,这个终端仍然会存在并且可以读写,但只是一个文件而不再是一个控制终端;

这里需要强调的是,在调用setsid之前必须保证调用函数进程不是组长进程,这样的话,可以考虑用fork来创建出子进程,让子进程来调用setsid,因为在同一个进程组中子进程之前还有进程,而组长进程是该组的第一个进程,这样就可以确定子进程不是组长进程了。


创建守护进程一般有如下几个步骤:

  1. 调用umask将文件模式创建屏蔽字设置为0,以确保党进程想要生成文件时不受系统权限的控制;

  2. 调用fork创建出子进程,以确保子进程不是组长进程,同时使父进程退出只保留子进程;

  3. 调用setsid函数,该函数创建成功会新建一个会话和一个进程组,该进程组ID和会话ID都是子进程的ID,同时使子进程失去其原本链接的控制终端(再次fork,使当前子进程退出,因为当前子进程是话首进程可能之后还会打开控制终端,而再次fork出的子进程不是话首进程,确保之后不会再次打开控制终端);

  4. 将当前用户目录更改为根目录,使进程工作目录稳定;

  5. 关掉不需要的文件描述符;

  6. 其他(忽略SIGCHILD信号);


程序设计:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

void my_daemon()
{
    umask(0);//设置文件创建屏蔽字为0

    pid_t pid;
    pid = fork();//创建出子进程
    if(pid < 0)
    {   
        printf("first fork failed...\n");
        perror("fork");
    }   
    else if(pid > 0)//father   父进程退出
    {   
        printf("father process out...pid: %u\n", getpid());
        exit(0);
    }   

    //child   子进程
    printf("child process...pid: %u\n", getpid());

    if((pid = setsid()) < 0)//调用setsid函数创建守护进程
        perror("setsid");
    else
        printf("create daemon process success...pid: %u\n", getpid());

    if((pid = fork()) < 0)//再次fork并使父进程退出,确保子进程不是话首进程不会再打开TTY设备
    {   
        printf("second fork failed...\n");
        perror("fork");
    }   
    else if(pid > 0)//father   父进程退出
    {   
        printf("last child as father process out...pid: %u\n", getpid());
        exit(1);
    }

    if(chdir("/") < 0)//将工作目录更改为根目录
    {
        printf("change dir failed...\n");
        perror("chdir");
    }

    printf("signal: %u\n", signal(SIGCHLD, SIG_IGN));

    if(close(0) < 0)//关掉标准输入
        printf("close stdin failed...\n");
    if(close(2) < 0)//关掉标准输出
        printf("close stderr failed...\n");
    if(close(1) < 0)//关掉标准错误
        printf("close stdout failed...\n");
}

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


运行程序:

wKioL1c0Fo7TUEukAABaXCGooSI150.png


程序运行结果到“signal:0”输出就结束了,但这里并不是死循环,因为第一次fork使父进程退出,此时shell认为当前作业已经完成,将把bash放到前台运行,但这时候daemon进程虽然调用setsid失去了控制终端,但还是能够进行读写,因此还是会打印出信息,只是不再作为一个控制终端了,用jobs命令来查看后台也查看不到,可用下面命令来查看:


wKiom1c0FbHTc847AAC4GcHZTNQ745.png


查看结果发现有一个自己写的daemon进程,可以看到其进程组ID和会话ID就是前面运行程序结果中调用setsid函数后来再次fork又退出的进程,而其TTY一项并没有关联,因此daemon是一个守护进程。

-------------------------------------------------------------------------------------------


    在上面罗列出的系统的守护进程中,有一个crond的守护进程,这个进程的作用就是定期执行任务或等待处理某些任务,在目录/etc下就可以查看有关的文件:

wKiom1c0Q2fxD3zRAABqsdndTqE037.png


cron.allow里面存放用户名,用来标识是否允许用户拥有自己的cron定时任务,一开始是没有这个文件的,可以由超级用户添加;

cron.deny里面则存放了禁止用户使用cron守护进程;

打开crontab文件:

wKiom1c0RJ2ComVOAAAZ116tzzs965.png

每个用户都可以有自己的crontab文件,而文件内容的格式则如上图:每一个*号的位置都是时间点,而后面则是定时要执行的任务;

*表示每分钟(小时、周、月...)都要执行;

*/n表示每隔n分钟(小时、周...)执行;

t1--t2表示在t1--t2时间段里都要执行;

t1,t2,t3表示分别在t1,t2,t3时间执行;


crontab -e 可以编辑用户crontab文件内容,若没有指定用户则为当前用户;

crontab -l 显示用户的crontab文件内容;

crontab -r 删除用户的crontab文件;

crontab -u 设定某个用户的cron服务,一般是超级用户;


另外:

service crond start   启动cron服务;

service crond stop    停止cron服务;

service crond restart  重启cron服务;

service crond status  查看当前cron状态


用户的crontab文件存放在目录/var/spool/cron下,文件名就是用户名;

比如在root的crontab文件中写入命令:

*/1 * * * * echo You should drink some water... >> test.txt


则重新启动cron服务并用tail -f 来查看:wKioL1c0S62gHKFKAAAMw513dyI709.png

如上所示,每隔一分钟就向test.txt文件追加一句话,这样就完成了用户自己的一个定时任务。



《完》