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

孤儿进程、僵尸进程和守护进程

孤儿进程

如果父进程先于子进程退出,则子进程成为孤儿进程,此时将自动被PID为1的进程收养, PID为1的进程就成为了这个进程的父进程。

当一个孤儿进程退出以后,它的资源清理会交给它的父进程来处理。

#include <testfun.h>
int main(){if(fork() == 0){while(1){sleep(1);}}else{}return 0;
}

ps -elf查看 

再使用 kill -9 <PID>  # 强制终止进程   我这里是kill -9 28667

 僵尸进程

如果子进程先退出,系统不会自动清理掉子进程的环境,而必须由父进程调用wait或waitpid函数来完成清理工作。

如果父进程不做清理工作,则已经退出的子进程将成为僵尸进程(defunct),在系统中如果存在的僵尸进程过多,将会影响系统的性能, 所以必须对僵尸进程进行处理。

当一个进程执行结束时,它会向它的父进程发送一个SIGCHLD/终止信号,从而父进程可以根据子进程的终止情况进行处理。

在父进程处理之前,内核必须要在进程队列当中维持已经终止的子进程的PCB。如果僵尸进程过多,将会占据过多的内核态空间。并且僵尸进程的状态无法转换成其他任何进程状态。

#include <unistd.h>  // 提供 fork() 函数声明int main() {// 调用 fork() 创建子进程if (fork() == 0) {  // fork() 返回 0 表示当前是子进程// 子进程执行的代码块// 这里为空,子进程会直接执行后面的 return 0} else {// 父进程执行的代码块(fork() 返回子进程的 PID)while (1) {  // 无限循环,使父进程持续运行// 循环体为空,父进程会一直占用 CPU}}// 子进程会执行到这里(因为 if 块为空)// 父进程不会执行到这里(因为 while(1) 是无限循环)return 0;  // 子进程正常退出
}

ps -elf查看 

孤儿进程 vs 僵尸进程

类型定义如何终止
孤儿进程父进程已终止,由 init 接管kill -9 <PID>
僵尸进程已终止但未被父进程 wait()需终止其父进程或重启系统

 wait函数

wait函数是一个系统调用函数。

wait函数的作用是等待一个已经退出的子进程,并进行清理回收子进程资源工作。

  1. wait 随机地等待一个已经退出的子进程,并返回该子进程的PID, 并给子进程进程资源清理和回收
  2. wait是一个阻塞函数, wait会一直阻塞直到等到一个结束的子进程, 解除阻塞.(前提是有子进程)(没有子进程的时候, 直接返回-1)

其函数声明如下:

// man 2 wait#include <sys/types.h>
#include <sys/wait.h>
//wait for process to change state
pid_t wait(int *wstatus);
// 返回值, 成功返回子进程的ID, 失败返回-1

wstatus: 了解

  1. 该参数是一个整型指针, 用来存储进程的退出状态(可以用宏解析)
  2. 这个wstatus的整型内存区域中由两部分组成,其中一些位用来表示退出状态(当正常退出时),而另外一些位用来指示发生异常时的信号编号。
  3. 我们可以通过一些宏检查状态的情况。 (参考: man 2 wait)
说明
WIFEXITED(wstatus)子进程正常退出的时候返回真,此时可以使用WEXITSTATUS(wstatus),获取子进程的返回情况
WIFSIGNALED(wstatus)子进程异常退出的时候返回真,此时可以使用 WTERMSIG(wstatus)获取信号编号,可以使用 WCOREDUMP(wstatus)获取是否产生core文件
WIFSTOPPED(wstatus)子进程暂停的时候返回真,此时可以使用WSTOPSIG(wstatus)获取信号编号
...

EgCode:

#include <testfun.h>  // 自定义头文件(假设包含必要的函数声明)
#include <stdio.h>    // 提供 printf() 函数声明
#include <unistd.h>   // 提供 fork(), sleep(), wait() 等函数声明
#include <sys/wait.h> // 提供 wait() 相关的宏(WIFEXITED等)int main() {// 创建子进程if (fork() == 0) {  // 子进程执行的代码块(fork()返回0)printf("child process \n");  // 打印子进程标识sleep(20);                  // 子进程休眠20秒(模拟耗时操作)return 100;                 // 子进程退出,返回状态码100} else {// 父进程执行的代码块(fork()返回子进程PID)int status;                // 存储子进程退出状态int s_pid = wait(&status);  // 阻塞等待任意子进程结束,并获取其PID和状态printf("wait child: child pid=%d \n", s_pid);  // 打印被回收的子进程PID// 判断子进程退出方式if (WIFEXITED(status)) {  // 子进程正常退出(调用return或exit)printf("child status: %d \n", WEXITSTATUS(status));  // 打印子进程退出码(100)} else if (WIFSIGNALED(status)) {  // 子进程因信号终止(如kill -9)printf("child signed: %d \n", WTERMSIG(status));  // 打印终止信号的编号}}return 0;  // 父进程退出
}

waitpid函数 

函数区别
wait(&status)等待 任意一个子进程 结束。
waitpid(pid, &status, options)可以指定等待某个子进程(pid),并支持非阻塞模式(WNOHANG)。

waitpid函数也是一个系统调用函数。

waitpid函数的作用是等待一个已经退出的子进程,并进行清理工作。

  1. waitpid 等待一个指定退出的子进程,并返回该子进程的PID
  2. waitpid 是一个阻塞函数

其函数声明如下:

#include <sys/types.h>
#include <sys/wait.h>
// wait for process to change state
pid_t waitpid(pid_t pid,      // 指定等待的PID的子进程int *wstatus,   // 存储进程的退出状态int options     // 修改 waitpid 的行为的选项, 默认0
);
// 返回值:  返回值, 成功返回子进程的ID, 失败返回-1。
// 返回值:  如果在options参数上使用WNOHANG选项,且没有子进程退出:返回0

 pid参数:pid参数可以控制支持更多种模式的等待方式。

表 3. PID数值效果

PID数值效果
< -1等待进程PID和pid绝对值的进程
== -1等待任一个子进程, 等价于wait
== 0 等待同一进程组的任意子进程

什么是进程组 ?

进程组是一个或多个进程的集合:

  1. 每个进程除了是一个单独的进程,还归属于某一个进程组; 所以进程不仅有进程ID, 还有进程组ID
  2. 当使用shell创建进程的时候,除了这个进程被创建,这个进程将会创建一个进程组, 并作为进程组的组长。
  3. 组长的PID就是进程组ID。
  4. 通过fork创建一个子进程时,子进程默认和父进程属于同一个进程组。
  5. 只要进程组当中存在至少一个进程(这个进程即使不是组长),该进程组就存在。
  6. getpgrp()函数获得进程组ID。

options参数:

  1. waitpid是阻塞函数,如果给waitpid 的options参数设置一个名为WNOHANG的宏,则系统调用会变成非阻塞模式。
  2. 如果默认阻塞: 填0。

EgCode:

#include <testfun.h>  // 自定义头文件(假设包含必要的函数声明)
#include <stdio.h>    // 提供 printf() 函数声明
#include <unistd.h>   // 提供 fork(), sleep() 函数声明
#include <sys/wait.h> // 提供 waitpid() 及相关宏定义int main() {// 创建子进程if (fork() == 0) {// 子进程执行的代码块printf("child process \n");  // 打印子进程标识信息sleep(20);                  // 子进程休眠20秒(模拟耗时操作)return 100;                 // 子进程退出,返回状态码100} else {// 父进程执行的代码块int status;                 // 用于存储子进程退出状态/* 两种waitpid调用方式:* 1. 阻塞方式(0):父进程会一直等待子进程结束* 2. 非阻塞方式(WNOHANG):立即返回,不等待*/// int s_pid = waitpid(-1, &status, WNOHANG); // 非阻塞方式int s_pid = waitpid(-1, &status, 0);       // 阻塞方式(常用)// 判断waitpid的返回结果if (s_pid == 0) {// 只有在WNOHANG模式下可能返回0,表示没有子进程结束printf("no child process end \n");} else {// 正常获取到结束的子进程printf("wait child: child pid=%d \n", s_pid);  // 打印被回收的子进程PID// 解析子进程退出状态if (WIFEXITED(status)) {// 子进程正常退出printf("child status: %d \n", WEXITSTATUS(status));  // 打印退出码} else if (WIFSIGNALED(status)) {// 子进程被信号终止printf("child signed: %d \n", WTERMSIG(status));    // 打印信号编号}}}return 0;  // 父进程退出
}

守护进程 

守护进程是 在后台运行的特殊进程,它脱离终端控制,通常用于提供系统服务(如网络、日志、定时任务等)。它没有控制终端(TTY),生命周期长,随系统启动而运行,直到系统关闭才结束。 

    特征说明
    后台运行不占用终端,用户无法直接交互(如 sshdnginx)。
    脱离终端控制不受用户登录/注销影响(即使终端关闭,守护进程仍运行)。
    生命周期长通常在系统启动时运行,直到系统关闭。
    无控制终端其 stdin/stdout/stderr 通常重定向到 /dev/null 或日志文件。
    独立会话组调用 setsid() 创建新会话,脱离原进程组和终端关联。

     守护进程的创建流程

    1. 父进程创建子进程,然后让父进程终止。
    2. 在子进程当中创建新会话。
    3. 修改当前工作目录为根目录。(因为如果使用当前目录, 意味着当前目录活跃, 则当前目录无法在文件系统中卸载; 而根目录所在的文件系统正常来讲是一直挂载的)
    4. 重设文件权限掩码为0,避免创建文件的权限受限。
    5. 关闭不需要的文件描述符,比如0、1、2。
    #include <unistd.h>    // 提供 fork(), setsid(), chdir(), close(), sleep() 等函数
    #include <sys/stat.h>  // 提供 umask() 函数int main(int argc, char* argv[]) {// 创建子进程if (fork() == 0) {  // 子进程进入该分支(fork()返回0)// 1. 创建新会话,脱离终端控制setsid();  // 使子进程成为新会话的领头进程,脱离原终端// 2. 更改工作目录到根目录chdir("/");  // 避免守护进程阻止文件系统卸载// 3. 重设文件权限掩码umask(0);  // 确保守护进程创建文件时有最大权限(模式0777)// 4. 关闭所有文件描述符(防止资源泄漏)for (int i = 0; i < 1024; i++) {  // 遍历可能的文件描述符close(i);  // 关闭每个描述符(包括标准输入/输出/错误)}// 5. 守护进程主循环while (1) {  // 无限循环保持守护进程运行sleep(1);  // 避免CPU占用过高(实际应用会替换为任务逻辑)}}// 父进程直接退出(子进程由init接管)return 0;  
    }

    补充:job、bg、fg

    关键区别总结

    特性jobsbg  fg 
    主要作用查看作业状态恢复暂停的任务到后台back将任务调回前台front
    依赖场景需先有后台/暂停的作业需先用 Ctrl+Z 暂停任务需先用 & 或 Ctrl+Z
    输出示例[1] Running sleep 100 &[1] + Continued vim &前台占用终端(如 vim

     +:最近一个被放入后台的作业。(fg  %+=fg)

    -:倒数第二个被放入后台的作业。

    &: 表示放到后台运行

    快捷键功能信号进程状态恢复方式适用场景
    Ctrl+C强制终止当前前台进程SIGINT进程直接退出无法恢复,需重新启动想立即结束任务(如卡死的程序)
    Ctrl+Z暂停当前前台进程SIGTSTP进程暂停(Stopped)可通过 fg 或 bg 恢复临时释放终端(如暂停 vim 去执行其他命令)

    符号含义等价命令
    %1作业编号 1fg %1bg %1
    %+ 或 %%最近操作的任务(带 + 标记)fg(不指定编号)
    %-倒数第二个操作的任务fg %-

    fg %1:将作业 1 调回前台运行。

    bg %1:将作业 1 在后台继续运行(针对已暂停的任务)。 

    补充:Kill 

    kill 是 Linux/Unix 系统中用于 终止进程 的核心命令,通过向目标进程发送信号(Signal)来实现控制。

    kill命令可以用来给指定的进程发送信号。

    1. 通常用户经常会从终端启动shell再启动进程,当进程正在运行时,它可以接受一些键盘发送的信号:比如ctrl+c表示终止信号, ctrl+z表示暂停信号。这种可以直接接受键盘信号的状态被称为前台,否则称为后台。
    2. 当进程处于后台的时候,只能通过kill命令发送信号 给它。 
    kill [选项] <PID或作业号>  # 通过进程ID或作业号终止
    kill -<信号名或编号> <PID> # 发送指定信号
    信号编号信号名作用是否可捕获
    1SIGHUP终端挂断(重启配置)
    2SIGINT键盘中断(Ctrl+C)
    9SIGKILL强制终止(不可捕获/忽略)
    15SIGTERM默认终止信号(允许清理)
    19SIGSTOP暂停进程(Ctrl+Z)
    kill -15 1234      # 先尝试正常退出(如保存数据)
    kill -9 1234       # 若无效,强制终止
    kill -9 -1        # 终止当前用户的所有进程(危险!)
    kill %1           # 终止作业编号为1的任务(Shell作业控制)

    http://www.xdnf.cn/news/1197793.html

    相关文章:

  1. 【element-ui】HTML引入本地文件出现font找不到/fonts/element-icons.woff
  2. Android CameraX 使用指南:简化相机开发
  3. 从零搭建3D激光slam框架-基于mid360雷达节点实现
  4. [10月考试] C
  5. 论文阅读-IGEV
  6. Java进阶7:Junit单元测试
  7. Windows10系统使用Cmake4.1.0构建工具+Visual Studio2022编译Opencv4.11教程
  8. LabelImg:简洁高效的图像标注工具和下载
  9. B站直播视频 | 深度讲解 Yocto 项目:从历史、架构到实战与趋势
  10. Vue vuex模块化编码
  11. 网络基础19:OSPF多区域实验
  12. 中级全栈工程师笔试题
  13. Maven之多模块项目管理
  14. 什么是加密货币中的节点?
  15. 【Linux系统编程】环境变量,进程地址空间与进程控制
  16. 使用GIS中基于森林的分类与回归模型来估算房屋价值
  17. 工业控制系统安全之 Modbus 协议中间人攻击(MITM)分析与防范
  18. 解决ubantu系统下matplotlib中文乱码问题
  19. 逆向入门(43)程序逆向篇-tsrh-crackme
  20. 【笔记】系统
  21. 20250727让飞凌OK3576-C开发板在Rockchip的原厂Android14下通过耳机播音
  22. 【设计】设计一个web版的数据库管理平台后端(之二)
  23. 29.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--用户配置服务
  24. Java中排序规则详解
  25. solidity从入门到精通 第六章:安全第一
  26. vmware虚拟机中 ubuntu 20.04通过nat设置静态ip(固定ip)
  27. Java学习-------桥接模式
  28. 文件权限标记机制在知识安全共享中的应用实践
  29. 通信名词解释:I2C、USART、SPI、RS232、RS485、CAN、TCP/IP、SOCKET、modbus
  30. 基于开源AI智能名片链动2+1模式S2B2C商城小程序的人格品牌化实现路径研究