当前位置: 首页 > news >正文

Linux——守护进程

Linux——守护进程

目录

一、守护进程

1.1 概念

1.2 特点

1.3 守护进程的典型应用场景

 1.5 编程流程

1. fork() 退出父进程

2. setsid() 创建新会话

3. fork() 退出父进程 / 丢弃组长和会话首进程的身份,防止

4. chdir("/")

5. umask(0)

6. close()

1.6 守护进程实现:定时记录系统时间到日志文件


一、守护进程

1.1 概念

也叫精灵进程。在后台运行不和用户交互,要给信息让用户看只能写到文件中。日志。

会话:会话首进程  eg打开终端,在打开终端时和内核建立了会话。进程组也有id用进程组组长的gid标识,方便控制。每个会话都有sid

进程组:组长进程

1.2 特点

后台运行:脱离终端控制,不会因终端关闭而终止。

生命周期长:通常在系统启动时创建,直到系统关闭才终止。

无控制终端:没有关联的控制终端(TTY),无法直接接收用户输入。

权限特殊:部分守护进程需要 root 权限(如网络服务、日志服务)。

命名规范:通常以 d 结尾(如 sshd, crond, httpd)。

1.3 守护进程的典型应用场景

系统服务:如网络服务(sshd, httpd)、定时任务(crond)、日志服务(syslogd)。

监控任务:监控系统资源(如内存、磁盘)、检测硬件状态。

批处理作业:定期执行备份、数据同步等任务。

1.4 代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int main() {// 使用printf函数输出进程ID(pid)、会话ID(sid)和进程组ID(pgid)printf("pid=%d,sid=%d,pgid=%d\n", getpid(), getsid(0), getpgrp());return 0;
}
  • getpid() 获取当前进程ID
  • getsid(0) 获取当前进程所属会话 ID,参数 0 表示获取自身会话 ID
  • getpgrp() 获取当前进程所属进程组 ID

会话id没有变化,因为没有换终,打开终端运行的第一个进程就是bash,bash的id等于会话id。进程组id等于进程id是因为此时组内只有一个进程

现在增加进程fork增加子进程

第一个进程和组进程id相同,所以说明它是组长进程。子进程组进程也是3755说明它和第一个进程处于一个进程组中

 1.5 编程流程

1. fork() 退出父进程

  • 代码行为:使用 fork() 系统调用创建一个子进程,父进程执行 exit() 退出 。
  • 原理fork() 会复制当前进程(父进程),产生一个新的子进程,二者几乎拥有相同的资源副本。父进程退出后,子进程就成为了孤儿进程,会被 init 进程(在现代 Linux 系统中多为 systemd 进程,进程号通常为 1 )收养。这样做的目的是让子进程在后台运行,脱离父进程的控制终端,为后续成为守护进程做准备。如果父进程不退出,子进程和父进程可能会产生一些关联和干扰,比如共享控制终端等,不利于子进程在后台独立运行。

2. setsid() 创建新会话

  • 代码行为:子进程调用 setsid() 函数创建一个新的会话,子进程成为这个新会话的首进程
  • 原理:会话是一组进程的集合,会话首进程具有特殊地位。调用 setsid() 后,子进程与原来的控制终端脱离关系,不再受原来终端的控制(比如终端关闭、用户注销),并且获得了一个新的进程组和会话。这是守护进程在后台持续稳定运行的关键一步,使其能够独立于用户的终端操作。只有非进程组组长才能成功调用 setsid 创建新会话

3. fork() 退出父进程 / 丢弃组长和会话首进程的身份,防止

  • 代码行为:再次调用 fork() 创建子进程,然后让当前的父进程(即之前 setsid 后的会话首进程 )退出。
  • 原理:虽然之前调用 setsid 使子进程成为会话首进程,但会话首进程仍有重新获得控制终端的潜在可能(比如某些特殊情况或误操作 )。再次 fork() 并让会话首进程退出后,剩下的子进程就不再是会话首进程,进一步确保守护进程无法再关联到控制终端,更加彻底地在后台运行。

4. chdir("/")

  • 代码行为:使用 chdir() 函数将当前工作目录更改为根目录 / 。
  • 原理:守护进程可能会在系统运行期间一直存在,如果其工作目录是某个特定的挂载点,当该挂载点被卸载时,可能会导致守护进程出现异常(比如文件操作失败等 )。将工作目录设置为根目录 / ,可以避免因工作目录相关问题影响守护进程的正常运行,保证其运行环境的稳定性

5. umask(0)

  • 代码行为:调用 umask() 函数,将文件模式创建掩码设置为 0
  • 原理:文件模式创建掩码(umask)会影响新创建文件和目录的默认权限。默认情况下,系统会有一个初始的 umask 值,它会屏蔽掉一些权限。将 umask 设置为 0 ,意味着守护进程后续创建的文件和目录会尽可能地拥有最大权限,方便进行文件读写等操作,避免因权限问题导致无法创建或访问相关资源。

6. close()

  • 代码行为:通常是关闭标准输入(STDIN_FILENO )、标准输出(STDOUT_FILENO )和标准错误(STDERR_FILENO )对应的文件描述符。
  • 原理守护进程在后台运行,不需要与用户的终端进行交互,也就不需要使用标准输入来接收用户输入,也不需要将输出打印到终端。关闭这些文件描述符可以避免资源浪费,并且防止意外的输入输出操作干扰守护进程的运行,同时也能避免守护进程的输出信息混乱终端显示等情况。

 

windows上的守护进程,许多正在运行但我们没有看到

1.6 守护进程实现:定时记录系统时间到日志文件


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>// 获取文件描述符表大小的函数声明(在部分系统中可能需要额外定义或链接相关库)
int getdtablesize(); // 将当前进程转化为守护进程的函数
void set_daemon() {pid_t pid = fork();// 第一次fork,父进程退出if (pid!= 0) {exit(1);}// 创建新会话setsid(); pid = fork();// 第二次fork,父进程退出if (pid!= 0) {exit(1);}// 更改工作目录到根目录chdir("/"); // 设置文件掩码umask(0); int maxfd = getdtablesize();// 关闭所有文件描述符for (int i = 0; i < maxfd; i++) {close(i);}
}int main() {// 将当前进程设置为守护进程set_daemon(); while (1) {time_t tv;// 获取当前时间戳time(&tv); FILE* fp = fopen("/tmp/c2405d.log", "a");if (fp == NULL) {// 打开文件失败则退出循环break; }// 将格式化后的当前时间写入文件fprintf(fp, "Time is %s", asctime(localtime(&tv))); fclose(fp);// 休眠5秒sleep(5); }return 0;
}

 

第二次调用fork使得剩下的新子进程不再是会话首进程,进一步确保守护进程无法再关联到控制终端,更加彻底地在后台运行。

定义一个 time_t 类型变量 tv 用于存储时间戳,并调用 time 函数获取当前时间戳,存储到 tv 中。

fprintf 函数

    • 作用:将格式化后的当前时间信息写入指定文件
    • 参数:
      • fp:文件指针(指向已打开的日志文件)。
      • "Time is %s":格式化字符串,%s 表示替换为字符串。
      • asctime(...):实际替换的时间字符串 。
      • 通过 localtime 函数可以将时间戳 tv =1612137600 (假设对应 2021 年 1 月 31 日 00:00:00 )转换为包含具体年月日时分秒等信息 struct tm 结构体
      • asctime 函数 接受一个指向 struct tm 结构体的指针 timeptr ,将其表示的时间格式化为字符串形式。格式化后的字符串格式固定,例如:"Sun Jun 6 10:23:59 2021\n" ,包含了星期、月份、日期、时间、年份信息,并且字符串末尾自动添加换行符 \n 。

  • fopen 函数

    • 作用:以指定模式打开文件,返回文件指针(FILE*)。
    • 参数:
      • 路径"/tmp/c2405d.log" 指定日志文件位置。/tmp 是临时目录,程序崩溃或重启不会保留日志,但对短期测试足够。
      • 模式"a" 表示追加模式(Append),特点:
        • 文件不存在时自动创建。
        • 文件存在时,写入指针定位到文件末尾,不会覆盖原有内容。
        • 多进程 / 线程同时写入时,操作系统保证写入操作的原子性(但可能导致日志行交叉,需更高并发控制)。
    • 返回值:
      • 成功:返回非空文件指针(FILE*)。
      • 失败:返回 NULL(如权限不足、磁盘已满、路径错误等)

相关文章:

  • 改变应用的安装目录
  • 浅谈 Redis 数据类型
  • pdf url 转 图片
  • CSV注入攻击技术解析
  • Spark SQL 读取 CSV 文件,并将数据写入 MySQL 数据库
  • 【认知思维】过度自信效应:高估自我能力的认知偏差
  • 【Pandas】pandas DataFrame cumprod
  • PostgreSQL 服务器信号函数
  • ZYNQ实战:可编程差分晶振Si570的配置与应用指南
  • 安卓刷机模式详解:Fastboot、Fastbootd、9008与MTK深刷
  • 项目:博客系统——基于SSM框架Mybatis-plus
  • 基于 Spring Boot 瑞吉外卖系统开发(十三)
  • Vxworks 系统详解
  • 装饰器在Python中的作用及在PyTorchMMDetection中的实战应用
  • 我国城市轨道交通行业人工智能大模型发布,迈向智慧化新征程​
  • 本地的ip实现https访问-OpenSSL安装+ssl正式的生成(Windows 系统)
  • Java【10_1】用户注册登录(面向过程与面向对象)
  • tomcat搭建内网论坛
  • 【论信息系统项目的资源管理】
  • docker大镜像优化实战
  • 加拿大总理宣布新内阁名单
  • “救护车”半路加价?陕西卫健委已介入,记者调查:黑救护车挤占市场
  • 演员黄晓明、金世佳进入上海戏剧学院2025年博士研究生复试名单
  • 人民日报访巴西总统卢拉:“巴中关系正处于历史最好时期”
  • 山西省委常委李金科添新职
  • 人民日报整版聚焦:专家学者看2025中国经济增长点