天天看点

守护进程Daemon---fork两次

1.什么是守护进程?

守护进程也叫精灵进程,是在后台运行的一种特殊进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件;

在 linux系统启动时会有很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互,除守护进程外其他进程都是在用户登录或运行程序时创建,在系统注销时终止,但系统服务程序不受用户登录注销的影响(原因:它跟终端没有关系),我们把这种进程叫做守护进程。

一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。

2.为什么要有守护进程??

控制终端因为某些原因会发送一些信号,接受到信号的进程去执行这些信号的默认处理动作会导致进程退出。这就使得进程不能正常的处理某些任务,所以就需要像守护进程这样接受不到信号的进程。让进程独立与控制终端,执行某些任务或处理某些事件。

3.我们通过ps ajx命令查看系统的进程

ps ajx中ajx各参数代表的意思

  • 参数a,表示不仅列出当前进程,也列出所有其他用户进程;
  • 参数x,表示不仅列出控制终端的进程,也列出所有无控制终端的进程;
  • 参数j,表示列出与作业控制相关的信息;
    守护进程Daemon---fork两次

上图显示的是部分守护进程,因为精灵进程跟终端没有关系,所以TTY显示是‘?’的进程都为精灵进程;同时我们也可以看到,精灵进程的TPGID都为-1,且进程的COMMAND(执行命令)都用[]括号;

守护进程通常都已‘d’结尾,我们可以通过ps ajx|grep -E ‘d$’命令查看

守护进程Daemon---fork两次
守护进程Daemon---fork两次

init进程的id为1,所以上述父进程id为1的都是init进程创建的子进程,且每个守护进程的会话id、组id。进程id都是相同的(此时一个会话里只有一个进程);

4.setsid函数

  • 关键函数 setsid
    守护进程Daemon---fork两次
  • 该函数创建了一个新的会话,但是该函数有其创建的要求,如下图:
    守护进程Daemon---fork两次
    要求:调用这个函数的当前进程不允许是当前进程组的组长id,否则的话该函数调用失败返回-1,(解决方法:fork出一个子进程来调用setsid就可以满足要求)
  • 成功调用该函数后的结果

    <1>.创建了一个新的Session,当前进程为会话首进程,当前进程的id记为会话为id;

    <2>.创建了一个新的进程组,当前进程为进程组的组长进程,当前进程的id记为当前进程组的id;

    <3>.如果当前进程有一个控制终端,那么它将失去这个控制终端;(此时将控制终端看成一个普通文件,文件是打开的,可以都希望和关闭)

5.创建守护进程

  • 调用 unmask将文件模式创建屏蔽字设置为0;(保证精灵进程创建的文件是不受权限的限制的);
  • 调用fork函数,父进程退出

    -如果该进程作为简单的shell命令启动,父进程终止使得shell认为前台作业结束,将该进程转换到后台运行(为什么么父进程exit);

    -保证子进程不是一个进程的组长进程(为什么fork);

  • 在fork中调用setsid函数,创建一个新的会话,调用成功后得到三个结果(会话、进程组、控制终端);
  • 将当前的工作目录设置为根目录(因为系统可以删除掉除根目录的所有路径,设置为根目录,不会影响守护进程 chdir);
    守护进程Daemon---fork两次
  • 关闭不需要的文件描述符(标准输入、标准输出、标准错误);
  • 忽略信号(SIGCHID);

6.根据守护进程的创建步骤编写代码

  • fork一次
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>

void create_daemon()
{
    umask();//掩码设置为0,不会屏蔽任何文件权限操作
    pid_t id = fork();//创建子进程

    if(id<){//子进程创建出错
        perror("fork");
    }else if(id>){//father
        exit();//父进程直接退出,进程转到后台运行
    }else{//child
        setsid();//创建新的会话

        chdir("/");//设置工作目录为根目录

        close();//因为控制终端现在只是文件,关闭默认打开的文件描述符,节省资源,防止终端文件不能卸下
        close();
        close();

        signal(SIGCHLD,SIG_IGN);
    }
}
int main()
{
    create_daemon();
    while()
    {
        sleep();
    }
    return ;
}
           

结果图:

守护进程Daemon---fork两次

由上图我们可以看见,运行./myDaemon命令生成了一个守护进程,TTY为?,PID、PGID、SID都相同,PPID为1,父进程为init进程,都说明该进程为守护进程;

  • fork两次
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>

void create_daemon()
{
    umask();
    pid_t id = fork();//fork一次

    if(id<){
        perror("fork");
    }else if(id>){//father
        exit();
    }else{//child
        setsid();

        signal(SIGCHLD,SIG_IGN);

        if(id = fork()<){//fork第二次
            perror("fork");
        }else if(id > ){
            exit();
        }else{
            chdir("/");
            close();
            close();
            close();
        }
    }
}
int main()
{
    create_daemon();
    while()
    {
        sleep();
    }
    return ;
}
           

结果图:

守护进程Daemon---fork两次

可以看到,fork两次后,pid 与PGID和SID不相同;

7.fork一次与fork两次的区别:

8.我们也可以通过系统调用函数来创建守护进程

守护进程Daemon---fork两次
  • nochdir表示是否要将当前工作目录;当为0时,修改其为根目录”/“,否则不修改
  • noclose表示是否要关闭终端原来打开的文件;当为0时,将标准输入输出、标准错误关闭,否则不关闭;

继续阅读