深入 Linux 进程

news2024/11/25 0:44:38

问题

进程参数 和 环境变量 对于进程意味着什么?

进程参数和环境变量的意义

一般情况下,子进程的创建是为了解决某个子问题

子进程解决问题需要父进程的 "数据输入" (进程参数 & 环境变量)

设计原则:

  • 子进程启动时必然用到的参数使用进程参数传递
  • 子进程解决问题可能用到的参数使用环境变量传递

思考

子进程如何将结果 "返回" 父进程?

#include <stdio.h>

int main()
{
    printf("Test: Hello World!\n");
    return 33;
}

这个测试程序,执行完 main 函数后 return 33,我们在命令行中运行这个程序,那么这个程序就为命令行的子进程,我们通过 echo $? 命令可以得到这个程序的返回值,这个命令用于得到上一个进程的退出状态码

程序运行结果如下图所示:

在命令行中通过 echo $? 命令成功获取到了 a.out 这个子进程的进程退出状态码,那么我们可以在程序中获取到子进程的退出状态码吗?

在程序中我们可以通过 wait(...) 函数 或者 waitpid(...) 函数 来获取子进程的退出状态码

深入理解父子进程

子进程的创建是为了并行的解决子问题 (问题分解)

父进程需要通过子进程的结果最终解决问题 (并获取结果)

进程等待系统接口

pid_t wait(int* status);

  • 等待一个子进程完成,并返回子进程标识和状态信息
  • 当有多个子进程完成,随机挑选一个子进程返回

pid_t waitpid(pid_t pid, int* status, int options);

  • 可等待特定的子进程或一组子进程
  • 在子进程还未终止时,可通过 options 设置不必等待 (直接返回)

进程退出系统接口

头文件:#include <unistd.h>

void _exit(int status); 

  • 系统调用,终止当前进程

头文件:#include <stdlib.h>

void exit(int status);

  • 库函数,先做资源清理,再通过系统调用终止进程

void abort(void);

  • 异常终止当前进程 (通过产生 SIGABRT 信号终止)

下面的程序运行后会发生什么?

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>


int main(int argc, char* argv[])
{   
    pid_t pid = 0;
    int a = 1;
    int b = 0;
    int status = 0;
    
    printf("parent = %d\n", getpid());
    
    if( (pid = fork()) == 0 ) exit(-1);
    
    printf("child = %d\n", pid);
    
    if( (pid = fork()) == 0 ) abort();
    
    printf("child = %d\n", pid);
    
    if( (pid = fork()) == 0 ) a = a / b, exit(1);
    
    printf("child = %d\n", pid);
    
    sleep(3);
    
    while( (pid = wait(&status)) > 0 )
    {
        printf("child: %d, status: %x\n", pid, status);
    }
    
    return 0;
}

该程序创建了三个子进程,子进程被创建出来后,通过不同的方式退出;在父进程中,通过 wait(...) 函数来得到子进程的退出状态

程序运行结果如下图所示:

pid 为 368521 的子进程,通过 exit(-1) 来退出,退出状态码应该为 -1,而 wait(...) 函数中得到该进程的退出状态码却为 0xFF00,这是因为退出状态码由多个部分组成

进程退出状态详解

进程的退出状态码是16位的整型数,bit0 - bit7 用于记录进程被信号终止的状态值;bit8 用于表示是否生成了 coredump,coredump 记录了进程崩溃前的信息,可以用于调试 ;bit9 - bit15 用于记录进程的退出状态值

进程退出状态详解

使用上面的宏来重新获取进程的提出状态

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>


int main(int argc, char* argv[])
{   
    pid_t pid = 0;
    int a = 1;
    int b = 0;
    int status = 0;
    
    printf("parent = %d\n", getpid());
    
    if( (pid = fork()) == 0 ) exit(-1);
    
    printf("child = %d\n", pid);
    
    if( (pid = fork()) == 0 ) abort();
    
    printf("child = %d\n", pid);
    
    if( (pid = fork()) == 0 ) a = a / b, exit(1);
    
    printf("child = %d\n", pid);
    
    sleep(3);
    
    while( (pid = wait(&status)) > 0 )
    {
        if( WIFEXITED(status) )
        {
            printf("Normal - child: %d, code: %d\n", pid, (char)WEXITSTATUS(status));
        }
        else if( WIFSIGNALED(status) )
        {
            printf("Signaled - child: %d, code: %d\n", pid, WTERMSIG(status));
        }
        else
        {
            printf("Paused - child: %d, code: %d\n", pid, WSTOPSIG(status));
        }
    }
    
    return 0;
}

我们通过进程退出状态的相关宏,来得知进程是主动退出还是收到信号退出,并打印出对应的退出状态值或退出信号值

程序运行结果如下图所示:

-1 是 pid 为 368845 的子进程的退出状态值;6 和 8 分别是 pid 为 368846 和 368847 的子进程的退出信号值

僵尸进程 (僵死状态)

理论上,进程 退出 / 终止 后应立即释放所有系统资源

然而,为了给父进程提供一些重要信息,子进程 退出 / 终止 所占的部分资源会暂留

当父进程收集这部分信息后 (wait / waitpid),子进程所有资源被释放

  • 父进程调用 wait(),为子进程 "收尸" 处理并释放暂留资源
  • 若父进程退出,init / systemd 为子进程 "收尸" 处理并释放暂留资源

僵尸进程的危害

僵尸进程保留进程的终止状态和资源使用信息

  • 进程为何退出,进程消耗多少 CPU 时间,进程最大内存驻留值,等

如果僵尸进程得不到回收,那么可能影响正常进程的创建

  • 进程创建最重要的资源是内存和进程标识
  • 僵尸进程的存在可看作一种类型的内存泄露
  • 当系统僵尸进程过多,可能导致进程标识不足,无法创建新进程

僵尸进程初探


#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>


int main(int argc, char* argv[])
{   
    pid_t pid = 0;
    int a = 1;
    int b = 0;
    int status = 0;
    
    printf("parent = %d\n", getpid());
    
    if( (pid = fork()) == 0 ) exit(-1);
    
    printf("child = %d\n", pid);
    
    if( (pid = fork()) == 0 ) abort();
    
    printf("child = %d\n", pid);
    
    if( (pid = fork()) == 0 ) a = a / b, exit(1);
    
    printf("child = %d\n", pid);
    
    sleep(120);
    
    while( (pid = wait(&status)) > 0 )
    {
        if( WIFEXITED(status) )
        {
            printf("Normal - child: %d, code: %d\n", pid, (char)WEXITSTATUS(status));
        }
        else if( WIFSIGNALED(status) )
        {
            printf("Signaled - child: %d, code: %d\n", pid, WTERMSIG(status));
        }
        else
        {
            printf("Paused - child: %d, code: %d\n", pid, WSTOPSIG(status));
        }
    }
    
    return 0;
}


该程序创建了3个子进程,父进程 sleep 120s 后,使用 wait(...) 函数来获取子进程的退出状态

程序运行结果如下图所示:

红框圈出来的是 a.out 程序创建出来的3个子进程

此时,这三个子进程已经运行结束了,父进程还在 sleep 中,用 ps 查看,发现这三个子进程还存在,状态为 Z,处于僵尸态,资源并没有完全释放

父进程 sleep 120s,wait(...) 三个子进程后,再 ps 看下,发现已经没有 a.out 和 它的三个子进程了,此时,子进程的资源被父进程回收了

wait() 的局限性

不能等待指定子进程,如果存在多个子进程,只能逐一等待完成

如果不存在终止的子进程,父进程只能阻塞等待

只针对终止的进程,无法发现暂停的进程

wait() 的升级版 => waitpid

返回值相同,终止子进程标识符

状态值意义相同,记录子进程终止信息

特殊之处:

利用 waitpid(...) 以及 init / systemd 回收子进程


#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

static void worker(pid_t pid)
{
    printf("grand-child: %d\n", pid);
    sleep(150);
}

int main(int argc, char* argv[])
{   
    pid_t pid = 0;
    int status = 0;
    
    printf("parent = %d\n", getpid());
    
    pid = fork();
    
    if( pid < 0 )
    {
        printf("fork error\n");
    }
    else if( pid == 0 )
    {
        int i = 0;
        
        for(i=0; i<5; i++)
        {
            if( (pid = fork()) == 0 )
            {
                worker(getpid());
                break;
            }
        }
        
        sleep(60);
        
        printf("child(%d) is over...\n", getpid());
    }
    else
    {
        printf("wait child = %d\n", pid);     
        sleep(120);
        while( waitpid(pid, &status, 0) == pid )
        {
            printf("Parent is over - child: %d, status = %x\n", pid, status);
        }
    }
    
    return 0;
}


第 22 行,创建了一个子进程

第 34 行,子进程创建了 5 个孙进程

第 49 行,父进程通过 waitpid 来等待子进程运行结束,回收子进程的资源

该程序子进程先运行结束,然后是父进程,最后是孙进程

子进程运行结束后,孙进程就变为了孤儿进程,被 init / systemd 进程接管,孙进程运行结束后,资源由 init / systemd 进程来回收,所以父进程就回收一个子进程的资源即可

程序运行结果如下图所示:

第一阶段,父进程、子进程 和 5个孙进程都在运行

 第二阶段,子进程运行结束,父进程并没有回收它的资源。此时,子进程处于僵尸态,5个孙进程成为孤儿进程,父进程变为 systemd ,由 systemd (pid 为 1) 进程回收资源

 

第三阶段,父进程运行结束,并回收子进程的资源,此时还有 5 个孙进程在运行 

第四阶段,所有进程运行结束,并且资源被回收

在程序设计中,我们可以通过子进程不做其他事情,只创建孙进程来完成任务,父进程 waitpid 子进程的方式来有效的解决僵尸进程带来的问题,这样我们就只需要回收子进程的资源,不用主动回收孙进程的资源了,而是通过 init / systemd 进程自动回收孙进程资源,不过这样就获取不到孙进程的退出状态了

僵尸进程避坑指南

通过 wait(...) 返回值来判断是否继续等待子进程

  • while ( (pid = wait(&status)) > 0 ) { ... }

利用 waitpid(...) 以及 init / systemd 回收子进程

  • 通过两次 fork() 创建孙进程解决子问题

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

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

相关文章

SpringCloud学习路线(1)—— 从头开始的微服务

一、服务架构的历史 现有的服务框架&#xff1a; 单体架构 概念&#xff1a; 将业务所有功能集中在一个项目中开发&#xff0c;打包部署优点&#xff1a; 架构简单&#xff0c;部署成本低缺陷&#xff1a; 耦合度高 分布式架构 概念&#xff1a; 根据业务功能对系统进行拆分&a…

前端两种实现轮播图方式

今天研究两种简单实现轮播图功能的方式。 目录 Layui实现轮播图 码云下载 提取静态文件 示例 注意 参数说明 改为轮播图 增加图片资源文件 轮播栏目修改 改为上下切换 切换事件 脚本中绑定改变事件 控制器查看 Swiper实现轮播图 下载swiper 下载到本地 加载sw…

【数据结构】_3.List接口实现类ArrayList与线性表

目录 1.List接口 1.1 List接口的集合关系网络 1.2 List的使用 2. ArrayList与顺序表 2.1 线性表 2.2 顺序表 2.3 ArrayList 2.3.1 ArrayList的集合关系网络 2.3.2 ArrayList的使用 2.3.2.1 ArrayList的构造方法 2.3.2.2 ArrayList的扩容机制逻辑示图如下&#xff1…

企业如何通过CRM提高客户转化?

现如今&#xff0c;企业想要给潜在客户留下深刻的印象&#xff0c;那么就需要一系列的措施和手段。通过CRM管理系统让潜在客户信任企业&#xff0c;更好地进行客户培育&#xff0c;从而提高潜在客户转化。下面来说说&#xff0c;企业提高潜在客户转化的7个做法。 1、永远不要让…

input模糊搜索

input模糊搜索 getList() {let dicthis.queryParameters()let val this.queryParams["userName"]if (null ! val && ! val) {dic["userName"]*val*;}listPamsArchiveSearch(dic).then(response > {this.nameinfo response.data;});},主要问…

Spring Batch之读数据库——JdbcCursorItemReader之使用框架提供的BeanPropertyRowMapper(三十六)

一、BeanPropertyRowMapper介绍 参考我的另一篇博客&#xff1a; Spring Batch之读数据库——JdbcCursorItemReader&#xff08;三十五&#xff09;_人……杰的博客-CSDN博客 二、项目实例 1.项目框架 2.代码实现 BatchMain.java: package com.xj.demo27;import org.spri…

Echarts环形饼状图设置内外边框

上班快一个月了&#xff0c;最近再写echarts的项目&#xff0c;亚历山大啊 记录一下饼状图设置内外边框的代码 series: [{name: 项目分数,type: pie,radius: [50%, 70%],avoidLabelOverlap: false,labelLine: {show: true},data: [{value: 335, name: 970分以上},{value: 310…

python用playwright自动化测试程序打包exe

playwright自动化测试代码写好后&#xff0c;打包为exe运行在目标PC上可能出现错误。 原因&#xff1a; 1、运行的PC没有响应的浏览器。 2、playwright没有打包到代码中。 所以本例用AutoPytoExe为例来制作exe程序解决问题。 1、安装&#xff1a; 2、安装完成之后&#xff0c;…

CUDA并行归约算法(二)

CUDA并行归约算法&#xff08;二&#xff09; 文章目录 CUDA并行归约算法&#xff08;二&#xff09;前情回顾线程束分化内存组织Reference>>>>> 欢迎关注公众号【三戒纪元】 <<<<< 前情回顾 首先看下上节设计的核函数&#xff0c;如何进行并行…

基于FPGA的视频接口之HDMI1.4(以下)编码

简介 为什么要特别说明HDMI的版本,是因为HDMI的版本众多,代表的HDMI速度同样不同,当前版本在HDMI2.1速度达到48Gbps,可以传输4K及以上图像,但我们当前还停留在1080P@60部分,且使用的芯片和硬件结构有很大差别,故将HDMI分为两个部分说明1080@60以下分辨率和4K以上分辨率(…

docker运行redis容器

参考文章 Redis从入门到精通&#xff08;4&#xff09;&#xff1a;docker运行redis容器详解 问题及总结 docker 命令 拉取镜像&#xff1a;docker pull redis 或者 docker pull redis:bullseye&#xff1b;查看镜像&#xff1a;docker image ls&#xff1b;直接运行 redis…

Flask SQLAlchemy_Serializer ORM模型序列化

在前后端分离项目中&#xff0c;经常需要把ORM模型转化为字典&#xff0c;再将字典转化为JSON格式的字符串。在遇到sqlalchemy_serializer之前&#xff0c;我都是通过类似Java中的反射原理&#xff0c;获取当前ORM模型的所有字段&#xff0c;然后写一个to_dict方法来将字段以及…

Golang假共享(false sharing)详解

多核处理器(SMP)系统中, 每一个处理器都有一个本地高速缓存。内存系统必须保证高速缓存的一致性。当不同处理器上的线程修改驻留再同一高速缓存中的变量时就会发生假共享(false sharing),结果导致高速缓存无效,并强制更新,进而影响系统性能。 什么是假共享(false sharin…

论文解读|VoxelNet:基于点云的3D物体检测的端到端学习

原创 | 文 BFT机器人 01 摘要 论文提出了表述了一个新的基于点云的3D检测方法&#xff0c;名为VoxelNet&#xff0c;该方法是一个端到端可训练的深度学习架构&#xff0c;利用了稀疏点云的结构特性&#xff0c;直接在稀疏的3D点上进行操作&#xff0c;并通过高效的并行处理体素…

Stable Diffusion - ChatGPT4 与 Stable Diffusion 结合提供无限创意构图

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131782672 ChatGPT 和 StableDiffusion 结合使用的优势&#xff1a; 高效率&#xff1a;ChatGPT 可以在很短的时间内完成复杂的语言任务&#xf…

Kotlin基础(六) 枚举类和扩展

前言 本文主要讲解kotlin枚举类和扩展 Kotlin文章列表 Kotlin文章列表: 点击此处跳转查看 目录 1.1 枚举类 1.1.1 枚举类的基本用法 Kotlin中的枚举类&#xff08;enum class&#xff09;用于定义一组具有预定义值的常量。它们在许多情况下都很有用&#xff0c;例如表示一组…

介绍性能压力测试的重要性

在当今数字化时代&#xff0c;软件和应用程序的性能对于用户体验和业务成功至关重要。为了确保系统在面临高负载和压力时能够正常运行&#xff0c;性能压力测试成为一项不可或缺的活动。本文将介绍性能压力测试的重要性。 性能压力测试是一种通过模拟实际场景中的负荷和用户访问…

Echarts中饼状图,图例显示value而不是name

直接上代码 formatter(name) {var tarValue;for (var i 0; i < data.length; i) {if (data[i].name name) {tarValue data[i].value;}}var v tarValue;return [tarValue]} 效果图

spring boot 多模块项目搭建Knife4j文档,swagger-ui x2

介绍: knife4j jeecg-boot用的就是这个&#xff0c;我之前要搭过swagger-ui&#xff0c;但外观&#xff0c;体验都没有knife4j好&#xff0c;我没记错的话已经停止发布版本了&#xff0c;所以我的多模块项目就用到了这个&#xff0c;还搭建了jwt token获取我也是在网上找的…

260道2023最新网络安全工程师面试题(附答案)

2023年过去了一大半&#xff0c;先来灵魂三连问&#xff0c;年初定的目标完成多少了&#xff1f;薪资涨了吗&#xff1f;女朋友找到了吗&#xff1f; ​好了&#xff0c;不扎大家的心了&#xff0c;接下来进入正文。 由于我之前写了不少网络安全技术相关的文章和回答&#xff…