一,孤儿进程
**定义:**当一个进程的父进程先于它终止,该进程就会成为孤儿进程;
**产生原因:父进程意外崩溃、被强制终止(如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) |