Linux:进程(三)

1. 进程创建补充

fork之后父子两个执行流分别执行,fork之后谁谁先执行由调度器来决定。

一般,父子代码共享。当父子不再写入时,数据也是共享的,但是当有一方要写入,就触发写时拷贝

fork调用失败的原因

1. 系统中有太多进程

2. 实际用户的进程数超过了限制

2. 进程终止

进程终止的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的代码和数据。

 进程退出主要有以下三个场景

1. 代码运行完毕,结果正确

2. 代码运行完毕,结果不正确

3. 代码异常终止

 如果是1和2这种正常终止的情况可以通过 echo $ ?  来查看进程退出码

退出码

退出码(退出状态)可以告诉我们最后一次执行的命令的状态。在命令结束后,我们可以知道命令是正确完成了还是错误结束的,是操作系统用来判断进程是否完成任务的方式。程序返回退出代码0时表示执行成功,返回0以外的任何代码都被视为不成功

正常退出有以下方法

1. 从main返回 

2. 调用exit   头文件<stdlib.h>

3. 调用_exit  头文件<unistd.h>

#include<stdio.h>#include<stdlib.h>#include<unistd.h>int main(){printf("hello world\n");//_exit(101);                                                                         exit(100);return 33;}     

 以下结果分别是return  exit 与 _exit的结果

 

当代码异常终止时:退出码无意义

程序的进程退出码是写在task_struct内部的,我们可以通过strerror函数来获取退出码的描述,

strerror(1);
strerror(2);
....
exit与_exit函数
#include<unistd.h>
void _exit(int status);
//status定义了进程的终止状态,父进程通过wait来获取该值
//status是int,但是只有低八位(后面说明)可以被父进程使用_exit(-1),echo $? 到的返回值是255#include<stdlib.h>
void exit(int status);

 exit最后也会调用_exit,但是还要先执行一些操作

1. 执行用户通过atexit或者on_exit定义的清理函数。

2. 关闭所有打开的流,所有缓存数据都被写入

3. 调用_exit

 _exit就直接退出进程,不会对缓冲区进行刷新

用以下代码来验证一下

#include<stdio.h>#include<stdlib.h>#include<unistd.h>int main(){printf("hello world");//先测试完_exit再将其删掉测试exit_exit(101);                                                                         exit(100);return 0;}     

 _exit结果如下

exit结果如下

 所以缓冲区一定不是操作系统内部的,在用户空间中,C语言标准库提供

三种方式

exit 是C库里的

_exit 是系统给的

但是实际上exit内部(底层封装)是用了_exit

main函数中的return表示进程完成而其他函数中的return只表示自己的函数调用完成,但是exit与_exit在哪调用都表示进程结束并返回给父进程bash子进程的退出码。

main函数中的 return x; 等价于执行exit(x); 因为在main函数运行结束后会将main函数的返回值当做exit参数来调用exit函数

 3. 进程等待

为什么要有进程等待

1.子进程退出,父进程如果不管不顾,就可能造成僵尸进程内存泄漏

2.变成僵尸进程后,就连kill -9 也对它没有办法,因为不能杀死一个死去的进程

3.我们需要知道父进程给子进程的任务如何了,例如子进程运行完成结果是否正确,是否正常退出了。

4.父进程通过等待的方式来回收子进程资源,获取子进程退出信息(可以选择的)

 wait函数与waipid函数
//头文件
#include<sys/types.h>
#include<sys/wait.h>pid_t wait(int* status);pid_t waitpid(pit_t pid,int* status,int options);

wait函数:

返回值:成功返回被等待进程的pid,失败返回-1。

参数:输出型参数,用来获取子进程退出状态,不关心可以设置成NULL。

waitpid函数:

返回值:

正常返回时waitpid返回收集到的子进程的进程id

如果第三个参数选择了WNOHANG,而调用waitpid发现没有已退出的子进程可收集,则返回0

如果调用中出错,则返回-1,这时errno会设置成相应的值以指示错误所在;

参数

pid:pid==-1,等待任一个子进程,与wait等效。pid>0,等待其进程ID与pid相等的子进程。

status:输出型参数,不关心可设置为NULL

options:默认为0,表示阻塞等待。

设置为WNOHANG时:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待,若正常结束返回该子进程的ID

status参数

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

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

status不能简单的当成整形来看,可以当做位图来看,如下图所示

 低七个比特位全0,一旦不是全0就是异常退出的,退出码就无意义了

 status在代码正常运行完毕的情况下是进程退出码,异常终止是保存异常时所对应的信号编号。

所以正常看高8位,异常时看低7位,第8位是coredump 标志用一下操作

(status>>8)&0xff;//获取高八位的值status&0x7f;//获取第七位的值

这两个操作,操作系统为我们提供了两个宏来表示对应的退出码和退出信号

(1)  WIFEXITED(status):若为正常终止子进程返回的状态则为真。(查看进程是否正常退出)

(2)  WEXITSTATUS(status):若WIFEXITED为0,提取子进程退出码。(查看进程的退出码)

#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/wait.h>#include<errno.h>#include<string.h>#include<sys/types.h>int main(){pid_t pid;if((pid=fork())==-1){perror("fork");exit(1);}if(pid==0){printf("子进程pid:%d\n",getpid());sleep(20);exit(10);}else{int st;int ret=wait(&st);//if(ret>0&&(st&0x7F)==0)//后七位为0{                                                                                 printf("子进程退出代码为:%d\n",(st>>8)&0xFF);}else if(ret>0)printf("终止信号:%d",st&0x7F);}return 0;
}

 让子进程正常终止

父进程成功获取退出信息。运行中在另一台终端将子进程kill掉

输出结果是9,父进程同样等待成功了

我们来使用waitpid与两个宏来看一下代码如下

 #include<stdio.h>2 #include<stdlib.h>3 #include<unistd.h>4 #include<sys/wait.h>5 #include<errno.h>6 #include<string.h>7 #include<sys/types.h>8 int main()9 {10     pid_t pid;11     if((pid=fork())==-1)12     {13         perror("fork");14         exit(1);15     }16     if(pid==0)17     {18         printf("子进程pid:%d\n",getpid());19         sleep(20);20         exit(10);21     }else if(pid>0)23     {24         int st;25         int ret=waitpid(pid,&st,0);//26                                                                                           27         if(ret>0&&WIFEXITED(st))//子进程正常结束返回真28         {29             printf("子进程退出代码为:%d\n",WEXITSTATUS(st));30         }31         else if(ret>0)32             printf("终止信号:%d",st&0x7F);33     }34     return 0;35 }

结果一样 

阻塞等待

验证阻塞等待

#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/wait.h>#include<errno.h>#include<string.h>#include<sys/types.h>int main(){for(int i=0;i<10;i++){pid_t pid;pid=fork();if(pid<0){printf("%s fork error\n",__FUNCTION__);//一个宏会自动替换为该函数名字}else if(pid==0){pid_t p1=getpid();printf("此子进程pid:%d\n",p1);sleep(2);exit(11);}else if(pid>0){int status=0;pid_t ret=waitpid(-1,&status,0);//阻塞式等待                                  printf("检测waitpid \n");if(ret==pid&&WIFEXITED(status)){printf("等待孩子成功,return pid: %d\n",WEXITSTATUS(status));}else{printf("失败\n");return 1;}}}return 0;
}

非阻塞等待

大部分的父子进程关系中,当子进程未退出时,父进程通常处于阻塞等待的状态,在此期间父进程不能进行其他操作,我们可以通过将waitpid的第三个参数设置为WNOHANG,即在没有子进程退出时立即返回不等待,等待方可以做自己的事情(通过函数指针或者function<>;包装器)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>#include <sys/wait.h>void test(){printf("hello\n");
}
int main()
{pid_t pid;pid=fork();if(pid<0){printf("%s fork error\n",__FUNCTION__);//一个宏会自动替换为该函数名字}else if(pid==0){pid_t p1=getpid();printf("此子进程pid:%d\n",p1);sleep(2);exit(11);}else{int status=0;pid_t ret=waitpid(pid,&status,WNOHANG);                                                               //pid_t ret=waitpid(pid,&status,0);//阻塞式等待test();test();test();printf("检测waitpid \n");if(ret==pid&&WIFEXITED(status)){printf("等待孩子成功,return pid: %d\n",WEXITSTATUS(status));}else{printf("失败\n");return 1;}}return 0;
}

阻塞等待先等子进程完毕再进行自己的操作,子进程不结束,父进程会阻塞在wait/waitpid调用处

非阻塞等待,先执行了函数调用,waitpid发现没有要退出的子进程直接返回0

另外:

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

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

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

 4. 进程替换

我们通过fork函数创建子进程后,父子会各自执行父进程的一部分代码,我们可以通过进程替换来让子进程执行一个全新的程序。

进程程序替换的概念

程序替换是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载到调用进程的地址空间之中。

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

其原理如下图所示

进程替换函数

以exec开头的函数,统一叫做exec函数:

#include <unistd.h>int execl(const char *path, const char *arg, ...);
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[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

需要注意

一旦程序替换成功了,就去执行新的代码了,原始代码的后半部分就不存在了

所以exec函数只有失败返回值,没有成功返回值

调用失败返回-1

在进程替换的过程中不是创建了新的进程只是把当前进程的代码和数据覆盖式的进行替换

不会影响父进程(进程具有独立性)->会释放旧空间(子进程首先释放其当前占用的地址空间,包括之前从父进程继承并共享的代码段、数据段、堆和栈等)->重新分配空间(根据新程序要求为子进程重新分配新的地址空间,这个地址空间是专门为新程序准备的)->加载新程序(通过加载器使得程序从新程序的入口点开始执行)

execl函数

 int execl(const char path,const char *arg,...);

path:路径+程序名(要执行谁)

arg:可变参数列表(怎么执行它)

结尾必须以NULL表明参数传递完成

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>int main()
{pid_t id=fork();if(id<0){perror("fork() error\n");}if(id==0){printf("child\n");execl("/usr/bin/ls","ls","-l","-a",NULL);perror("execl error\n");exit(-1);}else if(id>0){int status;pid_t tmp=waitpid(id,&status,0);if(tmp<0){                                                                                                                                                                                                          perror("waitpid error\n");exit(1);}if(WIFEXITED(status)){printf("exit code:%d\n",WEXITSTATUS(status));}}return 0;
}

将上面的语句替换为下方语句来执行一个cpp程序

        execl("./hello","./hello",NULL);

 即在当前目录下寻找hello,并执行

当然也可能调用python或者是java的程序

execlp函数

int execlp(const char *file, const char *arg, ...);
函数名字比上面多了一个p,这个p表示环境变量,无需写全路径了

file只要说明要执行的文件名即可(execlp会自动在环境变量PATH中查找指定的命令)作用:要执行谁

arg:同上可变模板参数(要怎么执行)

NULL结尾

execlp("ls", "ls", "-a", "-l", NULL);//执行ls -a -l
execlp("./hello", "./hello", NULL);//执行C++程序

execv函数

int execv(const char *path, char *const argv[]);

名字比第一个多了v:即vector数组

path:路径+程序名(要执行谁)

argv:一个命令行参数表(一个指针数组)

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };//通过指针数组
execv("/usr/bin/ls", myargv);

execle函数

 int execle(const char *path, const char *arg, ...,char *const envp[]);

path:路径+程序名

arg:可变参数列表

envp:自己设置的环境变量(传到替换程序的main参数env数组中)

char* env[] = { (char *cibst)"MYVAL=2025", NULL };//自己的环境变量
execle("./hello", "./hello", NULL, env);//执行./hello

其他的参数就是将以上的组合起来使用

 总结一下

l(list):表示参数采用列表

v(vector):参数用数组

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

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

函数名参数格式是否使用当前环境变量
execl列表
execlp列表
execle列表需要自己组装环境变量
execv数组
execvp数组
execve数组需要自己组装环境变量

只有execve是真正的系统调用其他的最终都调用了execve,所以execve在man手册的第二节,其他函数在man手册第三节。各个函数关系如下图所示


这篇就到这里啦(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

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

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

相关文章

一、vue智能Ai对话(高仿通义千问)普通版。

如需源码&#xff1a;请私信。 普通版视频地址&#xff1a;普通版视频 流式进阶版视频地址&#xff1a;流式进阶版视频 流式进阶版&#xff1a;流式进阶版源码 html结构和js方法&#xff1a; <!DOCTYPE html> <html lang"zh"><head><meta …

Taro+Vue实现图片裁剪组件

cropper-image-taro-vue3 组件库 介绍 cropper-image-taro-vue3 是一个基于 Vue 3 和 Taro 开发的裁剪工具组件&#xff0c;支持图片裁剪、裁剪框拖动、缩放和输出裁剪后的图片。该组件适用于 Vue 3 和 Taro 环境&#xff0c;可以在网页、小程序等平台中使用。 源码 https:…

【winRAR】windows11右键直接打开winRAR

总览 目前能够完成的操作不能像 win10 那样全面&#xff0c;需要做一些取舍&#xff0c;这两种解决后的样子任选其一&#xff1a; 1.右键之后&#xff0c;直接显示 “解压到当前文件夹” 2.右键之后&#xff0c;直接出现 winRAR 的母菜单&#xff0c;在鼠标 hover 到上面的时…

云计算、AI与国产化浪潮下DBA职业之路风云变幻,如何谋破局启新途?

引言 在近日举办的一场「云和恩墨大讲堂」直播栏目中&#xff0c;云和恩墨联合创始人李轶楠、副总经理熊军和欧冶云商数据库首席薛晓刚共同探讨了DBA的现状与未来发展。三位专家从云计算、人工智能、国产化替代等多个角度进行了深入的分析和探讨&#xff0c;为从业者提供了宝贵…

STM32 FreeRTOS 任务挂起和恢复---实验

实验目标 学会vTaskSuspend( )、vTaskResume( ) 任务挂起与恢复相关API函数使用&#xff1a; start_task:用来创建其他的三个任务。 task1&#xff1a;实现LED1每500ms闪烁一次。 task2&#xff1a;实现LED2每500ms闪烁一次。 task3&#xff1a;判断按键按下逻辑&#xff0c;KE…

2025年PHP面试宝典,技术总结。

面试是进入职场的第一道坎&#xff0c;因为我本身学校太一般的问题在面试中遇到了各种不爽&#xff0c;和那些高学历的相比自己真是信心大跌。我面试的方向是php开发工程师&#xff0c;主要做网站后台、APP接口等。下面是我这段时间总结的面试方面的常考常问的知识点&#xff0…

Python运算符

1、算术运算符 加 减— 乘* 除/ 整除// 取余% 幂运算** 优先级&#xff1a; 第1级&#xff1a;** 第2级&#xff1a;* &#xff0c; / &#xff0c; % &#xff0c;// 第3级&#xff1b; &#xff0c; - print("加", 1 4) print("减",8 - 19) p…

RTMP|RTSP播放器只解码视频关键帧功能探讨

技术背景 我们在做RTMP|RTSP直播播放器的时候&#xff0c;遇到过这样的技术诉求&#xff0c;在一些特定的应用场景中&#xff0c;可能只需要关键帧的信息&#xff0c;例如视频内容分析系统&#xff0c;可能只对关键帧进行分析&#xff0c;以提取特征、检测对象或场景变化。鉴于…

2024年度总结-CSDN

2024年CSDN年度总结 Author&#xff1a;OnceDay Date&#xff1a;2025年1月21日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 文章目录 2024年CSDN年度总结1. 整体回顾2…

【Node.js]

一、概述 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境 &#xff0c;使用了一个事件驱动、非阻塞式I/O模型&#xff0c; 让JavaScript 运行在服务端的开发平台&#xff0c;它让JavaScript成为与PHP、Python、Perl、Ruby等服务端语言平起平坐的脚本语言。 官网地…

【基于无线电的数据通信链】Link 11 仿真测试

〇、废话 Link 11 仿真测试 涉及多个方面&#xff0c;包括信号仿真、协议模拟、数据链路层的仿真以及网络性能评估等。Link 11 是一种基于 HF&#xff08;高频&#xff09; 或 UHF&#xff08;超高频&#xff09; 波段的无线通信协议&#xff0c;主要用于军事通信系统中。为了…

iOS 网络请求: Alamofire 结合 ObjectMapper 实现自动解析

引言 在 iOS 开发中&#xff0c;网络请求是常见且致其重要的功能之一。从获取资料到上传数据&#xff0c;出色的网络请求框架能夠大大提升开发效率。 Alamofire 是一个极具人气的 Swift 网络请求框架&#xff0c;提供了便据的 API 以完成网络请求和响应处理。它支持多种请求类…

分布式多卡训练(DDP)踩坑

多卡训练最近在跑yolov10版本的RT-DETR&#xff0c;用来进行目标检测。 单卡训练语句&#xff08;正常运行&#xff09;&#xff1a; python main.py多卡训练语句&#xff1a; 需要通过torch.distributed.launch来启动&#xff0c;一般是单节点&#xff0c;其中CUDA_VISIBLE…

RV1126+FFMPEG推流项目(8)AENC音频编码模块

本节分享的是AENC音频编码模块&#xff0c;是负责在AI模块通道里面取出收集到的音频数据&#xff0c;进行编码。了解AENC模块之前&#xff0c;先来看一个数据结构“RV1126_AENC_CONFIG”&#xff0c;这个数据结构是自己封装的&#xff0c;里面有AENC通道号&#xff0c;和内部描…

智能新浪潮:亚马逊云科技发布Amazon Nova模型

在2024亚马逊云科技re:Invent全球大会上&#xff0c;亚马逊云科技宣布推出新一代基础模型Amazon Nova&#xff0c;其隶属于Amazon Bedrock&#xff0c;这些模型精准切入不同领域&#xff0c;解锁多元业务可能&#xff0c;为人工智能领域带来革新。 带你认识一起了解Amazon Nova…

【Prometheus】PromQL进阶用法

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

C++《AVL树》

在之前的学习当中我们已经了解了二叉搜索树&#xff0c;并且我们知道二叉搜索树的查找效率是无法满足我们的要求&#xff0c;当二叉树为左或者右斜树查找的效率就很低下了&#xff0c;那么这本篇当中我们就要来学习对二叉搜索树进行优化的二叉树——AVL树。在此会先来了解AVL树…

微信小程序:实现单选,多选,通过变量控制单选/多选

一、实现单选功能 微信小程序提供了 radio 组件来实现单选功能。radio 组件需要配合 radio-group 使用。 1. WXML 代码 <radio-group bindchange"onRadioChange"><label wx:for"{{items}}" wx:key"id"><radio value"{{it…

《Effective Java》学习笔记——第1部分 创建对象和销毁对象的最佳实践

文章目录 第1部分 创建和销毁对象一、前言二、创建和销毁对象最佳实践内容1. 优先使用工厂方法而非直接使用构造器2. 避免创建不必要的对象3. 避免使用过多的构造器4. 避免使用原始类型&#xff08;Raw Types&#xff09;5. 避免创建对象的过度依赖6. 清理资源和关闭对象&#…

解决conda create速度过慢的问题

问题 构建了docker容器 想在容器中创建conda环境&#xff0c;但是conda create的时候速度一直很慢 解决办法 宿主机安装的是anaconda 能正常conda create,容器里安装的是miniforge conda create的时候速度一直很慢&#xff0c;因为容器和宿主机共享网络了&#xff0c;宿主机…