一、孤儿进程
定义:当一个进程的父进程先于它终止(父进程意外崩溃、被强制终止如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资源仍被占用且无法使用。
处理方式:
- 预防:
- 父进程调用wait()/waitpid()主动回收子进程状态。
- 若父进程先于子进程退出,子进程会被 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;
}
四、三者的核心区别

