Linux_理解程序地址空间和页表
目录
1、进程地址空间示意图
2、验证进程地址空间的结构
3、验证进程地址空间是虚拟地址
4、页表-虚拟地址与物理地址
5、什么是进程地址空间
6、进程地址空间和页表的存在意义
6.1 原因一(效率性)
6.2 原因二(安全性)
6.3 原因三(解耦)
7、页表的使用
8、页表的权限
9、页表的缺页中断
10、页表的好处
结语
前言:
每一个进程都有属于自己的进程地址空间,进程地址空间又叫虚拟内存、虚拟地址空间,从“虚拟二字”可以判断进程地址空间并不是真实的物理空间,他只是物理空间的一个映射表,具体是通过页表作为媒介来建立他们之间的映射关系,所以我们在程序中定义的一系列变量,这些变量的地址都只是该进程的进程地址空间上的地址数,并不是真实的物理地址数。
注意:本文物理空间和物理内存指的是一个概念。
1、进程地址空间示意图
我们所说的代码段(正文代码,包括字符常量区)、数据段(已初始化数据区)、BSS段(未初始化数据区)、堆区、共享区、栈区实际上都是在进程地址空间中的概念,物理内存上根本不存在上面这些划分,所以可以得出进程地址空间是在对物理内存进行管理。
进程地址空间一般分为两个部分:用户空间、内核空间,用户空间就是上面所说的堆、栈区域,而内核空间拥有比用户空间更高的权限级别,他主要是系统内部进行进程管理、内存管理、设备驱动、文件系统、网络系统等相关工作,对外只会暴露接口给到程序员使用。进程地址空间示意图如下:
2、验证进程地址空间的结构
从上图可以发现,进程地址空间的地址数是从下往上增大的,即在32位平台下,最低处代码段的地址是0x0000 0000,而最高处内核空间的地址是0xffff ffff,因此可以通过代码打印在不同区域所出创建的各种变量的地址,来观察他们的地址数就能验证进程地址空间的结构组织。
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int g_val_1;
int g_val_2 = 100;int main(int argc, char *argv[], char *env[])
{printf("main函数地址: %p\n", main);const char *str = "hello world";printf("代码段 %p\n", str);printf("数据段 %p\n", &g_val_2);printf("BSS段 %p\n", &g_val_1);char *mem = (char*)malloc(100);char *mem1 = (char*)malloc(100);char *mem2 = (char*)malloc(100);printf("堆区: %p\n", mem);printf("堆区: %p\n", mem1);printf("堆区: %p\n", mem2);printf("栈区: %p\n", &str);printf("栈区: %p\n", &mem);static int a = 0;static int a1;int b;int c;printf("数据段: %p\n", &a);printf("BSS段: %p\n", &a1);printf("栈区: %p\n", &b);printf("栈区: %p\n", &c);int i = 0;for(; argv[i]; i++)printf("命令行参数:argv[%d]: %p\n", i, argv[i]);for(i=0; i<10; i++)printf("环境变量:env[%d]: %p\n", i, env[i]);return 0;
}
运行结果:
从上图的测试结果可以发现,地址数的大小确实按照了进程地址空间的排布来打印,但是这里有必要说明一点:BSS段的地址数大部分场景下是比数据段的地址数要高的,但是BSS段的变量地址不一定就比数据段的变量地址要高,具体根据程序实现和操作系统加载机制有关。
3、验证进程地址空间是虚拟地址
若要验证进程地址空间里的地址数是虚拟地址,则需要用fork函数创建子进程来完成,具体思路:定义一个全局变量g_val,目的是让父子进程都能看到,然后在子进程中对g_val进行修改,发现父进程里看到的g_val还是原值,但是父子进程看到g_val的地址却还是一样的。即现象是:同一个地址下看到不同的值。
测试代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int g_val = 100;int main()
{pid_t id = fork();if(id == 0){int cnt = 3;// 子进程while(1){//观察g_val的值和地址printf("i am child, pid : %d, ppid : %d, g_val: \%d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);sleep(1);if(cnt) cnt--;else {g_val=200;printf("子进程change g_val : 100->200\n");cnt--;}}}else{// 父进程while(1){//观察g_val的值和地址printf("i am parent, pid : %d, ppid : %d, g_val: \%d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);sleep(1);}}return 0;
}
运行结果:
通过进程相关概念可以得知,当父子进程修改同一份资源时,会发生写时拷贝,即修改资源的进程会拷贝一份资源到该进程的空间地址中进行修改,这样一来修改的资源不会影响另一个进程所看到的资源,保证了进程的独立性。
但是从测试结果可以发现,虽然发生了写时拷贝,但是他们的地址却是一样的,如果进程地址空间的地址数就是真实物理内存的地址数,那么这里所看到的g_val的地址肯定是不一样的,因为同一个地址只能记录一个值,因此可以推断进程地址空间上的地址数是虚拟的(即我们平常打印出来的地址数都是虚拟的)。他们和物理内存的关系图如下:
4、页表-虚拟地址与物理地址
从上图可以发现虚拟地址和物理地址不一定是一一对应的,即虚拟地址的具体值在物理地址上可能指向的是另一个地址,那么要完成这个转换动作就必须有一个转换器,这个转换器就是页表,页表中记录了虚拟地址和物理地址的对应关系,其实发生写时拷贝的时候,虚拟地址在页表中对应的物理地址就已经是另一个地址数了,只是虚拟地址不关心物理地址的变化,并且程序员也无需关心,因为虚拟地址到物理地址的转换是由操作系统自动完成,操作系统只需要保证程序员访问虚拟地址时可以拿到正确的值即可。
页表结构图:
小结:每一个进程都会有一个进程地址空间的蓝图,即进程的进程地址空间是独立的,子进程会将父进程的PCB和进程地址空间和页表深拷贝一份给自己使用(除了一些子进程自带的特性字段不拷贝,比如子进程自己的pid,其他的内容都会拷贝一份),这也就解释了父子进程代码共享,所以创建一个进程的消耗是很大的,因为需要维护进程本身的结构体PCB还要维护地址空间、页表等。
有了上述的认知,就能理解fork创建子进程的细节了,因为fork返回值是一个写入,所以会发生写时拷贝,则子进程会在物理空间上开辟一块新空间存放子进程的id(此时子进程的页表的物理地址会被更改,但是虚拟地址不变),然后父进程和子进程通过查找自己的虚拟地址映射到不同的物理地址处,所以用于保存fork返回值的变量就会显示两个值。
5、什么是进程地址空间
我们知道物理内存中的地址是由32根地址总线经过不同的排列组合得来的(32位平台下),所以在32位平台下,物理内存的大小是2^32 = 4GB,而进程地址空间是物理内存的映射,即进程地址空间也是有自己的大小,但是他的大小不可能和物理内存一样大,前面虽说进程地址空间的大小是4GB,其实只是一个地址范围而不是真正的大小,记录一个范围只需要两个int类型的变量即可,例子示意图如下:
所以进程地址空间的堆、栈这些部分,其实只是用两个变量来维护的,这样一来只需多个变量就可以描述进程地址空间的结构了,所以进程地址空间本质是一个描述线性内存可视范围的结构体,该结构体内的成员变量的含义就是用来划分不同的区域板块。
比如可以用以下结构体来描述进程地址空间(进程PCB结构体中有一个指向地址空间的指针):
每个进程的进程地址空间的范围都是一样的,所以对于每个进程而言,仿佛都可以申请到3G的物理内存空间,但是实际上并不如此,因为一个进程不可能用掉3G的物理空间,当物理内存快被消耗殆尽了,则系统肯定会发出警告,所以进程地址空间的结构体就像是操作系统给进程画的一个大饼,因为该结构体让进程以为物理空间内有很多的空间,但实际上可能没剩下多少空间了,但是进程只要向系统申请空间则物理空间就会分给该进程,若物理空间不足则申请失败。
6、进程地址空间和页表的存在意义
6.1 原因一(效率性)
因为若没有进程地址空间,则进程直接在物理内存上进行数据的存放,此时如果进程的状态变成挂起状态,为了节省物理内存则该进程原本存放在物理内存的数据可能就要被移到磁盘中,下一次该进程进入内存时要重新摆放数据至物理内存中,并且还要重新修改PCB的内容了,太麻烦了效率又低。
有了进程地址空间后就无需关心进程在物理空间内的数据摆放的位置了,因为数据在物理内存中的存放顺序我们不关心,系统会帮我们建立页表和物理内存的映射关心,我们只要按照页表的虚拟地址进行寻址即可,所以可以把进程地址空间看成是进程和物理内存之间的桥梁、转换器。
以统一视角来看待内存,做到了一致性,让进程对内存的分配和控制更加方便了。
6.2 原因二(安全性)
进程如果直接访问物理内存,会有可能更改其他用户的内容,而如果进程先访问虚拟地址空间和页表,若发生了修改其他用户内容的情况,则虚拟地址空间和页表会直接反馈并拒绝这个动作,达到保护物理内存的效果。
并且对于代码的结构也做了明确的功能划分,比如代码段的数据不可更改,保护了代码。
6.3 原因三(解耦)
将进程管理模块和内存管理模块进行了解耦,具体示意图如下:
7、页表的使用
从上文得知,若要使用进程地址空间,则在PCB结构体中的pmm指针就能找到进程地址空间,但是该如何找到并使用页表呢?
使用页表的示意图如下:
总结而来就是进程PCB中间接的包含了找到页表的方法。
8、页表的权限
页表实际上还有一列用于显示权限,示意图如下:
页表会记录虚拟地址对应的物理地址是否为文字常量区,若为文字常量区而进程还要修改该地址的内容,则页表直接会报错并且终止这个进程,这也是为什么代码段的数据不可被修改,原因就是所有的访问都要通过页表这个媒介,页表会判断虚拟地址然后对权限做出相关改变。
9、页表的缺页中断
当进程被挂起时就表示缺页中断,该进程的代码会被从内存移至磁盘,页表中还有一列是专门记录代码是否还存放在内存中,因为若把进程的所有代码都从磁盘加载至内存中,有些代码还没使用到就会浪费内存的资源,因此进程的调度遵循着“分批加载-惰性加载” ,而页表的缺页中断就是为了让cpu知道目前哪些代码已经被加载进内存中哪些代码还在磁盘上。
页表中用于记录当前是否为缺页中断的标识符示意图如下:
进程的挂起实际上就是页表中断,他的底层是将进程的代码都拿走放到磁盘中,然后页表中的物理内存地址也清空,并且把内容标志位为0,这时候就是进程挂起了。挂起结束时就会根据内再将磁盘中的代码重新拿到内存中,然后把内容标志位从0置为1,表示缺页中断结束,并且页表中的物理内存地址填上加载后新的地址,这个过程虚拟地址是不需要改变的。
10、页表的好处
当把可执行程序加载到内存时,可以不考虑在内存的摆放顺序,因为有页表的存在,我们只需要关心页表中的虚拟地址就能判断出哪些数据只能读哪些地址只能写了,而且必须要用统一的视角看待内存,因为只有用统一的视角看待内存才能让内存的无序摆放对于进程来说是有序的。
结语
以上就是关于进程地址空间一级页表的讲解,理解进程地址空间和页表是理解进程管理的重要一环,他属于进程管理中较为细节的一部分。
最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!
相关文章:
Linux_理解程序地址空间和页表
目录 1、进程地址空间示意图 2、验证进程地址空间的结构 3、验证进程地址空间是虚拟地址 4、页表-虚拟地址与物理地址 5、什么是进程地址空间 6、进程地址空间和页表的存在意义 6.1 原因一(效率性) 6.2 原因二(安全性) …...
NAND闪存市场彻底复苏
在全球内存市场逐渐走出阴霾、迎来复苏曙光之际,日本存储巨头铠侠(Kioxia)凭借敏锐的市场洞察力和及时的战略调整,成功实现了从生产紧缩到全面复苏的华丽转身。这一转变不仅彰显了企业在逆境中的生存智慧,也为全球半导…...
过拟合与正则化
Location Beijing 过拟合 对于一个模型 A A A,解向量空间为 θ \theta θ,误差函数用式1表示 J ( θ ) J a c c [ y θ ( x ) − y ] 2 (1) J(\theta)J_{acc}[y_\theta(x)-y]^2\tag{1} J(θ)Jacc[yθ(x)−y]2(1) 首先我们考虑用模型 A A A拟合下…...
VMware挂载NAS存储异常处理
问题概述 由于非法关机或恢复,NFS存储可能会出现以下问题: 数据存储处于挂起状态或无法正常识别。虚拟机的配置文件或虚拟磁盘仍然注册在异常数据存储上。系统误认为有虚拟机在使用该数据存储。 问题对策 下面是详细的排查步骤和解决对策:…...
Redis 7.x 系列【4】命令手册
有道无术,术尚可求,有术无道,止于术。 本系列Redis 版本 7.2.5 源码地址:https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 说明2. 命令手册2.1 Generic2.2 数据类型2.2.1 String2.2.2 Hash2.2.3 List2.2.4 S…...
走进Elasticsearch
什么是ES 是一个分布式、RESTful风格的搜索和数据分析引擎 中文参考文档: 《Elasticsearch中文文档》 | Elasticsearch 技术论坛 elasticSearch官网: Functions and Operators | Elasticsearch Guide [7.11] | Elastic查询方式 Kibana查询(原…...
QT TCP服务器和客户端示例程序
下面是一个简单的 Qt TCP 服务器和客户端示例,演示了如何使用 vSetDriver、vSetListener 和 vTcpServerStart 函数。假设 vSetDriver 和 vSetListener 是你定义的自定义函数。 TCP 服务器部分 tcpserver.h #ifndef TCPSERVER_H #define TCPSERVER_H#include <QT…...
Xlua三方库Android编译出错解决办法
Xlua三方库Android编译出错解决办法 最近听老师的热更教程,讲到xlua编译android平台会报错,也是看了老师的博客,按照方法去解决,然而问题并没有解决。应该是因为代码更新或者版本不一样,在此简单记录一下解决过程。 参…...
美国犹他州立大学《Nature Geoscience》(IF=18)!揭示草本植物对土壤有机碳的重要贡献!
随着全球变暖的影响越来越显著,碳固定成为了一个备受关注的话题。在这个背景下,热带草原被认为是一个潜在的碳固定区域。然而,目前的研究主要关注于在热带草原中种植树木,以期望增加土壤有机碳含量。但是,热带草原中的…...
高考专业抉择计算机专业热度不减,兴趣、实力与挑战并存。
作为一名即将步入大学校门的高考生,我对于计算机相关专业是否仍是热门选择感到困惑。在过去几年里,计算机科学与技术、人工智能、网络安全、软件工程等专业一直备受追捧,吸引了无数学生。然而,随着市场竞争加剧和市场饱和度提高&a…...
Flask-RQ
Flask-RQ库教程 Flask-RQ 是一个用于在 Flask 应用中集成 RQ(Redis Queue)的扩展。RQ 是一个简单的 Python 库,用于将任务排入 Redis 队列并异步执行这些任务。这对于处理长时间运行的任务(如发送电子邮件、生成报告等࿰…...
LeetCode 58. 最后一个单词的长度
LeetCode 58. 最后一个单词的长度 你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串 示例 1: 输入:s “Hello World”…...
3阶段提交协议(3pc)
3阶段提交协议(3pc) 1 简介 三阶段提交协议是一个强一致、中心化的原子提交协议。解决了分布式事务、副本容错等分布式问题。其核心思想是将2PC的二阶段提交协议的“准备阶段”一分为二,形成了由CanCommit、PreCommit、DoCommit三个阶段组成…...
802.11中的各种帧
在无线网络中,802.11协议定义了三种类型的帧:管理帧(Management Frames)、控制帧(Control Frames)和数据帧(Data Frames)。每种类型的帧都有其特定的功能,帮助维护和管理…...
SAP PP学习笔记21 - 计划策略的Customize:策略组 > 策略 > 需求类型 > 需求类(消费区分,计划区分)
上面几章讲了MTS,MTO,ATO的计划策略。 本章来讲一下它的后台 Customize。 1,Customizeing:Planned Indep.Reqmts Management 这是配置计划策略的整个过程: - Requirements Type / Class 需求类型 / 需求类 - Plann…...
axure9设置组件自适应浏览器大小
问题:预览时不展示下方的滚动条 方法一:转化为动态面板 1.在页面上创建一个矩形 2.右键-转化为动态面板 3.双击进入动态面板设置 4.设置动态面板矩形的颜色 5.删除原来的矩形 6.关闭动态面板,点击预览 7.此时可以发现底部没有滚动条了 方法…...
示例:WPF中TreeView自定义TreeNode泛型绑定对象来实现级联勾选
一、目的:在绑定TreeView的功能中经常会遇到需要在树节点前增加勾选CheckBox框,勾选本节点的同时也要同步显示父节点和子节点状态 二、实现 三、环境 VS2022 四、示例 定义如下节点类 public partial class TreeNodeBase<T> : SelectBindable<…...
C++ explicit关键字的用法
在C中,explicit关键字用于构造函数和转换运算符,以防止隐式转换。它可以帮助我们避免意外的类型转换,从而提高代码的安全性和可读性。explicit关键字只能用于单参数构造函数和转换运算符。 使用explicit的场景 单参数构造函数: 当…...
51.Python-web框架-Django开始第一个应用的增删改查
目录 1.概述 2.创建应用 创建app01 在settings.py里引用app01 3.定义模型 在app01\models.py里创建模型 数据库迁移 4.创建视图 引用头 部门列表视图 部门添加视图 部门编辑视图 部门删除视图 5.创建Template 在app01下创建目录templates 部门列表模板depart.ht…...
Redis之线程IO模型
引言 Redis是个单线程程序!这点必须铭记。除了Redis之外,Node.js也是单线程,Nginx也是单线程,但是他们都是服务器高性能的典范。 Redis单线程为什么能够这么快! 因为他所有的数据都在内存中,所有的运算都…...
针对微电网中可时移,柔性,基础负荷的电价响应模型---代码解析
前言: 在上两篇帖子中,讲解了我对于粒子群算法的理解,站在巨人的肩膀上去回望:科研前辈们确实非常牛逼,所以它才成为了非常经典的算法。这篇帖子主要是想分享一下,对于微电网、电力系统的论文中,…...
git使用http协议时免密pull和push方法
1、创建文件 在项目目录下创建.git-credentials文件,内容如下,填入自己的用户名和密码即可,如果是gitlab,把地址换成自己的gitlab的地址即可。 https://{用户名}:{密码}github.com2、终端执行 git config --global credential.…...
编译期间生成代码(Lombok原理)
通过在编译期间,修改Java的AST(Abstract Syntax Tree)树,可以往类中,添加/修改(覆盖)方法、属性等。 现在比较常见的三方依赖例子有:Lobbok的Data可以生成get、set方法,Sl4j2可以生成静态常量l…...
第2讲:pixi.js 绘制HelloWorld
基于第0讲和第1讲,我们增添了vite.config.ts文件。并配置了其他的http端口。 此时,我们删除掉没用的东西。 删除 conter.ts、typescript.svg 在main.ts中改成如下内容: import {Application, Text} from pixi.js import ./style.css// 指明…...
golang HTTP2 https测试POST变GET问题小记
概述 因为工作需要协助修改某个golang程序,添加双向认证。但是在调整的过程遇到一个HTTP POST请求变成GET诡异的问题,最后各种搜索,总算解决,博文记录,用于备忘。 代码 服务端 因工作内容,代码有删减&a…...
Linux下的lvm镜像与快照
lvm镜像(mirror) (1)划分三个PV,其中2个PV大小要一模一样 Disk /dev/sdb: 21.5 GB, 21474836480 bytes 255 heads, 63 sectors/track, 2610 cylinders Units cylinders of 16065 * 512 8225280 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/…...
嵌入式linux系统中SPI子系统原理分析01
大家好,今天给大家分享一下,如何使用linux系统中的SPI通信协议,实现主从设备之间的信息传递。 SPI是一种常见的设备通用通信协议。它是一个独特优势就是可以无中断发送数据,可以连续发送或接收任意数量的位。而在I2C和UART中,数据以数据包的形式发送,有限定位数。 …...
Part 4.2 背包动态规划
->背包模型模板(0/1,分组,完全,多重)<- [NOIP2018 提高组] 货币系统 题目背景 NOIP2018 提高组 D1T2 题目描述 在网友的国度中共有 n n n 种不同面额的货币,第 i i i 种货币的面额为 a [ i ] a[i] a[i],你可以假设每…...
Elasticsearch-使用Logstash同步Mysql
1.安装logstash es服务器版本必须和logstash版本一致 7.9.2 在/usr/local/src/下新建logstash文件夹,解压 下载logstash后查看是否安装成功,在logstash的bin目录下输入指令: ./logstash -e input { stdin { } } output { stdout {} }2.my…...
6.17作业
升级优化自己应用程序的登录界面。 要求: 1. qss实现 2. 需要有图层的叠加 (QFrame) 3. 设置纯净窗口后,有关闭等窗口功能。 4. 如果账号密码正确,则实现登录界面关闭,另一个应用界面显示。 //发送端头文件…...
养生网站建设免费/企业网络营销系统分析报告
作者:郑连虎,在数学学院取得理学学位的文科生,中国人民大学硕博连读生在读,山东大学管理学学士、理学学士个人公众号:阿虎定量笔记本期目录01网页抓取02中文分词03文档矩阵04词频共现05文本聚类06主题建模07情感分析08…...
官方网站建立/企业培训权威机构
本篇项目地址,名字是媒体解码MediaCodec,MediaExtractor,求starhttps://github.com/979451341/Audio-and-video-learning-materials这次要用到新的东西SurfaceView、MediaCodec、MediaExtractor、MediaFormat 1.文字说明 SurfaceView:一个Vie…...
网站用户互动/网站安全查询系统
1: React 组件 (函数式组件) 1:React提供了两种创建组件方式:1.1:函数式组件 1.2: 类组件执行了ReactDOM.render(<myComponent />... 之后, 发生了什么)1: React 解析组件标签。 找到myComponent 组件2: 发现…...
wordpress 导购按钮/seo是什么意思 职业
杨辉三角形,相信大家都学习过,该如何用C语言来做它呢? 如下是其代码: #include<stdio.h> int main() {int n 0;scanf("%d", &n);int arr[30][30] {0};for (int i 0; i < n; i){for (int j 0; j < i; j){if (j…...
互联网 网站定制/在线培训系统平台
很久没写东西了,文笔生疏了许多,这段时间多的东西是总结和知识积累吧,少的是一些心路历程,很久了,没有去图书馆的书吧更新自己的人生啦,这是第一篇公开意义的心路历程,如果觉得安全,…...
渭南疫情防控最新通知/王通seo赚钱培训
题面 二分后用网络流判定 S->人,流量为二分的mid 人->比赛,流量为1 比赛->T,流量为1 输出方案只要判断a就可以了 # include <bits/stdc.h> # define IL inline # define RG register # define Fill(a, b) memset(a, b, si…...