特殊进程——孤儿、僵尸、守护进程

一、孤儿进程

定义:当一个进程的父进程先于它终止(父进程意外崩溃、被强制终止如kill -9,或父进程主动退出但未正确处理子进程),该进程就会成为孤儿进程。

特点:孤儿进程是一种正常进程,他不是一种状态。

  • 失去父进程后,会被系统的"祖先进程"(linux中是 init 或 system,PID=1)收养;最后由1号进程回收资源 。
    如果需要父进程回收资源,则需要使用wait函数(子进程终止之后才会执行父进程,父进程终止时会回收子进程资源)。
  • 仍能正常运行,完成自身任务后会正常终止,不会占用额外资源;

影响:本身是系统正常处理机制的结果,无负面影响,反而避免了进程成为"无主进程";例如:若父进程因bug崩溃,子进程被收养后可继续提供服务。

程序运行时在其他终端执行命令:**ps -aux | grep orphan(程序的可执行文件)**查看子进程的父进程变化:

cpp 复制代码
//孤儿进程:父进程先于子进程终止,子进程成为孤儿进程
//孤儿进程会被init/systemd进程(1号进程)收养,成为init/systemd进程的子进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,const char *argv[]){
    pid_t pid = fork();
    if(pid == -1){
        perror("创建子进程失败");
        return -1;
    }
    else if(pid == 0){//子进程
        printf("子进程:pid=%d\n",getpid());
        printf("子进程开始延时10s\n");
        sleep(10);
        printf("子进程进行终止");
    }
    else if(pid > 0){//父进程
        printf("父进程:pid=%d\n",getpid());
        printf("父进程开始延时5s\n");
        sleep(5);
        printf("父进程进行终止\n");
    }
    //父进程终止后会将控制权交给终端
    //子进程目前处于延时,延时后子进程终止
    return 0;
}

二、僵尸进程

定义:进程终止后,内核未释放其进程控制块(PCB),该进程状态变为Z(僵尸态),成为僵尸进程;

产生原因:父进程未调用wait()或waitpid()等系统调用(父进程未调用资源回收 ),读取子进程的退出状态(如退出码、终止原因),导致内核无法回收PCB;

特点:已终止运行,不再执行任何代码,但PCB仍占用内存资源(如PID、退出状态) ;通过ps命令中显示状态为 Z 或者是Z+(僵尸态),名称可能为<defunct>;

影响:系统PID数量有限,大量僵尸进程会耗尽PID资源,导致新进程进行无法创建 ;不占用用户空间的内存数据段,但内核空间的PCB资源仍被占用且无法使用。

处理方式:

  • 预防:
    1. 父进程调用wait()/waitpid()主动回收子进程状态。
    2. 若父进程先于子进程退出,子进程会被 init 进程(PID=1)收养,init 会定期回收其僵尸子进程。
  • 清除:kill-9(只能作用于正在运行的进程)无法直接杀死僵尸进程(其已终止,无运行实体);父进程未处理,可终止父进程(僵尸进程被1号进程收养,1号进程会定期回收其PCB);
cpp 复制代码
//僵尸进程:进程终止之后,父进程未调用系统调用(wait/waitpid)回收子进程的资源
//子进程的资源一直被占用,子进程成为僵尸进程
//父进程终止后,子进程成为孤儿进程让1号进程收养,1号进程会定期回收其PCB
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,const char *argv[]){
    pid_t pid = fork();
    if(pid == -1){
        perror("创建子进程失败");
        return -1;
    }
    else if(pid == 0){//子进程
        printf("子进程:pid=%d\n",getpid());
        sleep(5);
        printf("子进程进行终止");
    }
    else if(pid > 0){//父进程
        //子进程终止后,父进程还在运行中;
        //父进程中没有调用wait/waitpid回收子进程资源;所以子进程会变成僵尸进程
        //父进程终止后,子进程成为孤儿进程让1号进程收养,1号进程会定期回收其PCB
        //父进程调用wait(NULL)/waitpid(-1,NULL,0)回收子进程资源
        printf("父进程:pid=%d\n",getpid());
        sleep(15);
        printf("父进程进行终止\n");
    }
    //父进程终止后会将控制权交给终端
    //子进程目前处于延时,延时后子进程终止
    return 0;
}

三、守护进程

定义:运行在后台的特殊进程,独立于控制终端,用于持续提供系统服务;不受用户登录/注销影响,长期稳定运行(如网络服务、定时任务)。

特点:父进程为1号进程:启动后会与原本父进程脱离,被1号进程收养。

  • 脱离终端:无控制终端,避免终端关闭导致进程终止;
  • 后台运行:通过ps命令查看状态通常为S(休眠),名称多以d结尾(如httpd、systemd);

环境干净:默认不继承终端信号,工作目录通常为\(根目录),文件描述符(如stdin/stdout/stderr)被关闭或重定向到/dev/null。

守护进程创建流程:

  • fork()创建子进程 :父进程退出,让子进程在后台运行;子进程成为孤儿进程(被1号收养);脱离原终端的进程组,为后续setsid做准备。
  • 子进程调用setsid( )函数创建新会话,脱离原终端控制;脱离原来的会话、进程组、控制终端,守护进程不在绑定终端,关闭终端不会终止程序。
  • 调用chdir( )函数改变进程的工作目录到一个不会被删除和卸载的目录下;如:chdir("/")修改工作目录到根目录:防止当前目录是挂载点,进程占用目录导致文件系统无法卸载。
  • 使用umask( )函数修改创建文件的掩码;清除从父进程继承的umask,守护进程创建文件/目录时权限可控。
  • 关闭所有从父进程继承过来的文件描述符集合(标准输入0、输出1、错误2);脱离终端IO,避免在终端输出信息;一般重定向到/dev/null。
  • 使用dup( )或者是dup2( )将标准输入/标准输出/标准出错都重定向到文件中;
  • 开启自己的服务;

setsid函数:

所需头文件:#include <sys/types.h>、#include <unistd.h>

原型:pid_tsetsid(void); 创建一个新会话,并让调用该函数的进程成为这个新会话的首进程(脱离原本的会话和进程组),同时也成为新进程组的组长

返回值:成功则返回调用进程的新会话标识符;失败则返回-1,重置错误码。

若进程原本就是进程组的组长,则调用setsid函数会失败(不会创建新会话,返回-1重置错误码)
chdir函数:

所需头文件:#include <unistd.h>

原型:int chdir(const char *path); 将调用进程的当前路径修改为参数所指定的目录

参数path:新的工作目录

返回值:成功则返回0;失败则返回-1,重置错误码。

umask函数

所需头文件:#include <sys/types.h>、#include <sys/stat.h>

原型:mode_t umask(mode_t mask); 修改进程文件的掩码(控制文件或目录的默认权限)

参数mask:新的掩码

  • 实际文件权限 = ~mask & 0666 (默认不具备可执行权限)
  • 实际目录权限 = ~mask & 0777 (默认具有可执行权限)

返回值总是成功,返回之前的掩码。

cpp 复制代码
//守护进程一直在后台运行;
//杀死守护进程:kill -9 pid(进程号)
//正常结束:kill -15 pid(进程号)  -15:随系统启动而启动,终止而终止(系统服务)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,const char *argv[]){

    //1.使用fork函数创建子进程,父进程退出,子进程变成孤儿进程
    //孤儿进程会被1号进程收养
    pid_t pid = fork();
    if(pid == -1){
        perror("创建子进程失败");
        return -1;
    }
    else if(pid == 0){//子进程
        printf("子进程:pid=%d\n",getpid());

        //2.使用setsid创建新会话,脱离原本的会话和进程组
        pid_t setsid_pid = 0;
        if(-1 == (setsid_pid = setsid())){
            perror("创建新会话失败");
            return -1;
        }
        printf("创建新会话成功,setsid_pid=%d\n",setsid_pid);

        //3.使用chdir改变当前工作目录(视情况而定)
        int chdir_pid=0;
        if(-1 == (chdir_pid = chdir("/home/hqyj/Linux"))){
            perror("改变当前工作目录失败");
            return-1;
        }
        printf("改变当前工作目录成功,返回值:%d\n",chdir_pid);//成功则为0

        //4.使用umask设置文件权限掩码(设置屏蔽权限,而非实际文件/目录的权限)
        //0022说明要屏蔽掉文件所属组和其他用户的写权限
        //umask返回值总是成功,返回之前的文件权限掩码
        mode_t umask_mode = umask(0022);
        printf("umask函数返回值:%d\n",umask_mode);

        //5.关闭所有从父进程中继承的文件描述符
        for(int i=3;i<1024;i++){
            close(i);
        }

        //6.对标准输入0,标准输出1,标准错误2这三个特殊文件描述符进行重定向
        int fd=0;
        if(-1 == (fd = open("process.log",O_RDWR|O_CREAT|O_TRUNC))){
            perror("打开文件失败");
            return -1;
        }
        printf("打开文件成功,fd=%d\n",fd);
        //对标准输入0,标准输出1,标准错误2重定向到文件中
        dup2(fd,0);
        dup2(fd,1);
        dup2(fd,2);

        //7.开启自己的服务(视情况而定)
        while(1){
            printf("hello\n");
            fflush(stdout);//刷新缓冲区数据到文件中
            sleep(1);
        }
    }
    else if(pid > 0){//父进程
        printf("父进程:pid=%d\n",getpid());
        exit(EXIT_SUCCESS);//父进程正常退出,子进程成为孤儿进程
    }
    //父进程终止后会将控制权交给终端
    //子进程目前处于延时,延时后子进程终止
    return 0;
}

四、三者的核心区别

相关推荐
AI_Lab0111 小时前
宝塔用了两年,有没有必要换成轻量方案?
linux·运维·服务器
众壹新能源科技11 小时前
逆变器直流开关柜温度监测与预防:红外巡检发现3个120度热点
运维·人工智能·自动化
by2099911 小时前
Linux GDB 调试与 CGDB 使用
linux·运维·服务器·经验分享
小羊没烦恼!11 小时前
关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁
java·服务器·网络·学习·负载均衡
小Ti客栈11 小时前
Netty 零拷贝原理与实战
运维·服务器·网络
ai_coder_ai11 小时前
AIOps 智能运维架构设计及其应用
运维
静以修身zzz12 小时前
计算云服务
运维·网络·云计算
星恒随风12 小时前
Linux网络编程:应用层协议究竟是什么?从网络计算器到TCP Socket抽象基类完整实现
linux·网络·笔记·tcp/ip
万联WANFLOW12 小时前
异地组网速度忽快忽慢到底怎么回事?直连与中继的判断方法和处理步骤
运维·网络·网络协议·p2p
SDWAN_Cheap12 小时前
物理服务器跟云服务器有什么区别,使用于什么场景
运维·服务器