Linux中的进程

进程控制

fork 函数

fork 函数从已存在的进程中创建新的进程,已存在进程为父进程,新创建进程为子进程

fork 的常规用法

  1. 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求
  2. 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数

fork 失败的原因

  1. 系统中有太多的进程
  2. 实际用户的进程数超过了限制

fork 通过写时拷贝的方式进行内容的修改:

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本

返回值:子进程返回 0,父进程返回子进程 pid,出错返回-1

返回子进程 pid 的原因:方便管理子进程

进程创建时,先分配 task_struct 然后分配空间

进程退出时,先回收资源,然后销毁 task_struct

僵尸进程

一个进程的关闭是先回收资源,然后再将 PCB 清理,僵尸进程就是 PCB 未被清理的进程

在系统中有个 "?" 环境变量,这个变量用来获取子进程的返回值,0 表示成功,非零表示失败,同时不同的非零值可以表示不同的失败原因,虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255

进程退出的方式

  • 正常终止(可以通过echo $? 查看进程退出码):
  1. 从main返回
  2. 调用exit
  3. _exit
  • 异常退出:
  1. ctrl + c,信号终止

进程退出时,会产生退出码和退出信号,进程会将这两个值写入 PCB 中,这样就获取到了退出的信息,如果进程是异常的,将会产生退出信号,通过退出信号就能判断出异常的原因,如果没有退出信息,就可以继续查看退出码

_exit 函数

#include<unistd.h>
void _exit(int status); 
//参数:status 定义了进程的终止状态,父进程通过wait来获取该值 
//虽然status是int,但是仅有低8位可以被父进程所用。
//所以_exit(-1)时,在终端执行$?发现返回值是255。

exit 函数

#include <unistd.h>
void exit(int status);

exit 和 _exit 的区别

  • exit()正常终止,会执行清理操作。
  • _exit()立即终止跳过清理操作,更底层。
  • _exit() 是一个低层次系统调用,直接返回内核。
    • 它保证:
    • 立刻终止
    • 不执行任何用户态清理逻辑

子进程退出时推荐使用 _exit,因为 _exit 不会刷新缓冲区,避免了多次刷新,因为在父进程结束时还会再刷新一次

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main() {printf("Hello world\n"); // 缓冲区中暂存pid_t pid = fork();if (pid == 0) {// 子进程exit(0);  // 刷新缓冲区 → 输出 "Hello world"} else {// 父进程wait(NULL);}return 0;  // 父进程也刷新缓冲区 → 再输出一次
}

exit() 在退出进程时会刷新缓冲区,而 _exit() 不会

exit() 是库函数,而 _exit() 是系统调用

exit最后也会调用 _exit,
但在调用exit之前,还做了其他工作:

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit

return退出

return是一种常见的退出进程方法。执行return n;等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数

僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程

进程等待

子进程退出后,若父进程不进行任何操作,将会产生僵尸进程,造成内存泄漏,一旦进程变成僵尸进程,将会无法被杀死

进行进程等待的原因:

父进程通过等待来解决僵尸进程的问题

父进程获取子进程的退出信息,知道子进程是什么原因退出的

wait

#include <sys/types.h>/* 提供类型pid_t的定义*/#include <wait.h>pid_t wait(int *status)
返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

作用:进程一旦调用了wait,就会立刻阻塞自己,由wait分析当前进程中的某个子进程是否已经退出了,如果让它找到这样一个已经变成僵尸进程的子进程,wait会收集这个子进程的信息,并将它彻底销毁后返回;如果没有找到这样一个子进程,wait会一直阻塞直到有一个出现

等待就是将父进程设置为 S 状态,然后将父进程的 PCB 链接到子进程,此时就能获取到子进程的退出状态

阻塞:子进程没有结束,父进程执行 wait,等待某种条件的发生,此时就发生了阻塞,阻塞本质上就是进程不在调度队列上,CPU 不执行进程的代码

非阻塞等待:在等待的过程中还可以继续执行进程, 调用者立刻返回,如果事件未发生,不会停在那里等待 ,可能会导致没有等待到子进程的问题

阻塞等待:在等待过程中只能等待,不能做其他任何事情,不就绪就不返回

特性

阻塞等待

非阻塞等待

调用行为

卡住,直到子进程结束

立即返回,可能没等到子进程

CPU 资源使用

更节省(系统调度)

需要你自己轮询,可能浪费 CPU

使用场景

同步执行、流程控制

异步程序、服务端进程池管理

接口实现

wait(), waitpid(..., 0)

waitpid(..., WNOHANG)

waitpid

 waitpid(pid_t pid, int *status, int options); 
返回值:当正常返回的时候waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;返回值>0:等待成功,子进程退出,父进程成功获取退出信息返回值<0:等待失败返回值==0:检测成功,但是子进程还没退出,需要下一次重复等待参数:pid:Pid=-1,等待任一个子进程。与wait等效。Pid>0.等待其进程ID与pid相等的子进程。status:WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)options:WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。(非阻塞等待)

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息

如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞

如果不存在该子进程,则立即出错返回

waitpid 作用和 pid 等价

等待失败的情况:id 值填错

非阻塞等待+循环=非阻塞轮询,较为常用,能够允许父进程在等待的时候进行其他操作

获取子进程status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。

如果传递NULL,表示不关心子进程的退出状态信息。 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

waitpid 和 wait 的区别

函数

wait

waitpid

原型

pid_t wait(int *status);

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

功能

等待任意一个子进程结束

根据条件等待一个或多个特定子进程结束

waitpid 的参数:

  • pid: 可指定要等待的子进程。
    • >0: 等待指定 PID 的子进程。
    • -1: 等价于 wait,等待任意一个子进程。
    • 0: 等待与当前进程同组的任何子进程。
    • <-1: 等待特定进程组 ID 的任何子进程。
  • options: 控制行为,例如:
    • WNOHANG: 非阻塞地检查子进程是否结束。
    • WUNTRACED: 也报告已停止(但未终止)的子进程。

获取子进程 status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充
  • 如果传递NULL,表示不关心子进程的退出状态信息
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位)

printf("child exit code:%d\n", (status>>8)&0XFF);
//退出状态在前8位,因此将后8位移除,通过和FF与操作,将退出状态中为1的保留,为0的舍去
printf("sig code : %d\n", status&0X7F );//将低7位按位与得到终止信号

 

进程程序替换

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变

exec 函数可以让进程替换掉自己的代码和数据转而执行其他的程序

原理:exec 将被替换程序从外存中加载到内存中,将原来进程的代码和数据替换掉,task_struct 并没有被替换,因此也就没有创建新的进程

exec*系列函数在执行完毕后,后面的代码也就不会被执行了,因为已经被 exec 所执行的函数替换掉了

exec*可以不用在乎其返回值,因为一旦执行成功,后续的代码全部被替换;

一旦失败,就会继续向下执行

子进程执行 exec 时,由于进程具有独立性,因此会将原来父进程的数据和代码重新拷贝一份,然后在新拷贝的地方进行替换代码,这样就不会影响父进程,此时的父子进程就在数据结构和代码层面上彻底的分离了

一共有 6 中 exec 系列函数#include<unistd.h>
int execl(const char *path, const char *arg, ...);后面可以加上多个命令的参数,但必须以NULL结尾
int execlp(const char *file, const char *arg, ...);不需要传程序的路径,因为会自动在环境变量中查找
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);和execl类似,只不过是将参数放入argv[]中
int execvp(const char *file, char *const argv[]);不需要传程序的路径,因为会自动在环境变量中查找
int execve(const char *path, char *const argv[], char *const envp[]);envp为环境变量,可以传入,也可以自定义,整体把所有环境变量替换掉

命名解释: l(list) : 表示参数采用列表

v(vector) : 参数用数组

p(path) : 有p自动搜索环境变量PATH

e(env) : 表示自己维护环境变量

exec/exit就像call/return

一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。

这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程序之内的模式扩展到程序之间。如下图

一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来 返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值

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

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

相关文章

EDR与XDR如何选择适合您的网络安全解决方案

1. 什么是EDR&#xff1f; 端点检测与响应&#xff08;EDR&#xff09; 专注于保护端点设备&#xff08;如电脑、服务器、移动设备&#xff09;。通过在端点安装代理软件&#xff0c;EDR实时监控设备活动&#xff0c;检测威胁并快速响应。 EDR核心功能 实时监控&#xff1a;…

AGI大模型(21):混合检索之混合搜索

为了执行混合搜索,我们结合了 BM25 和密集检索的结果。每种方法的分数均经过标准化和加权以获得最佳总体结果 1 代码 先编写 BM25搜索的代码,再编写密集检索的代码,最后进行混合。 from rank_bm25 import BM25Okapi from nltk.tokenize import word_tokenize import jieb…

2025最新的软件测试面试大全(含答案+文档)

一、软件测试基础面试题 1、阐述软件生命周期都有哪些阶段? 常见的软件生命周期模型有哪些? 软件生命周期是指一个计算机软件从功能确定设计&#xff0c;到开发成功投入使用&#xff0c;并在使用中不断地修改、增补和完善&#xff0c;直到停止该软件的使用的全过程(从酝酿到…

C++.神经网络与深度学习(二次修改)

神经网络与深度学习 1. 神经网络基础1.1 神经元模型与激活函数1.2 神经网络结构与前向传播2.1 损失函数与优化算法均方误差损失函数交叉熵损失函数梯度下降优化算法2.2 反向传播与梯度计算神经元的反向传播3.1 神经元类设计与实现神经元类代码实现代码思路3.2 神经网络类构建神…

FPGA图像处理(六)------ 图像腐蚀and图像膨胀

默认迭代次数为1&#xff0c;只进行一次腐蚀、膨胀 一、图像腐蚀 1.相关定义 2.图像腐蚀效果图 3.fpga实现 彩色图像灰度化&#xff0c;灰度图像二值化&#xff0c;图像缓存生成滤波模块&#xff08;3*3&#xff09;&#xff0c;图像腐蚀算法 timescale 1ns / 1ps // // Des…

中国版Cursor:CodeBuddy腾讯云代码助手使用体验

我正在参加CodeBuddy「首席试玩官」内容创作大赛&#xff0c;本文所使用的 CodeBuddy 免费下载链接&#xff1a;腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴” 1.CodeBuddy简介 腾讯云代码助手CodeBuddy&#xff0c;这个是一款编程插件&#xff0c;我们可以在各个编程…

Go语言 GORM框架 使用指南

在 Go 语言社区中&#xff0c;数据库交互一直是开发者们关注的重点领域&#xff0c;不同开发者基于自身的需求和偏好&#xff0c;形成了两种主要的技术选型流派。一部分开发者钟情于像sqlx这类简洁的库&#xff0c;尽管其功能并非一应俱全&#xff0c;但它赋予开发者对 SQL 语句…

从零开始学习three.js(18):一文详解three.js中的着色器Shader

在WebGL和Three.js的3D图形渲染中&#xff0c;着色器&#xff08;Shader&#xff09; 是实现复杂视觉效果的核心工具。通过编写自定义的着色器代码&#xff0c;开发者可以直接操作GPU&#xff0c;实现从基础颜色渲染到动态光照、粒子效果等高级图形技术。本文将深入解析Three.j…

Python函数库调用实战:以数据分析为例

一、引言 Python之所以在编程领域广受欢迎&#xff0c;很大程度上得益于其丰富且强大的函数库。这些函数库涵盖了从数据分析、科学计算到Web开发、机器学习等众多领域&#xff0c;极大地提高了开发效率。本文将以数据分析为例&#xff0c;介绍如何调用Python的一些常用函数库。…

shell脚本之条件判断,循环控制,exit详解

if条件语句的语法及案例 一、基本语法结构 1. 单条件判断 if [ 条件 ]; then命令1命令2... fi2. 双分支&#xff08;if-else&#xff09; if [ 条件 ]; then条件为真时执行的命令 else条件为假时执行的命令 fi3. 多分支&#xff08;if-elif-else&#xff09; if [ 条件1 ]…

现代 Web 自动化测试框架对比:Playwright 与 Selenium 的深度剖析

现代 Web 自动化测试框架对比&#xff1a;Playwright 与 Selenium 的深度剖析 摘要&#xff1a;本文对 Playwright 与 Selenium 在开发适配性、使用难度、场景适用性及性能表现等方面进行了全面深入的对比分析。通过详细的技术实现细节阐述与实测数据支撑&#xff0c;为开发者…

系统架构设计(十):结构化编程

定义 结构化编程是一种遵循清晰逻辑结构、避免使用 goto 的编程方法。它强调使用有限的三种基本控制结构来组织程序&#xff0c;提高程序的可读性、可维护性和可测试性。 它是现代程序设计的基础&#xff0c;被广泛应用于命令式语言&#xff08;如 C、Pascal、Java&#xff0…

TC3xx学习笔记-UCB BMHD使用详解(二)

文章目录 前言Confirmation的定义Dual UCB: Confirmation StatesDual UCB: Errored State or ECC Error in the UCB Confirmation CodesECC Error in the UCB ContentDual Password UCB ORIG and COPY Re-programming UCB_BMHDx_ORIG and UCB_BMHDx_COPY (x 0-3)BMHD Protecti…

OTA与boot loader

OTA指的是无线升级&#xff0c;通常用于更新设备的固件或软件&#xff0c;用户不用手动操作&#xff0c;非常方便。而bootloader是启动时加载操作系统的程序&#xff0c;负责硬件初始化和启动流程。 首先&#xff0c;OTA是如何通过bootloader工作的。OTA下载更新包后&#xff0…

实验六:FPGA序列检测器实验

FPGA序列检测器实验(远程实验系统) 文章目录 FPGA序列检测器实验(远程实验系统)一、数字电路基础知识1. 时钟与同步2. 按键消抖原理代码讲解:分频与消抖3. 有限状态机(FSM)设计代码讲解:状态机编码与转移4. 边沿检测与信号同步5. 模块化设计二、实验数字电路整体思想三…

jenkins部署

开发者将代码push到git运维人员通过jenkins部署&#xff0c;自动到git上pull代码通过maven构建成jar包&#xff0c;并结合dockerfile打包成镜像&#xff0c;push docker镜像到docker registry通过k8s发起 发布/更新 服务 操作 通过Jenkins部署&#xff0c;自动到Git上PULL代码 …

BBR 的 buffer 动力学观感

这周很忙&#xff0c;今天还加了一天班&#xff0c;但还是抽空实现了五一在安徽泾县山区喝着一壶酒写的 BBR ProbeRTT 的想法&#xff0c;没多少行代码&#xff0c;它真就消除了带宽锯齿&#xff0c;皮了个鞋&#x1f45e;&#xff0c;昨天我还在群里说了今天再说说 BBR 的&…

第9讲、深入理解Scaled Dot-Product Attention

Scaled Dot-Product Attention是Transformer架构的核心组件&#xff0c;也是现代深度学习中最重要的注意力机制之一。本文将从原理、实现和应用三个方面深入剖析这一机制。 1. 基本原理 Scaled Dot-Product Attention的本质是一种加权求和机制&#xff0c;通过计算查询(Query…

el-tree结合checkbox实现数据回显

组件代码 <el-tree:data"vertiList"show-checkboxnode-key"id":props"defaultProps"ref"treeRefx"class"custom-tree"check-change"handleCheckChange"> </el-tree>获取选择的节点 handleCheckChan…

OpenResty 深度解析:构建高性能 Web 服务的终极方案

引言 openresty是什么&#xff1f;在我个人对它的理解来看相当于嵌入了lua的nginx; 我们在nginx中嵌入lua是为了不需要再重新编译,我们只需要重新修改lua脚本,随后重启即可; 一.lua指令序列 我们分别从初始化阶段&#xff0c;重写/访问阶段&#xff0c;内容阶段&#xff0c;日志…