守护进程深度分析

思考

代码中创建的会话,如何关联控制终端?

新会话关联控制终端的方法

会话首进程成功打开终端设备 (设备打开前处于空闲状态)

  • 1、关闭标准输入输出和标准错误输出
  • 2、将 stdin 关联到终端设备:STDIN_FILENO => 0
  • 3、将 stdout 关联到终端设备:STDOUT_FILENO => 0
  • 4、将 stderr 关联到终端设备:STDERR_FILENO => 0

一些相关推论

新会话关联控制终端后,会话中所有进程生命期与控制终端相关

只有会话首进程能够关联控制终端 (会话中的其他进程不行)

进程的标准输入输出与标准错误输出可以进程重定向

  • 由描述符 0,1,2 决定重定向的目标位置 (按顺序打开设备)
  • 控制终端与进程的标准输入输出以及标准错误输出无直接关系

一个大胆的想法

会话首进程会关联到伪终端从设备

代码示例

会话与终端深度实验

master.c

#define _XOPEN_SOURCE  600
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main()
{
    char rx = 0;
    int master = 0;
    int c = 0;
    
    master = posix_openpt(O_RDWR); // gnome-terminal
    
    if( master > 0 )
    {
        grantpt(master);
        unlockpt(master);
        
        printf("Slave: %s\n", ptsname(master));
        
        while( (c = read(master, &rx, 1)) == 1 )
        {
            if( rx != '\r' )
            {
                printf("%c", rx);
            }
        }
        
        close(master);
    }
    else
    {
        printf("create pty error...\n");
    }
    
    return 0;
}

session.c


#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    int pid = 0;
    int i = 0;
    
    if( (pid = fork()) > 0 )
    {
        printf("parrent = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
        printf("new: %d\n", pid);
        exit(0);
    }
    else if( pid == 0 )
    {
        setsid();
        
        sleep(200);
        
        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        close(STDERR_FILENO);
        
        i += open(argv[1], O_RDONLY);  // 0 --> STDIN
        i += open(argv[1], O_WRONLY);  // 1 --> STDOUT
        i += open(argv[1], O_RDWR);    // 2 --> STDERR
        
        printf("child = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
        printf("i = %d\n", i);
    }
    else
    {
        printf("fork error...\n");
    }  
    
    sleep(240);
    
    return 0;
}

在 master.c 中,我们创建一个伪终端,读取伪终端主设备接收到的消息并打印出来

在 session.c 中,我们创建一个子进程并通过 setsid() 让子进程成为会话首进程,此时子进程的标准输入输出和标准错误输出连接到的是父进程的控制终端,标准输入对应文件描述符 0,标准输出对应文件描述符 1,标准错误对应文件描述符 2。第 25 - 27 行,我们将这三个文件描述符关闭,切断了原先的连接;第 29 - 31 行,我们将子进程关联到新的控制终端,并将子进程的标准输入输出和标准错误输出重定向到新的控制终端中去,这样输出的消息是送到新的控制终端中去,而不是送到父进程的控制终端中去

程序运行结果如下图所示:

第一个阶段,子进程通过 setsid() 成为会话首进程,然后 sleep 200s

此时,子进程的 PID == PGID == SID,说明子进程成功成为了会话首进程,但通过 TTY 和 TPGID 两个字段可以看出,此时子进程未关联任何终端

第二个阶段,子进程打开伪终端从设备,与伪终端相关联,并将子进程的标准输入输出和标准错误输出重定向到伪终端从设备中

此时 子进程的 TTY 字段为 pts/2,说明子进程已经成功关联到伪终端 pts/2 了,然后可以看到 master.out 中接收到了子进程要打印出的消息,这是因为子进程重定向标准输入输出和标准错误输出到伪终端从设备中,伪终端从设备接收到消息后会发送给伪终端主设备,master.out 中会将伪终端主设备接收到的消息打印出来

第三个阶段,子进程处于 240s 的睡眠中,我们手动关闭父进程的控制终端,另起一个终端来查看子进程的相关信息

子进程依旧在运行,这是因为子进程已经关联到伪终端 pts/2 中去了,只要 pts/2 没有关闭,则子进程依旧会继续运行

最后,我们将 master.out 程序退出执行,则伪终端 pts/2 也会被关闭,这时我们查看子进程的相关信息

可以看到子进程运行结束了,这是因为子进程关联到了伪终端 pts/2,pts/2 被关闭的话,则这个伪终端所关联的所有进程都会结束运行

什么是守护进程 (Daemon) ?

守护进程是系统中执行任务的后台进程

  • 不与任何终端相关联 (不接收终端相关的信号)
  • 生命周期长,一旦启动,正常情况下不会终止 (直到系统退出)
  • Linux 大多服务器使用守护进程实现 (守护进程名以后缀 d 结尾)

守护进程的创建步骤

1、通过 fork() 创建新进程,成功后,父进程退出

2、子进程通过 setsid() 创建新会话

3、子进程通过 fork() 创建孙进程 (肯定不是会话首进程)

4、孙进程修改模式 umask(),改变工作目录为 "/"

5、关闭标准输入输出和标准错误输出

6、重定向标准输入输出和标准错误输出 ("/dev/null")

守护进程关键点分析

父进程创建子进程是为了创建新会话

子进程创建孙进程是为了避免产生控制进程

孙进程不是会话首进程,所以不能关联终端

重定向操作可以避开奇怪的进程输出行为

创建守护进程

first-d.c


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


int main(int argc, char* argv[])
{
    int pid = 0;
    int i = 0;
    
    if( (pid = fork()) > 0 ) // 1
    {
        printf("parrent = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
        printf("child: %d\n", pid);
        exit(0);
    }
    else if( pid == 0 )
    {
        setsid(); // 2
        
        if( (pid = fork()) > 0 )  // 3
        {
            printf("child = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
            printf("groundson: %d\n", pid);
            exit(0);
        }
        
        if( pid == 0 )
        {   
            // 4
            umask(0); 
            chdir("/"); 
            // 5
            close(STDIN_FILENO);
            close(STDOUT_FILENO);
            close(STDERR_FILENO);
            // 6
            i += open("/dev/null", O_RDONLY);  // 0 --> STDIN
            i += open("/home/book/dt_linux/10/d.log", O_WRONLY);  // 1 --> STDOUT
            i += open("/dev/null", O_RDWR);    // 2 --> STDERR
            
            printf("child = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
                       
            while( 1 )
            {
                // do something
                printf("i = %d\n", i++);
                sleep(1);
                fflush(stdout);
            }
        }
    }
    else
    {
        printf("fork error...\n");
    }  
    
    return 0;
}

第 44 行,我们将守护进程的标准输出重定向到一个文件中,是因为大多数情况下,我们需要查看守护进程的一些日志,所以我们需要将标准输出重定向到文件中,不需要日志的话,可以将标准输出重定向到 "/dev/null"

第 54 行,我们手动调用 fflush() 是因为,默认写文件是全缓冲,我们写的数据首先会放到一段大小的缓冲区中去,只有这片缓冲区填满时,才会进行 IO 操作,将填满的缓冲区数据写入到磁盘中去,这样是可以提高效率;手动调用 fflush() 后,不管缓冲区有多少数据,都会写到磁盘中去

程序运行结果如下图所示:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值