深入 Linux 进程

news/2024/11/21 1:34:28/

问题

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

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

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

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

设计原则:

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

思考

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

#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.ppmy.cn/news/965728.html

相关文章

小程序控制台警告:DevTools failed to load SourceMap(控制台报错DevTools 无法加载来源映射)

在调试项目的时候&#xff0c;控制台报错:**DevTools failed to load SourceMap: Could not load content for http://xxx.js. 这段报错的意思是dev工具未能成功加载source map&#xff08;文件映射&#xff09;。这里的报错实际上和项目本身的代码没有任何关系&#xff0c;而是…

MySQL表关联更新

背景&#xff1a; 有两张表&#xff0c;一张class信息表&#xff0c;一张student信息表&#xff0c;但student表里的信息存在错误&#xff0c;需要用class表中的信息去更新student表数据。 方法一&#xff1a; update student_info s set class_name (select class_name fr…

1、两数之和

给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 示例 1&#xff1a; 输入&#xff1a;nums [2,7,11,15], target 9 输出&#xff1a;[0,1] 解释&#xff1a;因为 nums…

ChatGPT分销版多开v3.8.0

新增 系统敏感词支持对提问与回答内容进行检测并替换为** 已完成 彻底优化上下文问题 已完成 优化 后台绘画记录支持展示用户头像昵称 已完成 优化 4.0加载速度改为打字特效 已完成 修复 分销商排行榜累计佣金问题是income_sum而不是money 已完成 后台 音速API 更名为INS…

报名截止,最后十天!AI GPT 应用创新Hackathon,赢$26000奖金

点击蓝字 关注我们 01 引言 AI & GPT 黑客松活动 融合人工智能与Web3&#xff1a;构建下一代去中心化应用 GPT作为人工智能AI的先锋之一&#xff0c;以其惊人的自然语言处理和创作能力闻名。而Web3则代表着区块链和去中心化应用的浪潮&#xff0c;将信任、安全和隐私保护推…

利器在手,华为要让合作伙伴IT建设和运维不再是难事

华为针对商业市场和分销市场伙伴需要&#xff0c;发布了全新平台工具——华为蓝鲸应用商城和DME IQ&#xff0c;以构建一种全新的IT基础设施建设和运维模式&#xff0c;帮助伙伴用好超融合。 出品 | 常言道 作者 | 丁常彦 近半年来&#xff0c;ChatGPT火爆全球&#xff0c;让人…

文心一言绘图无需代码连接飞书即时消息的方法

1 使用场景 随着chatgpt的大火&#xff0c;带来了一波人工客服智能机器人的热潮&#xff0c;除自动聊天外&#xff0c;又增加了AI生成图像的功能&#xff0c;也有越来越多企业关注到了AI绘画的热度&#xff0c;并选择在这一领域加速布局。 文心一言作为国内代表&#xff0c;自然…

程序员怎么就不能去外包公司

前言 程序员怎么就不能去外包公司&#xff0c;我真的很奇怪&#xff0c;为什么总有人劝不要去外包公司。 都是听人劝&#xff0c;吃饱饭。但是听这样的话真的能吃饱饭吗&#xff1f; 今天就来聊聊程序员到底能不能去外包公司。 软件外包公司是做什么的&#xff1f; 软件外…