Linux 学习笔记13 Daemon进程

Daemon进程

守护进程(daemon)
Daemon进程运行在后台,也称为"后台服务进程"。
由于在 linux中,每一个从此终端(terminal,黑窗口)开始运行的进程都会依赖这个终端,这个终端就称为这些进程的控制终端。当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能突破这种限制,Daemon不依赖于终端,即使控制终端被关闭了,只要它被执行开始运转,就要到整个系统关闭时才会退出。Linux下常见的命令如 inetd 和 ftpd,末尾的字母 d 通常就是指 daemon。
通常情况下,会话关闭后,进程也就终止,验证代码如下:

#include <func.h>
//用于测试关闭会话后,此进程是否还存在
//测试结果:关闭会话后此进程终止                                                                              
int main()
{
    while(1);   
    return 0;
}

执行效果如下:

启动 while1 进程

daemon修改配置 daemon命令_进程组


while1进程运行

daemon修改配置 daemon命令_ubuntu_02


2号会话窗口关闭后,while1 进程也随之终止。

daemon修改配置 daemon命令_c语言_03


Daemon的特性:

1守护进程最重要的特性是后台运行。

2 其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述

符、控制终端、会话和进程组、工作目录已经文件创建掩码等。这些环境通常是守护进程

从父进程那里继承下来的。

3如何创建守护进程:

1、创建子进程,父进程退出。

2、在子进程中创建新会话:使用系统函数 setsid()。由于创建守护进程的第一步调用了 fork 函数来创建子进程,再将父进程退出。由于在调用 fork 函数的时候,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端并没有改变,因此,还不是真正意义上的独立开来。而调用 setsid 函数会创建一个新的会话并自任该会话的组长,调

用 setsid 函数有下面 3 个作用:让进程摆脱原会话的控制,让进程摆脱原进程组的控制,让进程摆脱原控制终端的控制

对上述名词解释:

进程组:是一个或多个进程的集合。进程组有进程组 ID 来唯一标识。除了进程号(PID)之外,进程组 ID(GID)也是一个进程的必备属性。每个进程都有一个组长进程,其组长进程的进程号等于进程组 ID。且该进程组 ID 不会因为组长进程的退出而受影响。

会话周期(即会话组):会话期是一个或多个进程组的集合。通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。

控制终端:由于在 linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依赖这个控制终端

3、改变当前目录为根目录

使用 fork 函数创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当前

目录所在的文件是不能卸载的(涉及Linux文件系统,挂载),这对以后的使用会造成很多的不便。利用 chdir("/");把当前工作目录切换到根目录。

4、重设文件权限掩码:

umask(0);将文件权限掩码设为 0,Deamon 创建文件不会有太大麻烦;

5、关闭所有不需要的文件描述符

新进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,而它们一直消耗系统资源。另外守护进程已经与所属的终端失去联系,那么从终端输入的字符不可能到达守护进程,守护进程中常规方法(如 printf)输出的字符也不可能在终端上显示。主要是关闭0,1,2。

例:getpgid 查看进程组的ID,代码如下:

#include <func.h>
//获取进程组ID
//
int main()
{
    pid_t pid=fork();
    if(!pid)
    {   
        printf("Child Mark2,pid=%d,ppid=%d,pgid=%d\n",getpid(),getppid(),getpgid(0));
        // If pid is zero, the process ID of the calling process is used
        // getpgid中参数为0,当前进程的id就作为进程组id
        return 0;
    }else
    {   
        printf("Parent Mark1,pid=%d,ppid=%d,pgid=%d\n",getpid(),getppid(),getpgid(0));
        //此时父进程是新进程组组长,和bash进程组不同                                                          
        wait(NULL);
        return 0;
    }   
}

执行效果如下:

daemon修改配置 daemon命令_linux_04


可见,子进程的pgid和父进程的pgid同为3847,属于同一个进程组。

例:setpgid改变进程组ID,代码如下:

#include <func.h>
//如何设置进程组id
//int setpgid(pid_t pid, pid_t pgid);
//
int main()
{
    pid_t pid=fork();
    if(!pid)
    {   
        printf("Child Mark2,pid=%d,ppid=%d,pgid=%d\n",getpid(),getppid(),getpgid(0));
        // getpgid中参数为0,当前进程的id就作为进程组id
        setpgid(0,0);
        //0代表子进程自己成立一个进程组
        printf("Adult Mark2,pid=%d,ppid=%d,pgid=%d\n",getpid(),getppid(),getpgid(0));                         
        return 0;
    }else
    {   
        printf("Parent Mark1,pid=%d,ppid=%d,pgid=%d\n",getpid(),getppid(),getpgid(0));
        //此时父进程是新进程组组长,和bash进程组不同
        wait(NULL);
        return 0;
    }   
}

执行效果如下:

daemon修改配置 daemon命令_daemon修改配置_05


子进程独立创建一个进程组4038,并且子进程自为进程组组长。

例:getsid获取会话组ID,代码如下:

#include <func.h>
//获取会话组id
//
int main()
{
    pid_t pid=fork();
    if(!pid)
    {   
        printf("Child Mark2,pid=%d,ppid=%d,pgid=%d,sid=%d\n",getpid(),getppid(),getpgid(0),getsid(0));
        // If pid is zero, the process ID of the calling process is used
        // getpgid中参数为0,当前进程的id就作为进程组id
        return 0;
    }else
    {   
        printf("Parent Mark1,pid=%d,ppid=%d,pgid=%d,sid=%d\n",getpid(),getppid(),getpgid(0),getsid(0));
        //sid是bash的id,bash即当前会话组                                                                     
        wait(NULL);
        return 0;
    }   
}

执行效果如下:

daemon修改配置 daemon命令_ubuntu_06


可见,子进程的会话组id和父进程相同。

进程不能从一个会话组,转移到另一个会话组,只能够创建新会话。
例:setsid创建新会话组,代码如下:

#include <func.h>
//如何设置会话组id
//setsid

int main()
{
    pid_t pid=fork();
    if(!pid)
    {   
        printf("Child Mark2,pid=%d,ppid=%d,pgid=%d,sid=%d\n",getpid(),getppid(),getpgid(0),getsid(0));
        setsid();
        //设置会话组id
        printf("Adult Mark2,pid=%d,ppid=%d,pgid=%d,sid=%d\n",getpid(),getppid(),getpgid(0),getsid(0));
        while(1);//此时子进程就是一个daemon进程,此时查看进程状态                                             
        return 0;
    }else
    {   
        printf("Parent Mark1,pid=%d,ppid=%d,pgid=%d,sid=%d\n",getpid(),getppid(),getpgid(0),getsid(0));
        //sid是bash的id,bash即当前会话组
        //wait(NULL);
        return 0;
    }   
}

执行效果如下:

daemon修改配置 daemon命令_进程组_07


此时 setid 被由祖先进程( init )所接管,会话组id也成为了4395。

例:创建一个daemon进程,代码如下:

#include <func.h>
//如何创建一个daemon进程                                                                                      
//
int main()
{
    if(fork())
    {   
        exit(0);//1父进程退出
    }else
    {   
        setsid();//2成立新会话
        chdir("/");//3改变目录到根目录
        umask(0);//4重设文件权限掩码
        for(int i=0;i<3;i++)
        {   
            close(i);//5关闭所有不需要的文件描述符
        }   
        while(1);//让进程不要立刻结束,便于查看进程
    }   
    return 0;
}

执行效果如下:

daemon修改配置 daemon命令_c语言_08

补充:子进程创建会话组的过程

daemon修改配置 daemon命令_ubuntu_09