守护进程(Daemon)

1.普通进程

普通进程有对应的终端,如果终端退出,那么对应的进程也就消失了,并且终端被占住了,输入各种命令这个终端都没有反应。

普通进程的父进程是一个 bash。

2.守护进程

守护进程是一种长期运行的进程(守护进程的生存期不一定长,但一般应该这样做),一般是操作系统启动的时候它就启动,操作系统关闭的时候它才关闭。

守护进程跟终端无关联,也就是说它们没有控制终端,所以控制终端退出,也不会导致守护进程退出。

守护进程是在后台运行的,不会占着终端,终端可以执行其他命令。

Linux 操作系统本身是有很多的守护进程在默默地运行,维持着系统的日常活动。

init 是系统守护进程,它负责启动各运行层次特定的系统服务,所以很多进程的 PPID 是 init,并且这个 init 也负责收养孤儿进程。

在这里插入图片描述

在这里插入图片描述

大多数守护进程都是以超级用户特权运行的,守护进程没有控制终端,TTY 这列显示的是 ?

  • 内核守护进程以无控制终端方式启动
  • 普通守护进程无控制终端可能是守护进程调用了 setsid 的结果

3.文件描述符

文件描述符其实就是一个正数,用来标识一个文件。当我们打开一个存在的文件或者创建一个新文件,操作系统都会返回这个文件描述符(其实就是代表这个文件的),后续对这个文件的操作的一些函数,都会用到这个文件描述符作为参数。

Linux 中有三个特殊的文件描述符,数字分别为 012

  • 0:标准输入(键盘),对应的符号常量叫 STDIN_FILENO
  • 1:标准输出(屏幕),对应的符号常量叫 STDOUT_FILENO
  • 2:标准错误(屏幕),对应的符号常量叫 STDERR_FILENO

类 Unix 操作系统默认从 STDIN_FILENO 读数据,向 STDOUT_FILENO 来写数据,向 STDERR_FILENO 来写错误。

类 Unix 操作系统中“一切皆文件”,与其说把标准输入、标准输出、标准错误都看成文件,倒不如说像看待文件一样看待标准输入、标准输出、标准错误,像操作文件一样操作标准输入、标准输出、标准错误。

同时,程序一旦运行起来,这三个文件描述符 012 会被自动打开,自动指向对应的设备。

虽然文件描述符是数字,但为了更好地理解,我们可以把文件描述符看成指针。

在这里插入图片描述

4.输入输出重定向

输出重定向:在命令行中用 > 即可。

在这里插入图片描述

在这里插入图片描述

输入重定向:在命令行中用 < 即可。

在这里插入图片描述

在这里插入图片描述

5.空设备

/dev/null 是一个特殊的设备文件,它丢弃一切写入其中的数据(像黑洞一样)。

6.守护进程的实现范例

守护进程编写规则:

  1. fork() 一个子进程出来,然后父进程退出。fork() 的目的是想成功调用 setsid() 来建立新会话,目的是让子进程有单独的 sid,而且子进程也成为了一个新进程组的组长进程,同时子进程不关联任何终端了。

  2. 调用 umask(0),其中 umask 是个函数,将其设置为 0 0 0,不要让它来限制文件权限,以免引起混乱。

  3. 守护进程是在后台运行,它不应该从键盘上接收任何东西,也不应该把输出结果打印到屏幕或者终端上来,所以,一般按照江湖规矩,我们要把守护进程的标准输入、标准输出重定向到空设备,从而确保守护进程不从键盘接收任何东西,也不把输出结果打印到屏幕。

  4. 守护进程虽然可以通过终端启动,但是和终端不挂钩。守护进程可以用命令启动,如果想开机启动,则需要借助系统初始化脚本来启动。

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

// 创建守护进程,创建成功则返回1,否则返回-1
int ngx_daemon()
{
    int fd;
    
    switch (fork()) // fork()子进程
    {
        case -1:
            // 创建子进程失败,这里可以写日志等等
            return -1;
        case 0:
            // 子进程,走到这里,直接break
            break;
        default:
            // 父进程,直接退出
            exit(0);
    }

    // 只有子进程流程才能走到这里
    if (setsid() == -1) // 脱离终端,终端关闭,将跟此子进程无关
    {
        // 记录错误日志等等
        return -1;
    }

    umask(0); // 设置为0,不要让它来限制文件权限,以免引起混乱
    
    fd = open("/dev/null", O_RDWR); // 打开空设备,以读写方式打开

    if (fd == -1)
    {
        // 记录错误日志等等
        return -1;
    }

    // dup2先关闭STDIN_FILENO(这是规矩,已经打开的描述符,动它之前,先close)
    // dup2复制文件描述符,类似于指针赋值,把第一个参数指向的内容赋给了第二个参数,相当于指针指向null,让/dev/null成为标准输入
    if (dup2(fd, STDIN_FILENO) == -1)
    {
        // 记录错误日志等等
        return -1;
    }

    // dup2先关闭STDOUT_FILENO(这是规矩,已经打开的描述符,动它之前,先close)
    // dup2复制文件描述符,类似于指针赋值,把第一个参数指向的内容赋给了第二个参数,相当于指针指向null,让/dev/null成为标准输出
    if (dup2(fd, STDOUT_FILENO) == -1)
    {
        // 记录错误日志等等
        return -1;
    }

    if (fd > STDERR_FILENO) // fd应该是3,这个应该成立
    {
        if (close(fd) == -1) // 释放资源,这样这个文件描述符就可以被复用,不然这个数字(文件描述符)会被一直占着
        {
            // 记录错误日志等等
            return -1;
        }
    }

    return 1;
}

int main(int argc, char* const* argv)
{
    if (ngx_daemon() != 1)
    {
        // 创建守护进程失败,可以做失败后的处理,比如写日志等等
        return 1;
    }
    else
    {
        // 创建守护进程成功,执行守护进程中要干的活
        for (;;)
        {
            sleep(1);
            // 这里就算打印也没用,现在标准输出指向空设备(/dev/null),打印不出任何结果(不显示任何结果)
            printf("休息1秒,进程id=%d!\n", getpid());
        }
    }

    return 0;
}

在这里插入图片描述

7.守护进程不会收到的信号

守护进程不会收到来自内核的 SIGHUP 信号,也就是说,如果守护进程收到了 SIGHUP 信号,那么肯定是另外的进程发的。

很多守护进程把 SIGHUP 信号作为通知信号,表示配置文件已经发生改动,守护进程应该重新读入其配置文件。

守护进程不会收到来自内核的 SIGINT 信号(Ctrl+C)、SIGWINCH 信号(终端窗口大小改变)。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值