【Linux】Linux进程地址空间
1.程序地址空间分配回顾
在前⾯C语⾔以及C部分介绍过⼆者的内存分配如下图所示:
全局变量区和未初始化全局变量区也被称为数据区,数据区中除了有全局变 量,还有静态变量和常量
使⽤下⾯的代码演示不同的内容所处的地址:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>// 初始化的全局变量
int global = 0;
// 未初始化的全局变量
int value;int main() {// 环境变量char* envi = getenv("PWD");// 堆区int* ptr = (int*)malloc(sizeof(int)*10);// 栈区int a = 0;int b = 0;int c = 0;printf("%p\n", &global);printf("%p\n", &value);printf("%p\n", ptr);// 代码区printf("%p\n", main); // 函数名即函数地址printf("%p\n", envi);printf("%p\n", &a);printf("%p\n", &b);printf("%p\n", &c);return 0;
}输出结果:
0x601048
0x60104c
0x7e1010
0x4005cd
0x7ffc99757f43
0x7ffc9975694c
0x7ffc99756948
0x7ffc99756944
实际上,所谓的程序地址空间就是进程地址空间,进程地址空间是如何产⽣的就是下 ⾯需要探讨的问题
2.进程地址空间
2.1.虚拟地址
前⾯提到,⽗进程和⼦进程会共享代码和数据,尤其是两个进程不进⾏数据修改时, 数据不会产⽣两份,那么这样理解就可以直观地认为当⼦进程修改了数据,对应的变 量内存地址就会发⽣改变,但是改变的是不是程序读取到的地址,看下⾯例⼦的演示 结果:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>// 初始化的全局变量
int global = 0;
// 未初始化的全局变量
// int value;int main() {printf("父进程, pid = %d, &global = %p\n", getpid(), &global);pid_t p = fork();if(p == 0) {// 子进程while(1) {printf("子进程, pid = %d, &global = %p\n", getpid(), &global);// 子进程修改global++;sleep(1);}} else {while(1) {}}return 0;
}输出结果:
父进程, pid = 12969, &global = 0x601050
子进程, pid = 12970, &global = 0x601050
可以看到,尽管⼦进程修改了与⽗进程共享的代码中的变量,⼦进程读取到的变量的 地址与⽗进程读取到的变量的地址是完全相同的。
实际上,在C语⾔程序中使⽤ & 获取到的地址是⼀个虚拟地址(也称线性地址),对 应虚拟地址的就是物理地址。
在上⾯提到的「⼦进程改变代码中的数据,对应的内存地址会发⽣改变」,本质是因 为此处的内存地址指的是物理地址,⽽不是虚拟地址
2.2.地址空间
地址空间,可以理解为是操作系统为每⼀个进程开辟的⼀块运⾏空间,示意图如下:
为了保证所有的进程都有⼀个完整的地址空间,操作系统为每个进程提供⼀个独⽴的 虚拟地址空间。例如,如果操作系统⽀持3GB的虚拟地址空间,每个进程都会认为⾃ ⼰拥有独⽴的3GB空间,不会受到其他进程占⽤内存的影响。这⼀过程暂且可以理解 为操作系统为每⼀个进程“画的⼀张⼤饼”,⽽这个“饼”即为进程地址空间
2.3.区域划分
进程地址空间中存在着多个区域,每⼀个区域有着⾃⼰的作⽤。每⼀块区域的划分实 际上是根据区域的起始值和终⽌值进⾏决定,示意图如下:
为了⽅便管理,在Linux中,操作系统在底层的 task_struct 内部存在着⼀个结构 体指针,该结构体指针的类型是 mm_struct 结构体,该结构体中存在着⼀些变量⽤ 于存储指定区域的起始值和终⽌值。这些值本质也是地址值,但是这个地址并不是实 际的物理内存的地址,⽽是通过映射后的虚拟地址,源码如下:
struct mm_struct {// ...unsigned long start_code, end_code, start_data, end_data;unsigned long start_brk, brk, start_stack;unsigned long arg_start, arg_end, env_start, env_end;unsigned long rss, total_vm, locked_vm;// ...
};
因为变量的地址都是在这些指定的进程地址空间区域中,所以也可以解释为什么程序 获取到的是虚拟地址⽽⾮物理地址
解释:
3.分页与数据独立
在上⾯探讨了虚拟地址和物理地址,操作系统为了管理这两个地址之间的映射,从⽽ 保证虚拟地址能够正确访问到实际的物理地址对应的内容,在创建⼀个进程PCB时会 同时创建⼀个⻚表。
在执⾏前⾯的代码的过程中,⼦进程未创建之前,⽗进程拥有⾃⼰的⻚表和进程地址 空间,此时全局变量 global 会由操作系统在物理地址上开辟⼀块空间,并将映射后 的虚拟地址填充到⻚表中被进程获取,如下图所示:
当创建⼦进程之后,⼦进程会与⽗进程共享代码和数据,此时意味着⼦进程拷⻉⽗进 程中的 task_struct 和⻚表,对 task_struct 中的内容进⾏针对性地修改,在⼦进 程没有改变复制过来的数据之前,由于⻚表内容相同,所以⼦进程⻚表中的映射与⽗ 进程完全相同,示意图如下:
当⼦进程对 global 变量进⾏修改时,操作系统就会在物理内存中开辟⼀块新的空 间,将共享的 global 数据拷⻉到该空间,这个拷⻉的过程也被称为写时拷⻉。物理 内存虽然开辟了⼀块新的空间,实际上对于⼦进程的⻚表来说,还是通过同样的虚拟 地址映射物理地址:
上⾯的过程也就可以解释为什么最开始的代码中,⼦进程和⽗进程获取到的变量值不 同,但是地址却相同,也就更加验证了⼀个变量中不可能存在两个不同的值
4.页表基本介绍
在Linux中,⻚表不仅仅有虚拟地址和物理地址的映射,还有物理地址的属性 RWX 以 及是否有数据的标识 isExists ,所以更加细致的⻚表应该如下所示:
RWX 属性:代表虚拟地址对应的物理地址是否具有读(R)、写(W)和执⾏权限 (X)。
前⾯提到,每⼀个进程地址空间区域都由指定的起始值和终⽌值进⾏划分,⽽这些区 域有的是可以写,有的不可以写只能读,但是对于物理内存来说,绝⼤部分的空间都 是可以写的,所以对于限制指定的物理地址是否可以写⼊就是通过 RWX 属性进⾏控制
例如,前⾯学习到的栈区和堆区,在程序代码运⾏时,可以在栈区和堆区申请空间并 进⾏写⼊,但是对于字符串常量等具有常性的值就不可以进⾏随意修改和写⼊。⽽之 所以在语⾔层⾯⽆法检测到这种问题,原因就是⻚表并不是在编译期间就创建的,⽽ 是在程序运⾏开始由操作系统创建,但是为了语⾔层⾯更加容易看出这种错误,就可 以把不想被修改的内容或者本身不可以修改的内容修饰为 const ,从⽽让编译器可 以在编译器检查出错误
此刻也便可以解释为什么程序会有野指针的概念,在语⾔层⾯,野指针表示指针中的 地址对应的空间已经被释放并归还给操作系统进⾏管理,此时不可以通过这个指针访 问对应的地址。在此时的系统层⾯,之所以可以限制野指针写⼊就是通过 RWX 属性, 因为虚拟地址对应的物理地址具有的属性已经是R
isExists 属性:代表虚拟地址对应的物理地址是否存在有效数据。在操作系统加载 进程时,会创建对应的进程地址空间和⻚表,在⻚表中将虚拟地址和物理地址进⾏映 射。然⽽,这个过程中某些进程的代码可能特别多,导致正⽂代码区占⽤的空间变得 很⼤,⽽实际使⽤时,可能有很⼤⼀部分的代码⻓时间不会被执⾏,造成资源浪费。 为了解决这个问题,操作系统通常采⽤按需加载(惰性加载,Lazy Loading)策 略。操作系统会先加载⼀部分内容,在运⾏过程中,通过检查⻚表中的有效位 (Valid Bit)来判断虚拟地址访问的物理地址是否已经加载。如果没有加载,则 触发缺⻚中断(Page Fault),操作系统会从磁盘加载相应的数据到内存中,再继 续执⾏。这⼀过程也适⽤于交换分区(Swap Partition),决定何时将数据换⼊ 或换出内存
结合前⾯的两个属性,操作系统就实现先告诉进程⾃⼰已经开辟好了空间,这个空间 的地址由多个虚拟地址组成,但是实际上可能物理地址并没有全部与指定的虚拟地址 对应,当程序运⾏到指定的部分再进⾏开辟映射,这个操作就可以实现将内存空间利 ⽤率最⼤化
5.缺页中断与写时拷贝
前⾯提到,当⼦进程修改了⽗进程的共享变量就会发⽣写时拷⻉,实际上,在这之前 需要进⾏⼀系列的操作检测
当⽗进程开始运⾏,⼦进程还未被创建之前,⽗进程的代码区为只读,但是数据区是 默认为读写的,当⼦进程创建后,代码区依旧是只读,但是⼆者的数据区均变为只 读,⼦进程从创建的位置开始执⾏代码,因为⼦进程会拷⻉ task_struct 中有关程 序计数器部分的内容,此时⽗⼦进程都只是读取对应代码区和数据区的数据,实现数 据和代码共享
注意::!
此处之所以⼦进程不额外直接拷⻉⽗进程的数据,尽管将来可能要对数据进⾏ 修改,是因为可能⽗进程的数据很⼤,但是⼦进程需要修改的数据很⼩,如果在创建 ⼦进程时就将数据拷⻉,此时就会导致空间的浪费
当⼦进程准备修改数据时,因为数据区已经变为只读,⼦进程想要修改就会修改失 败,此时就会触发缺⻚中断错误。但是因为缺⻚中断错误发⽣的原因不只有⼀种,还 有可能是野指针写⼊等情况,所以此时系统需要检测是否是误操作。当系统检测到数 据区本身是读写的,但是现在被设置为只读时,就会认为需要发⽣写时拷⻉
判定完需要进⾏写时拷⻉时,系统就会在物理内存中申请空间,将原始数据拷⻉到新 空间,并对⽗进程和⼦进程的数据区修改为只读,⽗进程和⼦进程代码继续执⾏
这个过程中涉及到原始数据拷⻉⽽不是简单开辟空间使⽤等待⼦进程使⽤新数 据进⾏覆盖是因为可能⼦进程对数据的修改是基于原数据的,例如变量⾃增⾏为等
上述过程简略示意图如下:
⼦进程修改数据::
6.进程地址空间结构初始化时机
任何⼀个结构体在创建时⼀定要进⾏初始化,⽽进程地址空间也是由⼀个结构进⾏描 述,这个结构的初始化由操作系统在可执⾏程序加载到内存时完成,可执⾏程序加载 到内存变成进程时,操作系统可以获取到部分区域的起始值和终⽌值,例如正⽂代码 区、数据区(包括全局变量区、常量区、静态变量区)和命令⾏与环境变量区。所以 这就可以解释为什么静态变量、全局变量和常量⼀直持续到进程结束
但是这其中有两个不同的区域:栈区和堆区,栈区在函数创建时会开辟对应的栈帧, 堆区在申请时会在已有的堆区上额外开辟需要的空间,所以这两个区域在程序刚加载 到内存时是不存在的
7.总结
之所以需要存在⻚表和进程地址空间有以下三个原因:
1. 保护内存:如果让进程直接访问物理内存,会导致在物理层⾯的野指针情况, 并且这个情况在物理层⾯并不容易被发现和阻⽌
2. 存在⻚表可以达到进程管理和内存管理耦合度降低:因为⻚表主要作⽤的是虚 拟地址和物理地址之间的映射,操作系统在创建进程时初始化对应的虚拟地址 即可,但是虚拟地址是否有对应的物理地址可以不⽤关⼼,只要没有被使⽤。 ⽽对于内存管理,操作系统只需要考虑在需要的时候将物理地址加载到⻚表, 此时对应的虚拟地址有映射就可以正常执⾏,在不需要的时候,将物理地址设 置为只读或者给其他进程使⽤
3. 让进程以统⼀的视⻆看待物理内存(⽆序变有序):因为操作系统会为每⼀个 进程开辟⼀个独⽴的⻚表和进程地址空间,让每⼀个进程都认为⾃⼰拥有操作 系统分配的全部内存空间,并且在进程访问地址空间时,实际上这个地址空间 的地址是⼀个虚拟地址,这个虚拟地址可以由操作系统⾃主决定为连续地址, 此时就可以不⽤考虑物理地址是否需要连续,因为进程只能获取到虚拟地址, 只要虚拟地址有物理地址映射并且拥有指定的权限,就不会出现问题,从⽽实 现让「让⽆序的物理内存地址变为有序的地址」
物理地址在开辟时,⼀般也会尽量是连续开辟,保证CPU在缓冲中的数据命中率
相关文章:

【Linux】Linux进程地址空间
1.程序地址空间分配回顾 在前⾯C语⾔以及C部分介绍过⼆者的内存分配如下图所示: 全局变量区和未初始化全局变量区也被称为数据区,数据区中除了有全局变 量,还有静态变量和常量 使⽤下⾯的代码演示不同的内容所处的地址: #includ…...

创建包含可导入浏览器信任的SSL自签名证书
问题:现在的三大浏览器,chrome、edge、firefox 一般都默认启用https检查,这就要求我们自建的局域网内的网址和其他诸如nextcloud、photoprism、tiddlywiki等应用也必须要有证书。解决方法是使用openssl自己生成一个。由此则会再衍生出一个问题…...

[Windows] 很火的开源桌面美化工具 Seelen UI v2.0.2
最近,一款来自Github的开源桌面美化工具突然在网上火了起来,引发了大家的关注,不少小伙伴纷纷开始折腾了起来。而折腾的目的,无非是为了一点点乐趣而已,至于结果如何,并不是最要紧的,反倒是体验…...

华帝携手抖音头部达人,金牌导演李力持量身打造厨电定制微短剧
10月21日,由华帝独家冠名,金牌导演李力持执导,抖音头部达人逆袭丁姐领衔主演,ATV亚洲电视、知行易达、抖音商城联合出品的24集现代家庭治愈美食定制微短剧《女厨神》上线抖音。 该剧讲述了热爱生活和美食的丁姐,为了家…...
监控易监测对象及指标之:JBoss 7.1.x中间件监控
监控易是一款功能全面的监控软件,能够实时追踪和分析各类IT资源的性能数据,提供及时的故障预警和性能报告。本文将对监控易针对JBoss 7.1.x中间件的监控指标进行深入解读,以帮助用户更好地理解和应用这些数据。 JBoss 7.1.x作为一款广泛使用的…...
Java 模拟退火算法
模拟退火算法(Simulated Annealing, SA)是一种用于全局优化的启发式搜索算法,它模仿了物理学中金属退火的过程。该算法在搜索空间中逐步降低“温度”,以寻找全局最优解。下面是一个用Java实现模拟退火算法的简单示例。 假设我们要…...

LeetCode[中等] 80. 删除有序数组中的重复项 II
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 public cl…...
机器学习5
1.1 决策树的定义 决策树是用于分类和回归的机器学习算法。它通过一系列的“是或否”的决策来分类数据。每个决策是基于数据的某个属性进行的,如“色泽是青绿吗?”。决策树的核心是通过树状结构,将一个复杂的问题逐步拆解为多个简单的二元问…...

【Python技术】利用akshare定时获取股票实时价,低于5日线钉钉通知报警
今天看了下大盘,临时有个想法,我想知道某个股票回踩5日线的价格,如果实时价格低于5日线通过钉钉报警通知我。 说干就干,临时撸了下简单的代码,仅做演示。 1、计算5日线思路 很多券商软件的MA5价格是近5个交易日收盘…...

LINUX1.2
1.一切都是一个文件 (硬盘) 2.系统小型 轻量型,300个包 3.避免令人困惑的用户界面 ------------------> 就是没有复杂的图形界面 4.不在乎后缀名,有没有都无所谓,不是通过后缀名来定义文件的类型(win…...
Proximal Distance Algorithm (近段距离算法)
文章目录 第一篇\section*{近端距离算法(Proximal Distance Algorithm)详解}\subsection*{1. MM原理(Majorization-Minimization Principle)}\subsection*{2. 近端距离算法(Proximal Distance Algorithm)}\…...
如何判断一个数是几位数与这个数是否为回文数并打印出其逆序数
1 问题 判断一个数是几位数与这个数是否为回文数并打印出其逆序数。 2 方法 先输入一个少于五位数的数用int的方法打出这个数的个十百千万的数字再用条件语句else-if来判断这个数是几位数,并打印其逆序数最后判断这个数是否为回文数,打印其数 通过实验、…...
Solon 之 STOMP
一、STOMP 简介 如果直接使用 WebSocket 会非常累,就像用 Socket 编写 Web 应用。没有高层级的交互协议,就需要我们定义应用间所发消息的语义,还需要确保连接的两端都能遵循这些语义。 如 HTTP 在 TCP 套接字之上添加了请求-响应模型层一样…...

在掌控板上搭建http服务器
在掌控板上搭建http服务器 打开Arduino IDE,并且已经添加了ESP32的支持库。以下是创建一个基本HTTP服务器的步骤: 包含必要的库: #include <WiFi.h> #include <WebServer.h>配置WiFi: 替换ssid和password为你的WiFi网…...

HCIA复习实验
实验要求 实验拓扑以及实验分析 第一步先划分网段 先对内网划分 192.168.1.0/24划分 192.168.1.0/26---骨干主线路 192.168.1.64/26---骨干备线路 ---192.168.1.128/25--vlan2 3汇总---便于减少路由表条目---在大型网络方便 192.168.1.128/26---vlan2 192.168.1.192/26---vla…...

生信软件39 - GATK最佳实践流程重构,提高17倍分析速度的LUSH流程
1. LUSH流程简介 基因组测序通常用于分子诊断、分期和预后,而大量测序数据在分析时间方面提出了挑战。 对于从FASTQ到VCF的整个流程,LUSH流程在非GVCF和GVCF模式下都大大降低了运行时间,30 X WGS数据耗时不到2 h,从BAM到VCF约需…...

c#编写的各类应用程序、类库的引用(黑白盒)
001 课程简介,C# 语言简介,开发环境准备 (yuque.com)https://www.yuque.com/yuejiangliu/dotnet/timothy-csharp-001 一个Solution里包含多个Project 一、见识 C# 编写的各类应用程序 二、类库的引用(黑/白盒引用) 1、黑盒引用&a…...

计算机网络考研笔记
...

用感性的方式浅要了解什么是AI 与 大模型
什么是人工智能(AI)? 人工智能(Artificial Intelligence,简称 AI)是指由人制造出来的具有一定智能的系统,能够理解和学习人类的行为,并在某些任务上模仿人类的智能行为。这些任务包…...

Linux文件的查找和打包以及压缩
文件的查找 文件查找的用处,在我们需要文件但却又不知道文件在哪里的时候 文件查找存在着三种类型的查找 1、which或whereis:查找命令的程序文件位置 2、locate:也是一种文件查找,但是基于数据库的查找 3、find:针…...

铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

高分辨率图像合成归一化流扩展
大家读完觉得有帮助记得关注和点赞!!! 1 摘要 我们提出了STARFlow,一种基于归一化流的可扩展生成模型,它在高分辨率图像合成方面取得了强大的性能。STARFlow的主要构建块是Transformer自回归流(TARFlow&am…...