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

news2024/11/18 9:43:55

文章目录

  • 前言
  • 孤儿进程
  • 僵尸进程
  • 守护进程
  • 总结

前言

孤儿进程、僵尸进程和守护进程是操作系统中的概念,它们分别表示不同的进程状态和特性。孤儿进程和僵尸进程了解了解(都是为守护进程做铺垫),但是对于守护进程大家还是可以好好学习学习,相信以后会用得到~~~~~~
【本博客的实例代码用的是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);//打印父进程的pid
    return 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);//打印父进程的pid
    return 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重定向到fd
        if (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重定向到fd
        if (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);//打印父进程的pid
    return 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()创建新会话,改变工作目录和文件访问权限,关闭不需要的文件描述符等。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/445165.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人民满意手机银行服务白皮书——服务分析篇

易观&#xff1a;商业银行积极践行“金融为民”&#xff0c;坚持“以用户为中心”的发展理念&#xff0c;从全客群、全服务、全渠道推动金融服务触达广大人民群众。其中&#xff0c;手机银行作为服务及经营主阵地&#xff0c;是人民群众获取金融服务的超级入口及服务平台。 “以…

【超算/先进计算学习】日报2

目录 今日已完成任务列表遇到的问题及解决方案任务完成详细笔记编辑器Vim程序开发步骤文件编辑器 Vim 快速入门任务练习 编译器 GCC程序编译流程编译器 gcc 的简易使用gcc 编译链接命令规则及常用编译选项 工程构建工具 Makemake 命令与makefile文件的规则和逻辑makefile 文件编…

数据库ER图详解

ER图也成ERD&#xff08;Entity Relationship Diagram&#xff09;,目前对于ER图的表示有两种方式&#xff1a; 一种是Chens notation&#xff0c;1976年Peter Chen首次提出了Entity Relationship Modeling&#xff08;实体关系建模&#xff09;概念。 另一种是Crow’s foot no…

【UITableViewCell的重用 Objective-C语言】

一、UITableViewCell有哪些属性,怎么看, 1.可以按住command键,点开这个UITableView,看到如下界面: 这个时候,文字比较大,你可能看起来不是很清楚,这个时候,可以点击左上角的xcode,选择Preference, 让它变小一些, 文字变小,变成12以后,这些属性,是不是对的很齐啊…

Postcat IDEA 插件,最全的使用教程

Postcat 插件不会入侵到代码内部&#xff0c;无需添加任何jar包依赖&#xff0c;这个真的很香&#xff01; 下面说一说&#xff0c;这个插件如何使用&#xff0c;是时候展现真正的技术了~ 如何安装配置&#xff1f; IDEA 版本需大于 IntelliJ IDEA 2022.03 在 IDEA “设置-插件…

JSon使用

官方文档 JSon在线文档&#xff1a;https://www.w3school.com.cn/js/js_json_intro.asp Ajax在线文档&#xff1a;https://www.w3school.com.cn/js/js_ajax_intro.asp Json介绍 Json快速入门 <!DOCTYPE html> <html lang"en"> <head><meta c…

从编译器角度理解C++编译和连接原理

C编译链接整体介绍 链接主要工作 1 所有.o文件段的合并&#xff0c;符号表合并后&#xff0c;进行符号解析 链接时就是在符号表中找对应的符号是否只出现于.text或.data段一次&#xff0c;若一次都无&#xff0c;则符号未定义&#xff1b;若出现多次&#xff0c;符号重定义 符…

JavaSE补充 | apache-common包的使用,快速完成IO操作

目录 一&#xff1a;apache-common包的使用&#xff0c;快速完成IO操作 1. IOUtils类的使用 2. FileUtils类的使用 一&#xff1a;apache-common包的使用&#xff0c;快速完成IO操作 IO技术开发中&#xff0c;代码量很大&#xff0c;而且代码的重复率较高&#xff0c;为此Ap…

【MySQL】数据库约束和聚合函数的使用

目录 上篇在这里喔~ 1.数据库约束 1.NULL约束 2.UNIQUE唯一约束 3.DEFAULT默认值约束 4.PRIMARY KEY主键约束 5.FOREIGN KEY外键约束 2.表的设计 1.设计思路​编辑 2.固定套路​编辑 2.1一对一关系 2.2一对多关系 ​编辑 2.3多对多关系 ​编辑​编辑​编辑 3.插入…

《C++模板》(初阶)零基础讲解

本文主要介绍C的模板&#xff0c;包括函数模板和类模板 文章目录 为什么要有模板1、函数模板1.1 函数模板概念1.1 函数模板格式1.3 函数模板的原理1.4 函数模板的实例化1.5 模板参数的匹配原则 2、类模板2.1 类模板的定义格式2.2 类模板的实例化 为什么要有模板 就拿我们写的交…

从“捐赠openEuler”到“向openEuler捐赠”,openEuler生态走入高速发展期

【中国&#xff0c;上海&#xff0c;2023年4月21日】openEuler Developer Day 2023于4月20-21日在线上和线下同步举办。本次大会由开放原子开源基金会指导&#xff0c;中国软件行业协会、openEuler社区、边缘计算产业联盟共同主办&#xff0c;以“万涓汇流&#xff0c;奔涌向前…

ROS使用(10)URDF

Building a visual robot model from scratch 在本教程中&#xff0c;我们将构建一个机器人的视觉模型&#xff0c;它看起来有点像R2D2。 在后面的教程中&#xff0c;您将学习如何 清晰地表达模型&#xff0c;添加一些物理属性&#xff0c;并使用xacro生成更简洁的代码&#x…

Rebex Total Pack文件传输组件,改进的递归断路器

Rebex Total Pack文件传输组件,改进的递归断路器 文件系统&#xff1a;改进的递归断路器。 ZIP&#xff1a;改进了ZIP数据描述符解析器&#xff0c;以更好地处理与Zip64的不一致性。 .NET组件 Rebex文件服务器-适用于.NET的SFTP、SCP和SSH服务器组件。可以轻松创建可供任何SFTP…

AI 时代的学习方式: 和文档对话

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;蚂蚁集团高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《EffectiveJava》独家解析》专栏作者。 热门文章推荐…

Spring请求与响应——响应

我们上一篇文章说了请求&#xff0c;SpringMVC接收到请求和数据后&#xff0c;进行一些了的处理&#xff0c;当然这个处理可以是转发给Service&#xff0c;Service层再调用Dao层完成的&#xff0c;不管怎样&#xff0c;处理完以后&#xff0c;都需要将结果告知给用户也就是给用…

Codeium的使用

官网&#xff1a;CodeiumCodeium offers best in class AI code completion & search — all for free. It supports over 40 languages and integrates with your favorite IDEs, with lightning fast speeds and state-of-the-art suggestion quality.https://codeium.co…

数据结构_第十三关(3):归并排序、计数排序

目录 归并排序 1.基本思想&#xff1a; 2.原理图&#xff1a; 1&#xff09;分解合并 2&#xff09;数组比较和归并方法&#xff1a; 3.代码实现&#xff08;递归方式&#xff09;&#xff1a; 4.归并排序的非递归方式 原理&#xff1a; 情况1&#xff1a; 情况2&…

docker的三种镜像创建

目录 dock的三种镜像创建 基于现有的镜像创建 基于本地模板创建 基于Dockerfile 创建 联合文件系统 镜像加载原理 为什么Docker里的centos的大小才200M&#xff1f; Docker 镜像结构的分层 Dockerfile 操作常用的指令 Dockerfile格式 dockerfile构建apache实例 dock的…

探索【Stable-Diffusion WEBUI】各种插件和追求更高效

文章目录 &#xff08;零&#xff09;前言&#xff08;一&#xff09;界面与翻译(1.1) 主题风格&#xff08;kitchen Theme&#xff09;(1.2) 对照翻译&#xff08;Bilingual Localization&#xff09;(1.3) 自行翻译(1.3) 提示词翻译&#xff08;Prompt Translator&#xff09…

asp.net765数码手机配件租赁系统

员工部分功能 1.员工登录&#xff0c;员工通过自己的账号和密码登录到系统中来&#xff0c;对租赁信息进行管理 2.配件查询&#xff0c;员工可以查询系统内的配件信息 3.客户信息管理&#xff0c;员工可以管理和店内有业务往来的客户信息 4.配件租赁&#xff0c;员工可以操作用…