特殊进程-

一,孤儿进程

**定义:**当一个进程的父进程先于它终止,该进程就会成为孤儿进程;
**产生原因:父进程意外崩溃、被强制终止(如kill-9),或父进程主动退出但未正确处理子进程;
特点:**失去父进程后,会被系统的"祖先进程"(linux中是init或system, PID=1)收养;
仍能正常运行,完成自身任务后会正常终止,不会占用额外资源;

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

代码示例:

cs 复制代码
//孤儿进程:父进程先于子进程进行终止,所以子进程就变成了孤儿进程
//孤儿进程会被init/systemd进程收养,成为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("创建子进程失败\n");
        return -1;
    }
    else if(pid == 0)
    {
        //子进程  孤儿进程
        printf("子进程开始延时10秒...\n");
        printf("子进程的进程号为:%d\n",getpid());
        sleep(10);
        printf("子进程进行终止...\n");

    }
    else if(pid >0)
    {
        //父进程
        printf("父进程开始延时5秒...\n");
        printf("父进程的进程号为:%d\n",getpid());
        sleep(5);
        printf("父进程进行终止...\n");
    }
    return 0;
}

二,僵尸进程

**定义:**进程终止后,内核未释放其进程控制块(PCB),该进程状态变为Z(僵尸态),成为僵尸进程;
**产生原因:父进程未调用wait()或waitpid()等系统调用,读取子进程的退出状态(如退出码、终止原因),导致内核无法回收PCB;
特点:**已终止运行,不再执行任何代码,但PCB仍占用内存资源(如PID、退出状态);
通过ps命令中显示状态为Z或者是Z+(僵尸态),名称可能为<defunct>;

影响:
系统PID数量有限,大量僵尸进程会耗尽PID资源,导致新进程进行无法创建;
不占用用户空间的内存数据段,但内核空间的PCB资源仍被占用且无法使用;
处理方式:
预防:父进程调用wait()/waitpid()主动回收子进程状态;
清除:kill-9无法直接杀死僵尸进程(其已终止,无运行实体);
父进程未处理,可终止父进程(僵尸进程被1号进程收养,1号进程会定期回收其PCB);

代码示例如下:

cs 复制代码
//僵尸进程示例代码
//僵尸进程在进程终止之后,父进程没有调用系统调用(wait/waitpid)来回收子进程的资源
//所以子进程的资源就会被一直占用,那么这个时候子进程就会变成僵尸进程
#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("创建子进程失败\n");
        return -1;
    }
    else if(pid == 0)
    {
        //子进程
        printf("我是子进程,我的pid是%d\n",getpid());
        printf("子进程结束...\n");
    }
    else if(pid > 0)
    {
        //父进程
        //子进程终止之后,父进程还在运行中
        //所以父进程不会回收子进程的资源,父进程中也没有调用wait/waitpid来回收子进程的资源
        //所以子进程就会变成僵尸进程
        //那么当父进程结束之后,子进程虽然前面已经结束,但是子进程的资源不会被
        //结束的父进程进行回收,而是由1号进程进行回收
        //僵尸进程会依赖于1号进程的"收养机制"
        //如何避免僵尸进程?
        //1.父进程在子进程退出之后,调用wait/waitpid来回收子进程的资源
        //wait(NULL);
        //waitpid(-1,NULL,0);
        printf("我是父进程,我的pid是%d\n",getpid());
        sleep(40);
        printf("父进程结束...\n");
    }
       return 0;
}

三,守护进程

**定义:**运行在后台的特殊进程,独立于控制终端,用于持续提供系统服务;

**设计目的:**不接受用户登录/注销影响,长期稳定运行(如网络服务,定时任务)

特点:

脱离终端:无控制终端,避免终端关闭导致进程终止;

后台运行:通过ps命令查看状态通常为S(休眠),名称多以d结尾(如htppd,systemd);

父进程为1号进程:启动后会于原本腹肌农村脱离,被1号进程收养;

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

1.守护进程的创建流程

示例图:

流程分为7步:

父进程fork()后退出,子进程成为孤儿进程(被1号收养);

子进程调用setsid()函数创建新会话,脱离原终端控制;

调用chdir()函数改变进程的工作目录到一个不会被删除和卸载的目录下;

使用umask()函数修改创建文件的掩码;

关闭所有从父进程继承过来的文件描述符集合;

使用dup()或者是dup2()将标准输入/标准输出/标准出错都重新定向到文件中;

开启自己的服务;

代码示例:

cs 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.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 = setsid();
        if(setsid_pid == -1)
        {
            perror("创建新会话失败");
            return -1;
        }
        printf("创建新会话成功,我的PID是%d\n",setsid_pid);
        //3.使用chdir()改变当前的工作目录
        //注意:改变的当前的工作目录需要根据实际的情况来定
        int chdir_pid = 0;
        if(-1 == (chdir_pid = chdir("/home/hqyj")))
        {
            perror("改变当前工作目录失败");
            return -1;
        }
        printf("改变当前工作目录成功,返回值是:%d\n",chdir_pid);//成功返回0
        //4.使用umask()设置文件权限掩码
        //注意:umask函数是用来设置屏蔽权限的
        //而不是设置实际文件/目录的权限
        mode_t umask_mode = umask(0022);//设置屏蔽权限为0022,说明要屏蔽掉文件所属组和其他用户的写权限
        printf("umask函数返回值是:%d\n",umask_mode);
        //5.关闭从父进程中继承过来的所有文件描述符集合
        //后序我们对0,1,2这三个特殊的文件描述符进行重定向操作
        for(int i = 3;i < 1024;i++)
        {
            close(i);
        }
        //6.将标准输入,标准输出,标准错误重定向到文件中
        //如果文件存在,以读写的方式打开文件
        //如果文件不存在,则需要创建
        //如果文件存在,就将原来的清空
        int fd = 0;
        fd = open("hqyj.log",O_RDWR|O_CREAT|O_TRUNC,0644);
        if(fd == -1)
        {
            perror("打开文件失败");
            return -1;
        }
        printf("打开文件成功,文件描述符是:%d\n",fd);
        //将文件描述符标准输入,标准输出,标准错误重定向到文件中
        dup2(fd,0);
        dup2(fd,1);
        dup2(fd,2);
        //7.开启自己的服务
        //一旦开启自己的服务,那么这个守护进程就会在后台进行运行
        //注意:开启的服务具体要根据实际情况来确定
        //针对于守护进程,因为一直在后台进行运行
        //所以想要将守护进程杀死,就需要通过 kill -9 pid 来杀死
        //如果想要守护进程正常结束,那么就需要通过kill -15 pid 来结束
        while(1)
        {
            printf("helloworld\n");
            fflush(stdout);//刷新标准输出缓冲区
            sleep(1);
        }

        }
    else if(pid > 0)
    {
        //父进程
        printf("我是父进程,我的PID是:%d\n",getpid());
        //父进程退出
        exit(EXIT_SUCCESS);//正常退出
    }
    return 0;
}

2.setsid函数

|-----------|--------------------------------------------------------|
| 所需头文件 | #include <sys/types.h> #include <unistd.h> |
| 原型 | pid_t setsid(void); |
| 功能 | 创建一个新会话,并让调用该函数的进程成为这个新会话的首进程,同时也成为新进程组的组长 |
| 参数 | |
| 返回值 | 成功:返回调用进程的新会话的标识符 失败:-1,重置错误码 |

3.chdir函数

|-----------|----------|------------|
| 所需头文件 | #include <unistd.h> ||
| 原型 | int chdir(const char *path) ||
| 功能 | 将调用进程的当前路径修改为参数所指定的目录 ||
| 参数 | path | 新的工作目录 |
| 返回值 | 成功:0 失败:-1,重置错误码 ||

4.umask函数

|-----------|----------|-----------------------------------------------------------------------------------------|
| 所需头文件 | #include <sys/types.h> #include <sys/stat.h> ||
| 原型 | mode_t umask(mode_t mask); ||
| 功能 | 修改进程文件的掩码 ||
| 参数 | mask | 新的掩码 实际文件权限 = ~mask & 0666(默认不具有可执行权限) 实际目录权限 = ~mask & 0777(默认具有可执行权限) |
| 返回值 | 总是成功,返回之前的掩码 ||

四,三者的核心区别

|----------|----------------------------------|-----------------------------------------------------|--------------------------------------------|
| 对比维度 | 孤儿进程(Orphan Process) | 僵尸进程(Zombie Process) | 守护进程(Daemon Process) |
| 定义 | 父进程先于子进程退出 子进程被1号进程收养的过程 | 子进程先退出 但父进程未调用wait/waitpid回收其PCB导致进程状态为Z的进程 | 脱离控制终端,在后台长期运行的系统服务进程(如 sshd,nginx) |
| 产生原因 | 父进程意外退出或主动退出,未等待子进程 | 子进程退出后 父进程未调用wait/waitpid回收其退出状态 | 程序主动调用setsid等函数创建 脱离终端并后台运行 |
| 进程状态 | 正常运行状态,PPID=1 | 僵尸状态,标记为<defunct> | 后台运行状态,无法控制终端, 名称常以 d 结尾(如 htppd) |
| 资源占用 | 占用正常进程资源(代码/数据/PCB) | 仅占用PCB资源(PID,退出状态)不占用代码段/数据段 | 占用正常进程资源,长期稳定运行 |
| 危害 | 无直接危害 被1号进程正常管理 | 大量积累会耗尽PID资源 导致新进程无法创建 | 无危是系统正常运行的必要服务(如网络服务,定时任务) |
| 处理方式 | 无需手动处理 1号进程会自动回收其资源 | 父进程调用 wait/waitpid主动回收; 终止父进程,由1号进程回收 | 无需处理,随系统启动而启动,终止而终止,可通过 systemctl等工具管理 |
| 典型场景 | 父进程未等待子进程就退出如:脚本中后台运行的子进程 | 父进程长期运行且未处理子进程退出如:服务器程序漏洞 | 系统服务(如:sshd ,crond,nginx) |

相关推荐
AOwhisky2 小时前
Kubernetes 学习笔记:Volume 存储卷与 ConfigMap 配置管理
linux·运维·笔记·学习·云原生·kubernetes
上海云盾-小余2 小时前
服务器 UDP/TCP 反射 DDoS 原理详解:攻击识别、清洗策略与企业防御部署指南
服务器·tcp/ip·udp
A_QXBlms2 小时前
企销宝新版本技术解读新客运营多天计划与关键词自动化拉群实践
运维·自动化
江畔何人初2 小时前
Kafka 消息队列概念及与RabbitMQ 的区别
运维·服务器·分布式·云原生·kafka·rabbitmq
nLif2 小时前
linux-stable-sw-v4.19.180-sw64-2203.tar.gz 编译错误排查方法
linux·运维·服务器
特长腿特长2 小时前
在debian系统上使用kvm、添加网卡设备案例
服务器·debian·php
Wmenghu2 小时前
Ubuntu 安装 RocketMQ 5.x + Dashboard 完整教程
linux·ubuntu·rocketmq
智象科技2 小时前
AI重构IT运维:从被动救火到智能自治,这场革命已不可逆!
大数据·运维·人工智能·ai·重构·一体化运维
Taking_fish3 小时前
docker常见操作命令(基础服务搭建)
运维·docker·容器