[Linux_OS] core dump | session | 守护进程daemon

发布于:2025-04-02 ⋅ 阅读:(52) ⋅ 点赞:(0)

在上一篇文章当中我们讲到了 status 的结构[一定能看懂的] 进程管理 | 等待进程 | Waitpid | status | pid重用,其中涉及到的 核心转储 部分当时没有讲

我们可以在这里结合信号,来进行一个感性的理解,其实主要就是在 gdb 调试当中使用的,core ==term +core dump,Core退出的可以被核心转储的以便于后序快速定位问题。

云服务器未默认采用核心转储 是架构特性与运维需求共同作用的结果,但其提供的替代方案在效率和安全性上更适应云计算场景

维度 核心转储 云服务器常规方案
故障定位粒度 内存级精确 日志/指标级
资源消耗 高(GB级存储) 低(KB-MB级日志)
安全性 高风险(含敏感数据) 可控(结构化日志过滤)
适用场景 本地物理服务器调试 分布式云环境运维

关于其中涉及到信号部分的理解,可以见去年暑假的这篇文章:[Linux][OS][详解信号的产生]


会话 | 守护进程

让我们用 公司组织架构 的比喻来解释这些概念

一、终端(Terminal)—— 公司的前台

生活实例
公司的前台是员工与外部沟通的接口,负责接收客户需求、转接电话等。
技术解释
终端是用户与操作系统交互的入口(如 Windows 的命令行或 Mac 的 Terminal)。所有输入输出都通过终端完成。

# 查看当前终端关联的进程
echo $$  # 输出当前终端进程的 ID

二、进程组(Process Group)—— 项目组

生活实例
公司接到一个项目,成立一个项目组(包含开发、测试、设计等成员)。
技术解释
进程组是多个相关进程的集合,通常由一条 Shell 命令创建(如管道命令)

#include <stdio.h>
#include <unistd.h>
int main() {
    pid_t pid = fork();  // 创建子进程
    if (pid == 0) {
        printf("子进程 PID=%d,进程组 ID=%d\n", getpid(), getpgrp());
    } else {
        printf("父进程 PID=%d,进程组 ID=%d\n", getpid(), getpgrp());
    }
    sleep(10);
    return 0;
}

运行结果
父进程和子进程属于同一个进程组,组 ID 是父进程的 PID(类似项目组组长)


三、会话(Session)—— 项目周期

生活实例
公司启动一个长期项目周期,包含多个阶段(需求、开发、测试等),每个阶段对应一个项目组。
技术解释
会话是多个进程组的集合,由用户登录终端时创建。一个会话对应一个终端。

# 查看当前会话 ID
ps -o pid,ppid,pgid,sid,comm

关键特性

  • 关闭终端(如退出 SSH)会向会话发送 SIGHUP 信号,导致会话终止
  • 前台进程组(如正在运行的命令)可以接收终端输入,后台进程组则不行

四、守护进程(Daemon)—— 24 小时值班的保安

生活实例
保安需要 7x24 小时工作,不受公司前台关闭影响,所以说其实可以理解为它就是一个后台进程组
技术解释
守护进程是脱离终端、长期运行的后台进程(如 Nginx、MySQL)
代码示例:

  • 通过fork创建一个子进程,此时父子进程属于同一个进程组,父进程是组长。
  • 如果id > 0说明是父进程,父进程直接退出,一个进程组中,进程组长退出,进程组其他进程继续运行,不会退出。
  • 因此子进程不会退出,继承了父进程的所有信息,并且还不是进程组长。

于是子进程调用setsid,自己创建一个会话,自己做话首进程,至此子进程就是一个守护进程了!

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

int main() {
    pid_t pid = fork();
    if (pid > 0) exit(0);  // 1. 父进程退出
    
    setsid();               // 2. 创建新会话(脱离原终端)
    chdir("/");             // 3. 切换工作目录到根
    umask(0);               // 4. 重置文件权限掩码
    
    // 关闭所有文件描述符(此处简化为关闭标准流)
    close(0); close(1); close(2);
    
    while(1) {              // 5. 守护进程主逻辑
        // 执行定时任务(如日志清理)
        sleep(3600);
    }
    return 0;
}

关键点

  • setsid():脱离原终端,成为新会话的首进程

关闭文件描述符:避免占用终端资源

  • ( 子进程的 三个标准流,都指向/dev/pts/0,这就是当前的终端文件。
  • 如果当前终端退出,那么如果这个守护进程还向终端输出内容,就会导致错误
  • 因此还要改变它的输出流
  • /dev/null是一个Linux提供的文件,它可以接收任何输入,但是不论输入什么都会被系统丢弃。因此如果一个程序有输出,但是又不希望接收到它的输出时,就可以把输出重定向到/dev/null下。
// 关闭标准输入、输出和错误
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

// 打开/dev/null作为标准输入、输出和错误
open("/dev/null", O_RDONLY);
open("/dev/null", O_WRONLY);
open("/dev/null", O_WRONLY);

五、setsid

比喻
保安队长(守护进程)需要:

  1. 脱离原部门(终端会话) → setsid()
  2. 自己成立新部门(新会话) → 成为会话组长
  3. 不再受原部门影响 → 独立运行

技术总结

概念

比喻

代码/命令

关键特性

终端

公司前台

echo $$

用户交互入口

进程组(父子)

项目组

getpgid()

共享终端信号

会话

项目周期

ps -o sid

关联终端,管理多个进程组

守护进程

24小时保安

setsid() + 后台运行

脱离终端,长期运行



daemon
  • setsid的用法还是有点复杂了,在setsid 生成新会话后还要close,open
  • 为此Linux提供了另一个系统调用daemon
  • 它封装了上述所有过程,可以快速创建一个守护进程。需要头文件<unistd.h>

参数:

  • nochdir:改变工作目录
    • 传入0:改变工作目录为根目录
    • 传入1:保持当前工作目录
  • nclose:改变输入输出流
    • 传入0:输入输出流重定向到/dev/null
    • 传入1:不改变输入输出流

原先的代码就可以变成:

#include <unistd.h>
#include <sys/types.h>

int main()
{
	daemon(0, 0);
	while(true)
	{}
	
    return 0;
}

这就是一个功能全面的守护进程了。