> 文章列表 > 孤儿僵尸守护进程基本概念与使用

孤儿僵尸守护进程基本概念与使用

孤儿僵尸守护进程基本概念与使用

文章目录

前言

孤儿进程、僵尸进程和守护进程是操作系统中的概念,它们分别表示不同的进程状态和特性。孤儿进程和僵尸进程了解了解(都是为守护进程做铺垫),但是对于守护进程大家还是可以好好学习学习,相信以后会用得到~~~~~~
【本博客的实例代码用的是C/C++多进程高并发框架分享【内附可执行源码注释完整】中的代码】

孤儿进程

一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作。
想想我们如何模仿一个孤儿进程?👇
1、kill 父进程
2、父进程不wait自然退出
这里进行模拟的场景是父进程不wait自然退出,看代码👇
multip_process1.cpp

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/wait.h>
#include <sys/types.h>
typedef void (*spawn_proc_pt) (void *data);
static void worker_process_cycle(void *data);
static void start_worker_processes(int n);
pid_t spawn_process(spawn_proc_pt proc, void *data, char *name); int main(int argc,char **argv){start_worker_processes(4);//启动四个工作进程//管理子进程,等待子进程都挂了//wait(NULL);printf("parent is over!!\\n");
}void start_worker_processes(int n){int i=0;for(i = n - 1; i >= 0; i--){spawn_process(worker_process_cycle,(void *)(intptr_t) i, "worker process");//生产四个子进程(处理任务的过程worker_process_cycle,传入i的地址,名字是work_process)}
}pid_t spawn_process(spawn_proc_pt proc, void *data, char *name){pid_t pid;pid = fork();switch(pid){case -1:fprintf(stderr,"fork() failed while spawning \\"%s\\"\\n",name);return -1;case 0://如果是子进程proc(data);return 0;default:break;}   printf("start %s %ld\\n",name,(long int)pid);//打印父进程的pidreturn pid;
}//设置cpu亲缘关系,把进程绑定在一个cpu的核上面
static void worker_process_init(int worker){cpu_set_t cpu_affinity;//cpu亲缘//worker = 2;//多核高并发处理  4core  0 - 0 core 1 - 1  2 -2 3 -3  CPU_ZERO(&cpu_affinity);//将结构体清零CPU_SET(worker % CPU_SETSIZE,&cpu_affinity);// 0 1 2 3,将核与掩码进行绑定 //sched_setaffinity//0:绑定自己if(sched_setaffinity(0,sizeof(cpu_set_t),&cpu_affinity) == -1){fprintf(stderr,"sched_setaffinity() failed\\n");}
}void worker_process_cycle(void *data){int worker = (intptr_t) data;//worker就是3210//初始化worker_process_init(worker);//干活,子进程该干什么就写在这下面for(;;){sleep(10);printf("pid %ld ,doing ...\\n",(long int)getpid());}
}

main方法中可以看到,没有wait,所以当创建完4个进程以后,父进程就自然结束了,这样就导致4个子进程交由init进程进行托管。
1、如果父进程有wait👇
孤儿僵尸守护进程基本概念与使用
孤儿僵尸守护进程基本概念与使用
2、如果父进程没有了wait👇

孤儿僵尸守护进程基本概念与使用
孤儿僵尸守护进程基本概念与使用


僵尸进程

僵尸进程是指子进程已经退出,但是其父进程没有调用wait或者waitpid等函数来回收子进程的资源,导致子进程的进程控制块(PCB)仍然保留在系统中,此时子进程成为僵尸进程。僵尸进程不会消耗系统资源,但是如果父进程一直不回收它的资源,就会占用系统的进程号,导致系统进程号不足,从而影响系统的正常运行。

Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装 SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了, 那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是 为什么系统中有时会有很多的僵尸进程。

1.怎么查看僵尸进程:
利用命令ps -ef,可以看到有标记为<defunct>的进程就是僵尸进程。
2.怎么清理僵尸进程:
①改写父进程,在子进程死后要为它收尸。具体做法是接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。这是基于这样的原理:就算父进程没有调用 wait,内核也会向它发送SIGCHLD消息,尽管对默认处理是忽略,如果想响应这个消息,可以设置一个处理函数。
②把父进程杀掉。父进程死后,僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程。它产生的所有僵尸进程也跟着消失。

来看看一串代码就知道什么意思了👇
模拟的场景是父进程创建完四个子进程以后,父进程没有wait,而四个子进程相继退出,父进程还在循环之中,这样就会让四个子进程变成僵尸进程
multip_process2.cpp

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/wait.h>
#include <sys/types.h>
typedef void (*spawn_proc_pt) (void *data);
static void worker_process_cycle(void *data);
static void start_worker_processes(int n);
pid_t spawn_process(spawn_proc_pt proc, void *data, char *name); int main(int argc,char **argv){start_worker_processes(4);//启动四个工作进程//管理子进程,等待子进程都挂了//wait(NULL);for(;;) sleep(1);return 0;
}void start_worker_processes(int n){int i=0;for(i = n - 1; i >= 0; i--){spawn_process(worker_process_cycle,(void *)(intptr_t) i, "worker process");//生产四个子进程(处理任务的过程worker_process_cycle,传入i的地址,名字是work_process)}
}pid_t spawn_process(spawn_proc_pt proc, void *data, char *name){pid_t pid;pid = fork();switch(pid){case -1:fprintf(stderr,"fork() failed while spawning \\"%s\\"\\n",name);return -1;case 0://如果是子进程proc(data);return 0;default:break;}   printf("start %s %ld\\n",name,(long int)pid);//打印父进程的pidreturn pid;
}//设置cpu亲缘关系,把进程绑定在一个cpu的核上面
static void worker_process_init(int worker){cpu_set_t cpu_affinity;//cpu亲缘//worker = 2;//多核高并发处理  4core  0 - 0 core 1 - 1  2 -2 3 -3  CPU_ZERO(&cpu_affinity);//将结构体清零CPU_SET(worker % CPU_SETSIZE,&cpu_affinity);// 0 1 2 3,将核与掩码进行绑定 
//sched_setaffinity
//0:绑定自己if(sched_setaffinity(0,sizeof(cpu_set_t),&cpu_affinity) == -1){fprintf(stderr,"sched_setaffinity() failed\\n");}
}void worker_process_cycle(void *data){int worker = (intptr_t) data;//worker就是3210//初始化worker_process_init(worker);//干活,子进程该干什么就写在这下面// for(;;){//   sleep(10);//   printf("pid %ld ,doing ...\\n",(long int)getpid());// }exit(1);
}

孤儿僵尸守护进程基本概念与使用
孤儿僵尸守护进程基本概念与使用


守护进程

守护进程是在后台运行的一种特殊进程,通常不与任何终端关联,也不接受用户输入。它通常用于执行一些周期性任务或者长期运行的服务,例如web服务器、数据库服务器等。守护进程通常在系统启动时自动启动,并且一般具有特定的权限和身份,以便访问一些系统资源和服务。守护进程需要注意的是,在运行时需要注意不要产生僵尸进程,否则会影响系统的稳定性。
以僵尸进程的代码为例(没有制作守护进程),如果在终端一中运行代码
孤儿僵尸守护进程基本概念与使用
在终端二中查看进程状态,有
孤儿僵尸守护进程基本概念与使用
如果现在将终端一进行关闭后在使用终端二进行查看进程状态,会发现几个进程都会消失【这就是不采用守护进程的后果,因为不采用守护进程,那么在这个终端启动的进程就是属于终端,终端关闭,那么同时几个进程就一起挂了
孤儿僵尸守护进程基本概念与使用

那如何成为一个守护进程呢,步骤如下:
1.调用fork(),创建新进程,它会是将来的守护进程.
2.在父进程中调用exit,保证子进程不是进程组长【这样就可以创建新会话】
保证子进程不是进程组长可以避免它接收到终端的信号,但父进程退出后,子进程自动成为孤儿进程,会被init进程接管。
3.调用setsid()创建新的会话区
通过setsid()函数创建新的会话,并使子进程成为新会话的首进程,从而与控制终端脱离关联。
4.将当前目录改成根目录
将当前工作目录改为根目录,确保守护进程的工作目录不会影响到其他程序的工作。
5.将标准输入,标准输出,标准错误重定向到/dev/null.
避免守护进程输出到终端或者接收终端的输入,/dev/null是一个特殊的文件,它不占用任何磁盘空间,任何写入该文件的数据都会被直接丢弃。在Unix/Linux系统中,/dev/null被广泛应用于重定向程序的输出,是一个非常有用的工具,在守护进程中,通常不需要从标准输入读取数据,因此重定向标准输入的常见做法是将其重定向到/dev/null。这样,程序就无法从标准输入读取任何数据,从而避免了一些潜在的安全问题。

经过上述步骤,进程就已经成为一个守护进程了,可以执行它需要执行的任务了。这些任务可能包括网络服务、定时任务等。
构建守护进程关键代码如下👇

#include <fcntl.h>
#include <unistd.h>int daemon(int nochdir, int noclose)
{int fd;switch (fork()) {case -1:return (-1);case 0:break;default://父进程exit_exit(0);}//子进程继续进行操作//创建一个新的会话if (setsid() == -1)return (-1);//改变工作目录if (!nochdir)(void)chdir("/");//重定向文件句柄//fd=....,打开这个文件句柄if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {(void)dup2(fd, STDIN_FILENO);//将stdin重定向到fd(void)dup2(fd, STDOUT_FILENO);//将stdout重定向到fd(void)dup2(fd, STDERR_FILENO);//将stderr重定向到fdif (fd > 2)(void)close (fd);}return (0);
}

来看正经完整的示例代码吧👇
multip_process3.cpp

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/wait.h>
#include <sys/types.h>
typedef void (*spawn_proc_pt) (void *data);
static void worker_process_cycle(void *data);
static void start_worker_processes(int n);
pid_t spawn_process(spawn_proc_pt proc, void *data, char *name); 
#include <fcntl.h>
#include <unistd.h>int daemon(int nochdir, int noclose)
{int fd;switch (fork()) {case -1:return (-1);case 0:break;default:_exit(0);}if (setsid() == -1)return (-1);if (!nochdir)(void)chdir("/");//fd=....,打开这个文件句柄if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {(void)dup2(fd, STDIN_FILENO);//将stdin重定向到fd(void)dup2(fd, STDOUT_FILENO);//将stdout重定向到fd(void)dup2(fd, STDERR_FILENO);//将stderr重定向到fdif (fd > 2)(void)close (fd);}return (0);
}int main(int argc,char **argv){daemon(0,0);start_worker_processes(4);//启动四个工作进程//管理子进程,等待子进程都挂了wait(NULL);
}void start_worker_processes(int n){int i=0;for(i = n - 1; i >= 0; i--){spawn_process(worker_process_cycle,(void *)(intptr_t) i, "worker process");//生产四个子进程(处理任务的过程worker_process_cycle,传入i的地址,名字是work_process)}
}pid_t spawn_process(spawn_proc_pt proc, void *data, char *name){pid_t pid;pid = fork();switch(pid){case -1:fprintf(stderr,"fork() failed while spawning \\"%s\\"\\n",name);return -1;case 0://如果是子进程proc(data);return 0;default:break;}   printf("start %s %ld\\n",name,(long int)pid);//打印父进程的pidreturn pid;
}//设置cpu亲缘关系,把进程绑定在一个cpu的核上面
static void worker_process_init(int worker){cpu_set_t cpu_affinity;//cpu亲缘//worker = 2;//多核高并发处理  4core  0 - 0 core 1 - 1  2 -2 3 -3  CPU_ZERO(&cpu_affinity);//将结构体清零CPU_SET(worker % CPU_SETSIZE,&cpu_affinity);// 0 1 2 3,将核与掩码进行绑定 
//sched_setaffinity
//0:绑定自己if(sched_setaffinity(0,sizeof(cpu_set_t),&cpu_affinity) == -1){fprintf(stderr,"sched_setaffinity() failed\\n");}
}void worker_process_cycle(void *data){int worker = (intptr_t) data;//worker就是3210//初始化worker_process_init(worker);//干活,子进程该干什么就写在这下面for(;;){sleep(10);printf("pid %ld ,doing ...\\n",(long int)getpid());}
}

1、"终端一"运行可执行程序
孤儿僵尸守护进程基本概念与使用
2、在"终端二"查看进程信息:
孤儿僵尸守护进程基本概念与使用
3、把"终端一"进行关闭后再在"终端二"查看进程信息【看相应的进程是否还会关闭】:
孤儿僵尸守护进程基本概念与使用


总结

1、孤儿进程:指父进程退出或异常结束,而其子进程继续运行的情况。孤儿进程会被init进程接管,其PPID会被设置为1。

2、僵尸进程:指子进程结束,但是父进程没有处理其退出状态信息,造成该进程处于僵尸状态,占用系统资源。可以通过wait()或waitpid()等函数来处理僵尸进程。

3、守护进程:是在后台运行的一种特殊进程,一般不与任何控制终端相连。创建守护进程的过程包括:fork()创建子进程,setsid()创建新会话,改变工作目录和文件访问权限,关闭不需要的文件描述符等。

在编写孤儿进程、僵尸进程和守护进程时,需要注意一些技巧和注意事项,例如:合理的处理子进程的退出状态信息,保证子进程不会成为进程组组长,及时关闭不需要的文件描述符等。
总之,孤儿进程、僵尸进程和守护进程是进程管理中的重要概念,在实际编程中需要充分了解它们的概念、特点和实现方式,以避免出现一些常见的问题,同时也需要注意代码实现的可靠性和安全性。