Linux-进程控制

🌎进程控制【上】

文章目录:

进程控制

    为什么要有地址空间和页表

    程序的内存
      程序申请内存使用问题

    写时拷贝与缺页中断

      父子进程代码共享
      为什么需要写时拷贝
      页表的权限位
      缺页中断

    退出码和错误码

      进程的退出码
      错误码

    总结


前言:

  进程控制涉及到操作系统如何管理和控制运行在计算机系统中的各个进程。那么话不多说,开启我们今天的话题!

在这里插入图片描述


🚀为什么要有地址空间和页表

  上次我们说,进程中存在着一张张由操作系统画的 “大饼”,也就是虚拟地址空间,但是很多人并不明白为什么需要虚拟地址空间,今天我们继续听故事:

  阿熊在小的时候,每逢过年都会收到压岁钱,总的加起来对阿熊来说是一笔巨大的财富,但是这个时候,阿熊的老妈总是以帮阿熊保管为理由把压岁钱给收走了,于是阿熊一哭二闹三上吊,终于老妈做出了让步,说:“钱还是在我这里放着,但是你可以去买东西,看到什么想要的就跟我说,我给你买。” 这个时候阿熊也妥协了,那就这么办吧。
  接下来的几天,阿熊跟小伙伴出去玩,路过小卖铺,看到一款新上的零食,小伙伴拿着自己的压岁钱纷纷买了这款新零食,给阿熊分了一点点,“太好吃了,我也要买!”于是,阿熊回到家里跟老妈说:“妈,我想买个零食,要5毛钱。”老妈一想,小孩子吃一点零食很正常,也不贵,“好,钱给你,买去吧!” 于是阿熊拿着钱蹦蹦跳跳的去了小卖铺,这些天,阿熊买各种小零食、小玩具,老妈都默许了。
在这里插入图片描述
  可是没过多久,阿熊在货架上看到了变形金刚玩具,瞬间阿熊就定在原地不走了,阿熊想了很久,于是回家跟老妈说:“老妈,我想买个玩具,要100块钱。” 老妈瞬间火气就上来了“你买了那么多玩具都还没坏,又想要新的了?我看你想找抽!” 于是阿熊不仅没能买到玩具,还吃了一顿皮带炒肉丝…

  其实,由此例子我们就能明白为什么要存在虚拟地址空间,把阿熊比作用户,把老妈比作页表,把商店比作内核数据。

  处于安全考虑,内核是不能随便被用户访问的,为了防止用户直接对内核数据操作,于是在用户和内核之间添加了页表和虚拟地址,当用户想用虚拟地址通过页表访问物理地址时,操作系统会做检测是否为非法访问,如果是则中断访问。

在这里插入图片描述

  如果你的错误情节很严重,操作系统甚至会杀死这个进程。这也就像当我们写C语言代码不小心犯了空指针问题时,进程会直接挂掉但是并不影操作系统的原因。


🚀程序的内存

✈️程序申请内存使用问题

  我们再使用C/C++开辟内存时,使用malloc或new,那么这些开辟的空间是立马就使用吗?

  实际上,我们可以先开辟空间,但是我们并不着急使用,这种情况绝对是存在的。而操作系统给程序申请物理内存的原理如下图:

在这里插入图片描述

  这个过程就像支票,你有支票你不一定会立马去银行换,程序空间开辟也是这个道理。

  所以,由此我们可以知道:程序申请空间实际上是从虚拟地址空间申请。这样做有以下几点好处:

  1、充分保证内存的使用率,不会空转。
  2、提升new或者malloc的速度。

  所以,我们 程序申请的空间都是虚拟地址空间,程序未访问时是被页表拦截的,而当程序真正访问这个空间时,操作系统才会在物理内存中开辟空间,重新建立虚拟地址与物理地址之间的映射关系。 这个过程我们称之为——缺页中断(Page Fault)

在这里插入图片描述


🚀写时拷贝与缺页中断

✈️父子进程代码共享

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

在这里插入图片描述

  通过上面的图,我们就能清晰的了解:父子进程代码共享本质上是父子进程的页表映射到同一片内存区域!


✈️为什么需要写时拷贝

  在刚开始说进程这个话题的时候,我们说过操作系统向上是为了给用户提供良好的运行环境,既然如此,父进程创建子进程的时候父进程的数据并没有直接给子进程的原因也就得到了解释:

  子进程被创建时操作系统并不知道你用不用父进程的数据,就算确定使用也不确定是不是现在就用,为了尽可能不浪费资源,所以 操作系统设置只有当进程需要写入时就会发生写时拷贝。

在这里插入图片描述

  可能有些人还有些疑惑:为什么写时拷贝,开辟空间就算了,为什么还要将原始数据再拷贝一份呢?

  我们对数据的操作无外乎就是增、删、查、改,比如原来进程中代码区有一个整形变量i = 100,这个时候子进程要对i做自增运算。
  简单来说就是对变量i进行写入操作,既然有写入操作,就会触发写时拷贝,子进程单独开辟内存,将父进程原始数据拷贝下来,如果没有拷贝,那么CPU根本就找不到需要修改的值!
  而不需要进行写入操作那么就不会触发写时拷贝,这种方式有时会大大提高程序运行速度。


✈️页表的权限位

  页表不仅仅只有虚拟地址与物理地址之间的映射,页表的每一个映射之间还存在权限位,也就是 可读可写可执行(r ,w, x)

在这里插入图片描述
  页表中存在着权限位这一栏,为了能更好理解权限位在这里的作用,我们来看下面这段代码:

#include<stdio.h>int main()
{char *str = "this is a good day!\n";*str = 'T';//这里我想把str字符串第一个字母大写return 0;
}

  这是一段很简单的C语言代码,也是很经典的错误,我们学过C语言都知道,字符串信息是放在常量区的,而常量区具有常兴不可修改,代码放在shell下运行:

在这里插入图片描述

  不出意外,发生了段错误,那么 程序是如何知道这个字符串是常量呢?或者说 为什么常量区具有常性

  我们前面学了,程序都所占用的空间都是虚拟地址空间,而对str进行修改,本质上就是对字符串进行写入,而 写入操作会触发页表通过虚拟地址到物理地址之间的转换,但是在映射时,页表发现映射的权限位为 只读(r),于是就终止了映射

注意:

  有些小伙伴在这里可能会晕,把const和这里搞混,其实,const仅仅是在语法层面上提前设定好的,在编译阶段起作用,因为前面你在某个程序段上加了const,其实是为了后文不会对该程序进行修改操作,如果执行了修改操作,在编译阶段就编不过去,所以const是为了提前告诉你你写的代码有问题,而页表是在程序编译通过了,运行期间操作系统返回的错误。
在这里插入图片描述
  其实加上const 属于防御性编程,在运行之前就将错误爆出来,减少程序运行时崩溃的风险。


✈️缺页中断

  上面我们提到了缺页中断,可是并没提到什么应用场景,其实缺页中断与写时拷贝也脱不了干系。

  写时拷贝我们已经很清楚了,当父子进程有一方发生写入操作时,就会触发写时拷贝,可是关键是,操作系统是用什么机制触发写时拷贝的?

在这里插入图片描述
  当子进程已经复制父进程的页表,此时操作系统不管你原来数据的权限是什么,绝大部分数据权限都设置为 只读(r)

  当触发了写时拷贝,子进程读取到 “写入”的指令,页表就会在虚拟地址和物理地址之间做映射,而页表在检查时发现需要修改数据的权限位为只读,则发生 缺页中断,此时进程就会强制 暂停,操作系统就会在物理内存开辟空间,并且将新开辟的物理内存映射到虚拟地址,进程再继续运行,并且修改当前执行数据的权限位为:可读可写(rw)

  因为操作系统是软硬件资源的管理者,而开辟物理空间是需要操作系统参与的,程序为了让操作系统发现自己,使用了缺页中断,进程暂停 操作系统一定会来查看原因,发现正在执行写时拷贝,于是操作系统开辟物理地址重新建立映射关系,并且将修改数据权限更改。

  所以说,写时拷贝过程中,缺页中断是故意触发的,让系统出错,目的就是为了 让操作系统发现并执行接下来的步骤


🚀退出码和错误码

✈️进程的退出码

  我们不论是在写C/C++,在main函数里都会有 return 0; 这个语句,这个语句的作用是什么你是否是一知半解?

  在程序中,一般大家写函数时如何看函数的执行结果?可能有人说,把结果打印出来不就完事了吗?那么我限制不让你打印呢?你可能会说,那就 通过返回值

  没错,程序就是通过返回值来确定程序是否运行成功,bash根据程序的返回值来判断程序是否是正常运行。而一个程序的返回值我们称之为——退出码

  一般进程的退出码为0表示成功,非0表示失败

  阿熊小时候的某一天,数学考了100分,回家父亲问阿熊:“数学考了多少分啊?”,阿熊如实回答,但是父亲总不能说为什么你考了一百分吧?第二天阿熊英语成绩也出来了,回到家父亲又问:“英语考多少分啊?”阿熊说:“59分。”这是父亲问:“为什么考这么少,什么原因,是因为没发挥好还是因为考试状态不佳或者其他原因?”

  计算机世界只有0和其他数,0是特殊的,所以以0作为进程执行成功的标志,就像阿熊考了100分。而其他的数就是失败,也就是进程出问题,就像阿熊英语考试出现问题,而出问题却可能是不同的原因。所以用不同的数字来表示不同的错误原因。

  我们看下面这段代码:

#include<stdio.h>int main()
{printf("Hello Axiong!\n");return 8;//注意返回值为8
}

在这里插入图片描述
  我们执行代码,从打印结果来看是正常运行的程序,可是我们前面说了,一个程序是否能运行我们要看退出码,而查看退出码的命令是:

echo $?//查看最近一次进程的退出码

在这里插入图片描述
  我们开始的结果显示是8,后面却显示未0了,其实这是因为这个命令是显示最近一次进程的退出码,除了第一次最近一次执行的进程就是这个命令的本身,所以,退出码为0。

  我们在C语言中曾经学过这个函数:

在这里插入图片描述
  为了清晰看到函数不同退出码表示什么意思,我们来看下面的程序:
%d

#include<stdio.h>
#include<string.h>int main()
{for(int i = 0; i < 200; ++i){printf("%d :%s\n", i, strerror(i));}return 0;
}

在这里插入图片描述
  打印出来之后,我们发现错误码一共有133个不同的错误信息。当然,这是基于Linux平台下显示的退出码,在其他平台可能会是不同的结果。

  我们来证实一下上面的错误码:

在这里插入图片描述

  这个是系统的退出码,其实我们完全可以自己写一套适用于自己的退出码,比如下面代码:

#include<stdio.h>
#include<string.h>enum{success=0,open_err,alloc_err
};const char* errorToDesc(int code)
{switch(code){case success:return "success";case open_err:return "file open error!";case alloc_err:return "alloc error!";default:return "unknow error";}
}int main()
{int ind = alloc_err;printf("%s\n", errorToDesc(ind));return ind;
}

在这里插入图片描述
  可以得出结论:main函数return返回,表示进程退出,而return跟的数字为退出码,可以设置相应退出码的信息。而其他函数退出,仅表示函数调用完毕!


✈️错误码

  除了进程退出,函数退出,我们如何知晓函数的退出情况?和main函数一样,通过返回值,调用函数,我们称为——错误码,我们通常想看到两种结果:

  1、函数的执行结果
  2、函数的执行情况

  我们在学C语言的时候,可能会见过这个函数接口:errno,用来返回错误信息,借此可以看到函数执行情况。

在这里插入图片描述
  想要看到函数执行结果很经典的就是返回类型为 FILE* 结构体指针的函数,失败时返回NULL,成功我们并不需要太过关注,那么我们以fopen函数为例:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>enum{success=0,open_err,alloc_err
};const char* errorToDesc(int code)
{switch(code){case success:return "success";case open_err:return "file open error";case alloc_err:return "malloc error";default:return "unknow error";}
}int Print()
{printf("hello Linux\n");printf("hello Linux\n");printf("hello Linux\n");return 0;
}int main()
{FILE *fp = fopen("./log.txt", "r");//尝试打开当前目录的文件,如果没有则返回NULLprintf("%d:%s\n", errno, strerror(errno));//获取错误码以及错误信return 0;
}

在这里插入图片描述
根据错误码我们通过调用函数也可获取相应错误信息。

  我们说了这么多,其实就是想要说进程退出时可能会发生错误,简单总结为一下三条:

  1、进程代码执行完,结果正确。
  2、进程代码执行完,结果错误。
  3、进程代码未执行完,进程异常。

  进程退出的情况,就这三种情况!


📒✏️总结

  •  我们曾使用的 C/C++ 有关内存操作全部是在 虚拟地址空间 操作的,目的是 为了内存隔离和内存管理,避免内存冲突
  •  虚拟地址与物理地址之间由 页表 建立映射关系,程序开辟空间时会发生写时拷贝,而 写时拷贝是通过缺页中断技术得以执行
  •  进程存在 退出码,根据退出码 判断进程是否执行成功,否则可以判断出了哪些问题。
  • 错误码 和退出码类似,作用对象是函数,判断函数执行失败情况,退出码和错误码可自行设置。

在这里插入图片描述
创作不易,还望给作者一个小小的赞呀~~

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

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

相关文章

【ENSP】交换机和交换机之间实现静态路由

1.概念 三层交换机只能在Vlanif逻辑口配置iP地址 2.实现方法 交换机允许对应vlan通行&#xff0c;配置vlanif的ip地址&#xff0c;做静态路由 3.静态路由配置方法 ip route-static 目的网段 子网掩码 下一跳设备 LSW1三层交换机配置 u t m sys vlan batch 10 20 …

HarmonyOS 应用开发之UIAbility组件生命周期

概述 当用户打开、切换和返回到对应应用时&#xff0c;应用中的UIAbility实例会在其生命周期的不同状态之间转换。UIAbility类提供了一系列回调&#xff0c;通过这些回调可以知道当前UIAbility实例的某个状态发生改变&#xff0c;会经过UIAbility实例的创建和销毁&#xff0c;…

LeetCode_1.两数之和

一、题目描述 二、方法 1.方法1&#xff08;暴力枚举法&#xff09; 利用两个for循环&#xff0c;对数组进行逐一的遍历&#xff0c;直到找到两个数的和为目标值时返回这两个数的下标。以下为c实现的完整代码。 # include<iostream> using namespace std; #include<…

【Linux】nmcli命令详解(文末送书)

目录 一、概述 二、常用参数使用 2.1 nmcli networking 1.显示NM是否接管网络 2.查看网络连接状态 3.开/关网络连接 2.2 general ​编辑 1.显示系统网络状态 2.显示主机名 3.更改主机名 2.3 nmcli connection ​编辑1.显示所有网络连接 2.显示某个网卡的详细信息…

Linux文件IO(2):使用标准IO进行文件的打开、关闭、读写、流定位等相关操作

目录 前言 文件的打开和关闭的概念 文件的打开 文件的打开函数 文件打开的模式 文件的关闭 文件的关闭函数 注意事项 字符的输入&#xff08;读单个字符&#xff09; 字符输入的函数 注意事项 字符的输出&#xff08;写单个字符&#xff09; 字符输出的函数 注意…

探索海外应用加速的作用与优势

随着互联网的快速发展&#xff0c;海外应用加速作为一种提高网络连接速度和性能的技术手段越来越受到关注。那么&#xff0c;海外应用加速究竟有什么作用呢&#xff1f;以下是这种加速技术的主要作用&#xff1a; 降低延迟&#xff1a; 海外应用加速在降低数据传输延迟方面发挥…

项目模块—实现抑郁测评(小程序)

script <script setup> import { ref } from "vue";//控制轮播图页码 let current ref(0);//答题逻辑 const add (value) > {if (current.value < 9) {current.value current.value 1;} else {uni.switchTab({url: "/pages/my/my",});} }…

双端队列deque和vector以及list的优缺点比较

参考:https://blog.csdn.net/TWRenHao/article/details/123483085 一、vector vector具体用法详情点这里 优点&#xff1a; 支持随机访问 CPU高速环缓存命中率很高 缺点&#xff1a; 空间不够&#xff0c;便需要增容。而增容代价很大&#xff0c;还存在一定的空间浪费。 头部…

redis在docker安装并启动流程

1、启动server docker run -d -p 6379:6379 --name redis01 redis:7.2.4以上命令&#xff0c;每次启动新的Redis容器&#xff0c;数据会丢失。 我们需要挂载数据文件&#xff0c;在宿主机上面&#xff0c;这样就可以持久化数据. 2、挂载数据文件&#xff08;可根据需求选择…

Git相关命令(一)

一、简介 Git 是一个开源的分布式版本控制系统。 当然&#xff0c; git 不会傻傻的把你的每一个版本完整的存储下来&#xff0c;他仅仅会存储每次修改的位置和内容&#xff08;可持久化&#xff09;&#xff0c;每一次 commit 可以理解为产生一个版本&#xff0c;接下来的版本…

vivado 生成比特流或器件镜像

在生成比特流或器件镜像之前 &#xff0c; 请复查其设置 &#xff0c; 确保这些设置对于您的设计都正确无误 &#xff0c; 这一点至关重要。 Vivado IDE 中的比特流和器件镜像设置分为 2 种类型 &#xff1a; 1. 比特流或器件镜像文件格式设置。 2. 器件配置设置。 在 V…

数据结构刷题篇 之 【力扣二叉树基础OJ】详细讲解(含每道题链接及递归图解)

有没有一起拼用银行卡的&#xff0c;取钱的时候我用&#xff0c;存钱的时候你用 1、相同的树 难度等级&#xff1a;⭐ 直达链接&#xff1a;相同的树 2、单值二叉树 难度等级&#xff1a;⭐ 直达链接&#xff1a;单值二叉树 3、对称二叉树 难度等级&#xff1a;⭐⭐ 直达…

【Godot4自学手册】第三十一节使用WorldEnvironment为地宫入口粒子系统添加辉光

本节&#xff0c;首先我将使用WorldEnvironment节点为地宫入口的例子系统添加辉光&#xff0c;让游戏看上去效果更加灿烂。其次加入相应提示信息&#xff0c;白天到达地宫附近、未杀死怪物进入地宫&#xff0c;都有提示信息&#xff0c;达到条件后地宫方可进入。先看一下效果&a…

CSS之动画

一&#xff0c;动画的制作 实现盒子绕圈走 二&#xff0c; 动画的常用属性 三&#xff0c;动画简写属性 前面两个属性一定要写&#xff0c;第三个linear是指匀速的意思&#xff08;默认是ease&#xff09;

Machine Learning机器学习之数据可视化

目录 前言 一、 数据预处理与清洗 二、常见可视化技术 三、可视化工具和平台 博主介绍&#xff1a;✌专注于前后端、机器学习、人工智能应用领域开发的优质创作者、秉着互联网精神开源贡献精神&#xff0c;答疑解惑、坚持优质作品共享。本人是掘金/腾讯云/阿里云等平台优质作者…

tls和ssl的区别,ssh和ssl区别

在网络通信和安全领域&#xff0c;TLS&#xff08;Transport Layer Security&#xff09;、SSL&#xff08;Secure Sockets Layer&#xff09;和SSH&#xff08;Secure Shell&#xff09;是常见的加密协议&#xff0c;它们都起着保护数据安全的重要作用。在本文中&#xff0c;我…

PPP、RRE、MGRE综合实验

一、实验拓扑图 二、实验要求 1.R5为ISP&#xff0c;只能进行IP地址配置&#xff0c;其所有地址均配为公有IP地址&#xff1b; 2.R1和R5间使用PPP的PAP认证&#xff0c;R5为主认证方: R2与R5之间使用ppp的CHAP认证&#xff0c; R5为主认证方;R3与R5之间使用HDLC封装; 3.R1、R2、…

MSTP环路避免实验(思科)

华为设备参考&#xff1a;MSTP环路避免实验&#xff08;华为&#xff09; 一&#xff0c;技术简介 MSTP&#xff08;多生成树协议&#xff09;&#xff0c;MSTP解决了STP和RSTP没有考虑vlan的问题&#xff0c;STP和RSTP将所有的vlan共享为一个生成树实例&#xff0c;无法实现…

linux提权笔记

1 linux提权简介 Linux提权&#xff0c;简单来说&#xff0c;就是用户尝试获取高于其当前权限级别的系统访问权限的过程。在Linux系统中&#xff0c;root用户拥有最高的权限&#xff0c;能够执行任何操作&#xff0c;包括修改系统文件、安装软件、管理用户账户等。而普通用户通…

岭师大数据技术原理与应用-序章-软工版

HeZaoCha-CSDN博客 序章—软工版 一、环境介绍1. VMware Workstation Pro2. CentOS3. Java4. Hadoop5. HBase6. MySQL7. Hive 二、系统安装1. 虚拟网络编辑器2. 操作系统安装 三、结尾 先说说哥们写这系列博客的原因&#xff0c;本来学完咱也没想着再管部署这部分问题的说&…