Linux之进程控制

news2024/11/17 21:24:06

一.进程创建

1.1 fork函数

我们创建进程的方式有./xxx和fork()两种

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核做:

1.分配新的内存块和内核数据结构给子进程
2.将父进程部分数据结构内容拷贝至子进程
3.添加子进程到系统进程列表当中
4.fork返回,开始调度器调度

fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。

1.2.fork返回值

1.子进程返回0。
2.父进程返回的是子进程的pid。

1.3.写时拷贝

当子进程被创建出来,子进程中的数据和代码都是和父进程共用一份,也就是说父子进程的页表中的代码和数据都是只读权限,当子进程想要修改数据的时候,就会发生缺页中断,也就是子进程修改数据的操作被暂停,然后操作系统开辟新的空间,将数据的数值拷贝进该空间中,修改父子进程对于页表中该数据的权限为可读可写,将页表的数据地址指向该新开辟的空间,操作系统完成这些操作之后,子进程修改数据的操作继续进行,通过页表完成数据修改。

1.4 fork常规用法

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

1.5 fork调用失败的原因

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

二.进程终止

2.1 进程退出场景

1. 代码运行完毕,结果正确
2. 代码运行完毕,结果不正确
3. 代码异常终止

main函数的return值是进程的退出码

 通过echo $?可以输出最近一次进程退出时的退出码,可以看到main函数的return值是进程的退出码

代码执行完毕,结果正确,只有一种可能,所以返回0,但是代码运行完毕,结果不正确,却有很多种可能性,在Linux中,我们查看到有134种错误的返回

 当进程异常终止的时候就相当于程序运行崩溃,它的退出码是没有意义的,程序正常运行结束之后的退出码才有意义

我们除以0值为例子

它的程序运行会崩溃,退出码没有意义 

2.2 进程常用退出方法 

正常终止(可以通过echo $? 查看进程退出码):

1. 从main返回:

main函数return,代表进程退出!!其他的非main函数呢??代表的是函数返回
2. 调用exit:

exit在任意地方调用,都代表终止进程,参数是退出码!它跟main返回一样,都会刷新输出缓冲区 

在4s之后,才刷新输出缓冲区,打印输出hello world

3. _exit:

终止进程,但强制终止进程,没有进行进程的后续收尾工作,比如刷新缓冲区(用户级缓冲区)!!

 进程退出,在操作系统层面做了什么呢?

在系统层面,少了一个进程:释放进程控制块,释放进程地址空间,释放页表和各种映射关系,还有代码和数据也都会被释放掉!

 三.进程等待

子进程被创建出来,是为了完成父进程的某些任务的,但是子进程和父进程何时结束,这是未知的,所以父进程fork之后,需要通过wait()/waitpid()等待子进程退出。

为什么要让父进程等待呢?

1.通过获取子进程退出的信息,能够得知子进程执行结果

2.可以保证:时序问题,子进程先退出,父进程后退出

3.进程退出的时候会先进入僵尸状态,会造成内存泄漏问题,需要通过父进程wait,释放该子进程占用的资源

两个等待函数:wait()和waitpid()

wait(int* status):

查看Linux进程的相关信息的指令:

while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep; sleep 1; echo "#####################################";done; 

子进程被创建: 

 5秒后,子进程终止退出,进入僵尸状态:

10秒后,父进程回收子进程,父进程继续执行程序:

再过5秒父进程结束 

上面这段代码是为了实现在子进程退出之后,成为僵尸进程,然后父进程继续执行程序,回收子进程的信息

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

 执行结果跟wait()一样

 在上面的代码中waitpid的第一个参数是要等待的那一个进程的pid,如果值为-1,表示等待任意一个子进程

第二个参数就是

我们通过上面的代码知道,当子进程退出码是0的时候,父进程从waitpid获取到的状态码是0,而子进程退出码是10的时候,父进程从waitpid获取到的状态码是2556;也就是说,父进程拿到什么status结果,一定和子进程如何退出是强相关的,而我们刚刚知道进程退出时,有三种结果,那么不难得出最终父进程一定会通过status得到子进程执行的结果,

如果程序代码运行完毕,结果与否,进程就会返回退出码给父进程(通过return / exit),如果代码异常终止,本质上时这个进程因为异常问题,导致自己收到某种信号

这个status是int类型,有32位,只使用低16位

 父进程首先识别status的低7位终止信号是否为0,如果为0,表示正常退出,否则异常退出,若为0,继续识别高8位是否为0,为0结果正确

验证:

tips:退出码=(status>>8)&0xFF,退出信号=status&0x7F

代码运行完成 ,且结果正确的情况:

代码运行完成 ,结果不正确的情况:

代码运行过程中,收到信号异常退出的情况:

 判断status的另外一种操作:

 理解一下waitpid():

waitpid()处在用户层和操作系统之间,是操作系统提供给用户层的接口,在操作系统中,僵尸子进程的PCB中保存着进程退出时的退出数据,里面包括退出码和退出信号,父进程回收该子进程的时候,就会将status与退出码和退出信号进行位与运算,获取到带有子进程退出时的退出码和退出信号信息的退出状态status,然后将status返回给用户

在WaitPid()的第三个参数中,0表示阻塞等待,WNOHANG表示非阻塞等待

阻塞等待:父进程等待子进程退出,子进程不退出,父进程就会一直等待,直到子进程退出。

阻塞了是不是意味着父进程不被调度执行了呢?

不会,父进程会被链入到等待队列中,从R状态变为S状态,不会被CPU调度执行,直到子进程退出,父进程从等待队列中取出,然后S状态变为R状态,被插入运行队列中,被CPU继续调度执行

阻寒的本质: 其实是进程的PCB被放入了等待队列,并将进程的状态改为S状态,返回的本质: 进程的PCB从等待队列拿到R队列,从而被CPU调度

非阻塞等待:父进程轮询检测子进程是否退出,如果子进程退出,查看waitpid检测成功与否,如果子进程没有退出,就继续轮询检测子进程是否退出

测试代码:

执行结果:

 子进程在执行代码得时候,父进程轮询等待,期间每轮询一次,父进程在做自己的事情。

四.程序替换 

4.1为什么要进行程序替换??

如果我们想让一个子进程执行一个全新的程序的时候,我们就需要程序替换

4.2 什么是程序替换?原理是什么?

进程不变,仅仅替换当前进程得代码和数据得技术叫做进程得程序替换 

程序本质上就是存放在磁盘中的文件,这些文件=程序代码+程序数据,操作系统将该文件中的代码和数据替换到已存在的进程中的代码和数据,这一过程我们称之为程序替换!!他并没有创建新的进程,只是老进程的壳子(进程地址空间,PCB,页表)不变,把新程序的代码和数据替换进物理内存就可以了

 在上面的代码中,程序替换之后的的代码是不会执行的

 程序替换的本质就是把程序的进程代码+数据,加载进特定进程的上下文中!!C/C++程序要运行,必须得先加载到内存中!那么怎么加载呢?通过exce*程序替换函数。

 在上面的代码中,由于进程具有独立性,以及写时拷贝的存在,虽然父子代码是共享的,但是进程程序替换会更改代码区的代码,然后发生写时拷贝

执行结果:

进程的程序替换的使用?

1.现象

2.fork()

3.exec*返回值

4.3 各个程序替换函数的基本使用

替换函数:

其实有六种以exec开头的函数,统称exec函数:

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[]);

函数解释:

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1
所以exec函数只有出错的返回值而没有成功的返回值。

命令理解:

l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量

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

在execl中,第一个参数是你的要执行的目标程序的全路径(所在路径/文件名)

第二个参数往后就是要执行的目标程序,在命令行上怎么写的这里的参数就怎么一个一个的传递进去,最后以NULL结尾

等价于

2.int execlp(const char *file, const char *arg, ...);

在execlp中,第一个参数是你的要执行的目标程序的文件名(不带路径,会自动去环境变量PATH找该文件)

第二个参数往后就是要执行的目标程序,在命令行上怎么写的这里的参数就怎么一个一个的传递进去,最后以NULL结尾

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

在execv中,第一个参数是你的要执行的目标程序的全路径(所在路径/文件名)

第二个参数是一个命令行参数数组,把命令行上写的那些参数一个一个写进这个数组里面

4.int execvp(const char *file, char *const argv[]);

在execv中,第一个参数是你的要执行的目标程序的文件名(不带路径,会自动去环境变量PATH找该文件)

第二个参数是一个命令行参数数组,把命令行上写的那些参数一个一个写进这个数组里面

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

 在execlp中,第一个参数是你的要执行的目标程序的文件名(不带路径,会自动去环境变量PATH找该文件)

第二个参数往后就是要执行的目标程序,在命令行上怎么写的这里的参数就怎么一个一个的传递进去,最后以NULL结尾

第三个参数是给该程序赋予环境变量

让myproc的子进程执行程序myexe,在myproc的子进程中使用execle函数将指定的环境变量给程序myexe,然后程序myexe打印出来

6.int execve(const char *path, char *const argv[], char *const envp[]);

 在execlp中,第一个参数是你的要执行的目标程序的文件名(不带路径,会自动去环境变量PATH找该文件)

第二个参数是一个命令行参数数组,把命令行上写的那些参数一个一个写进这个数组里面

第三个参数是给该程序赋予环境变量

 有了这些函数,我们就可以是在C/C++中调用其他编程语言的程序了!!

所有的接口看起来是没有太大差别的,只有一个参数的不同!!

为什么会有这么多接口,是为了满足不同的应用场景的

六个函数接口的关系:

execve是系统调用函数其他函数都是在该函数的基础上进行的封装

4.4 利用程序替换实现shell命令解释器

在Linux中shell命令行解释器本质上就是一个进程,它的名字叫bash,我们在命令行中执行的程序的父进程就是它,它通过解析我们在命令行上输入的命令字符串,然后去环境变量PATH中找到相应的命令程序,创建子进程来执行该命令程序,最后将结果打印出来。

上面说的是执行第三方命令的时候就会这样操作,但是执行内建命令的时候,就不需要创建子进程去执行命令,而是直接shell进程调用命令函数来执行内建命令

tips:什么是内建命令,什么是第三方命令?

第三方命令:

外部命令,有时候也被称为文件系统命令,是存在于bash shell之外的程序。外部命令程序通常位于/bin、/usr/bin、/sbin或/usr/sbin中。外部命令需要使用子进程来执行。受到环境变量的影响

内建命令:

它是shell的一部分,执行内建命令等于调用bash shell程序的一个程序,它不会受到环境变量的影响,内建命令比外部命令,效率更高,执行更快,执行内建命令相当于调用当前 Shell 进程的一个函数。比如cd、exit 这些是内部命令,本质是函数调用,可以直接使用,内建命令并不是某个外部程序,而是bash shell该程序的组成部分,只要在 bash shell 中就可以运行这个命令。

代码:

   
    1 #include<stdlib.h>
    2 #include<iostream>
    3 #include<unistd.h>
    4 #include<sys/wait.h>
    5 #include<sys/types.h>
    6 #include<string.h>
    7 #define NUM 128
    8 #define CMD_NUM 64
    9 
   10 int main()
   11 {
   12   char command[NUM];//命令字符串
   13   while(1)
   14   {
   15     char* argv[CMD_NUM] = {NULL};
   16     //1.打印提示符
   17     command[0] = 0;//清空字符串;
   18     std::cout << "[who@myhostname mysir]# ";
   19     fflush(stdout);
   20     //sleep(10);
   21     
   22     //2.获取命令字符串
   23     fgets(command, NUM, stdin);//从命令行获取命令字符串
   24     command[strlen(command) - 1] = 0;
   25     //std::cout << "command is: " << command << std::endl;
   26 
   27     //3.解析命令字符串,char* argv[];
   28     int cnt = 0;
   29     argv[0] = strtok(command, " ");
   30     cnt++;
   31     while(argv[cnt] = strtok(NULL, " "))
   32     {
   33       cnt++;
   34     }
   35 
   36     //4.执行内建命令,相当于调用一个函数                                                                                                                     
   37    if(strcmp(argv[0], "cd"))
   38    {
   39       if(argv[1] != NULL) chdir(argv[1]);
   40       continue;
   41    }
   42 
   43     //执行第三方命令
   44     if(fork() == 0)//创建子进程执行第三方命令
   45     {
   46       execvp(argv[0], argv);
   47      //td::cout << "执行命令失败" << std::endl;
   48       exit(1);
   49     }
   50     waitpid(-1, NULL, 0);
   51   }
   52   return 0;
   53 }

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

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

相关文章

Echarts实现多柱状图重叠重叠效果

有两种重叠效果: 1. 多个柱子重叠为一个 2. 多个柱子重叠为两组 第一种,图例: 这个灰色不是阴影哦, 是柱子. 1. 使用详解 (1) series.Z 折线图组件的所有图形的 z 值。控制图形的前后顺序。 z 值小的图形会被 z 值大的图形覆盖。z 相比 zlevel 优先级更低&#xff0c;而且不会…

GEE学习笔记 七十三:【GEE之Python版教程七】静态展示影像和动态展示影像

我们使用GEE在线编辑可以直接通过在线的网页可以加载展示我们计算的结果&#xff0c;而python版的GEE要展示我们的计算结果可能就比较麻烦。如果有同学看过GEE的python版API中可以找到一个类ee.mapclient&#xff0c;这个类的介绍是它是GEE官方通过Tk写的一个加载展示地图的类。…

【蓝桥日记⑤】2014第五届省赛(软件类)JavaA组❆答案解析

【蓝桥日记⑤】2014第五届省赛&#xff08;软件类&#xff09;JavaA组☃答案解析 文章目录【蓝桥日记⑤】2014第五届省赛&#xff08;软件类&#xff09;JavaA组☃答案解析1、猜年龄2、李白打酒3、神奇算式4、写日志5、锦标赛6、六角填数7、绳圈8、兰顿蚂蚁9、斐波那契10、波动…

Linux 操作系统原理 — NUMA 体系结构

目录 文章目录 目录NUMA 体系结构NUMA 的基本概念查看 Host 的 NUMA TopologyBash 脚本DPDK 脚步NUMA 体系结构 NUMA(Non-Uniform Memory Access,非一致性存储器访问)的设计理念是将 CPU 和 Main Memory 进行分区自治(Local NUMA node),又可以跨区合作(Remote NUMA nod…

操作系统 三(存储管理)

一、 存储系统的“金字塔”层次结构设计原理&#xff1a;cpu自身运算速度很快。内存、外存的访问速度受到限制各层次存储器的特点&#xff1a;1&#xff09;主存储器&#xff08;主存/内存/可执行存储器&#xff09;保存进程运行时的程序和数据&#xff0c;内存的访问速度远低于…

【信管12.2】知识管理与知识产权

知识管理与知识产权想必你对知识的概念多少都会有一些自己的理解&#xff0c;毕竟我们经过了那么多年的教育&#xff0c;学来学去可不都学习的是“知识”嘛。在今天的学习中&#xff0c;内容还是会比较多&#xff0c;因为除了知识管理相关的内容之外&#xff0c;还有知识产权相…

Matlab 最小二乘法拟合平面(SVD)

文章目录 一、简介1.1最小二乘法拟合平面1.2 SVD角度二、实现代码三、实现效果参考资料一、简介 1.1最小二乘法拟合平面 之前我们使用过最为经典的方式对平面进行了最小二乘拟合(点云最小二乘法拟合平面),其推导过程如下所示: 仔细观察一下可以发现

IP协议

网络层的一个重要作用就是把世界上的地址能够以一定的规范定义出来。地址管理路由选择网络层的代表:IP协议4位版本指的是&#xff1a;此处的取值只有两个ipv4,ipv64位首部长度&#xff1a;描述了ip报头有多长&#xff08;ip报头是变长的&#xff09;报头中有一个选项部分&#…

JUnit5文档整理

1、Overview 1.1、Junit5是什么 与以前的JUnit版本不同&#xff0c;JUnit 5是由三个不同子项目的几个不同的模块组成。 JUnit 5 JUnit Platform&#xff08;基础平台&#xff09; JUnit Jupiter&#xff08;核心程序&#xff09; JUnit Vintage&#xff08;老版本的支持&a…

JVM那些事——垃圾回收和内存分配

内存分配 默认情况下新生代和老年区的内存比例是1:2&#xff0c;新生代中Eden区和Survivor区的比例是8:1。 对象优先分配在Eden区。大对象直接进入老年区。通过-XX:PertenureizeThreshold参数设置临界值。长期存活的对象进入老年区。对象每熬过一次Minor GC&#xff0c;年龄1&…

【面试题】Map和Set

1. Map和Object的区别 形式不同 // Object var obj {key1: hello,key2: 100,key3: {x: 100} } // Map var m new Map([[key1, hello],[key2, 100],[key3, {x: 100}] ])API不同 // Map的API m.set(name, 小明) // 新增 m.delete(key2) // 删除 m.has(key3) // …

操作系统闲谈06——进程管理

操作系统闲谈06——进程管理 一、进程调度 01 时间片轮转 给每一个进程分配一个时间片&#xff0c;然后时间片用完了&#xff0c;把cpu分配给另一个进程 时间片通常设置为 20ms ~ 50ms 02 先来先服务 就是维护了一个就绪队列&#xff0c;每次选择最先进入队列的进程&#…

Prometheus PromQL入门

一、Prometheus简介和架构 Prometheus 是由 SoundCloud 开源监控告警解决方案。架构图如下&#xff1a; 如上图&#xff0c;Prometheus主要由以下部分组成&#xff1a; Prometheus Server&#xff1a;用于抓取和存储时间序列化数据Exporters&#xff1a;主动拉取数据的插件P…

Chrome开发者工具:利用网络面板做性能分析

Chrome 开发者工具&#xff08;简称 DevTools&#xff09;是一组网页制作和调试的工具&#xff0c;内嵌于 Google Chrome 浏览器中。 Chrome 开发者工具有很多重要的面板&#xff0c;比如与性能相关的有网络面板、Performance 面板、内存面板等&#xff0c;与调试页面相关的有…

字符串匹配 - 模式预处理:BM 算法 (Boyer-Moore)

各种文本编辑器的"查找"功能&#xff08;CtrlF&#xff09;&#xff0c;大多采用Boyer-Moore算法&#xff0c;效率非常高。算法简介在 1977 年&#xff0c;Robert S. Boyer (Stanford Research Institute) 和 J Strother Moore (Xerox Palo Alto Research Center) 共…

SpringCloud(二)负载均衡服务调用Ribbon、服务接口调用OpenFeign案例详解

五、负载均衡服务调用Ribbon 技术版本Spring Cloud版本Hoxton.SR1Spring Boot版本2.2.2RELEASECloud Alibaba版本2.1.0.RELEASE Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。 简单的说&#xff0c;Ribbon是Netflix发布的开源项目&#xff0c;主…

DOS经典软件,落下帷幕,新型国产平台,蓬勃发展

提起DOS时代&#xff0c;总让人难以忘怀&#xff0c;陷入深深回忆中&#xff0c;风靡一时的许多软件&#xff0c;如今早已不在&#xff0c;这几款被称为DOS必装的软件&#xff0c;更是让人惋惜。 你还记得这图吗&#xff1f;堪称DOS系统最经典的软盘复制与映像生成软件&#xf…

八十九——一三三

八十九、JavaScript——数组的简介 一、数组 数组(Array) - 数组也是一中复合数据类型&#xff0c;在数组可以存储多个不同类型的数据 - 数组中存储的是有序的数据&#xff0c;数组中的每个数据有一个唯一的索引 可以通过索引来操作获取数据 - 数据中存储的数据叫元素 - 索引&…

从 MVC 架构到三层(3-Tier)架构

一、MVC 存在的痛点问题 对于业务逻辑不甚复杂的场景&#xff0c;MVC 尚能胜任。但随着前端 MVVM&#xff08;Model-View-View-Model&#xff09;开发模式的兴起&#xff0c;尤其是前端框架如 Vue、React 的普及&#xff0c;服务端的 MVC 设计模式使用场景变得越来越少&#x…