当前位置: 首页 > news >正文

【Linux】进程虚拟地址空间详解

目录

一、引出虚拟地址空间

二、什么是虚拟地址空间

三、虚拟空间划分

        3.1 如何理解进程是独立的

        3.2 为什么全局变量全局有效

        3.3 为什么字符串常量只读

四、为什么要虚拟地址空间

        (1)安全风险

        (2)无序变有序

        (3)解耦合


正文:

一、引出虚拟地址空间

结论:我们历史所学的所有地址都是虚拟地址,我们是看不到真正的物理地址的。

在学习的过程中,大家在课堂上或网上可能经常看到以下内存的划分图:

进程具有独立性,所以进程在内存中的物理地址空间应该也具备独立性。

那么下面通过一个例子证明是否如我们所想这样:创建一个test.c文件把下面代码打进去再运行

查看结果发现gval已经从100加到101了,变量内容不⼀样,所以父子进程输出的变量绝对不是同⼀个变量,但地址值是⼀样的,说明,该地址绝对不是物理地址!

我们在⽤C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户⼀概看不到,由OS统⼀
管理

二、什么是虚拟地址空间

结论:通过页表映射到物理内存的位置进行访问。

多个可执行程序加载到内存,操作系统通过先描述后组织的手段管理进程,系统会自动生成描述该进程的结构体(PCB)、虚拟地址空间表和页表。

进程在访问内存时要先进行虚拟地址到物理地址的页表映射,找到物理内存才能访问数据。

三、虚拟空间划分

结论:虚拟地址空间的区域划分本质上是通过 vm_area_struct(VMA)来实现的

虚拟地址空间是操作系统为每个进程抽象出来的一种内存视图,它让每个进程都“认为”自己独占整个内存资源。有多个进程就有多个虚拟空间,这时操作系统就要通过先描述再组织的方法管理起来这些虚拟空间。

而我们所谈到的虚拟地址空间其实是一个内核数据结构(struct mm_struct)

虚拟地址空间的管理是通过 struct mm_struct这个关键数据结构实现的。每个进程都有一个独立的 struct mm_struct,它描述了该进程的整个虚拟内存布局,包括代码段、数据段、堆、栈、内存映射区域等。如下图所示:

 struct mm_struc是一个内核数据结构,是一段线性空间,也就可以通过一段开始和结束地址表明一段范围即可。开始到结束之间的内容都可以被使用。

在 Linux 内核中,struct mm_struc 并不直接“划分”虚拟地址空间,而是通过管理 虚拟内存区域(vm_area_struct) 来描述进程地址空间的布局。每个 VMA(vm_area_struct)代表一段连续的虚拟内存范围,并记录该区域的权限、映射方式等属性。

所以虚拟地址空间的区域划分本质上是通过 vm_area_struct(VMA)来实现的。mm_struc 通过链表和红黑树组织这些 VMAs,从而实现对虚拟地址空间的动态管理。如下图所示:

可以理解为:虚拟地址空间就是操作系统给进程画的一张饼。

比如一个大富翁有一个亿,同时也有6个私生子,它对每个私生子都说未来一个亿都是给他的,而私生子之间并不知道其他私生子的存在,都认为未来会继承一个亿。

同理,操作系统为每个进程分配一个独立的虚拟地址空间(比如 32 位系统是 4GB),让进程“以为”自己独占整个内存。

只有进程真正访问这块内存时(触发缺页异常),操作系统才会分配物理内存,相当于“先答应给你,等你要用的时候再兑现”。

虚拟地址空间分为:用户空间VS内核空间

用户空间:进程可以直接通过虚拟地址访问用户态内存(如代码、堆、栈)

内核空间:必须通过系统调用(如 read、write)使用

3.1 如何理解进程是独立的

进程=内核数据结构(PCB、虚拟地址空间、页表)+进程的代码和数据

可执行程序加载到内存会自动创建PCB、虚拟地址空间、页表。父进程fork()创建子进程也会把PCB、虚拟地址空间、页表给子进程拷一份,这样父子都有独立的内核数据结构,子进程是继承父进程的,所以进程的代码和数据都指向父进程的(共用)。但是代码是只读的,数据也是只读的,如果子进程要对数据进行修改会进行写实拷贝(数据层面的分离)也就和父进程不再是同一个数据。内核数据结构、进程的代码和数据都不同所以进程是独立的,子进程销毁不影响父进程。

3.2 为什么全局变量全局有效

全局变量是在全局数据区的,地址空间只要存在,那么全局数据区就要存在,所以全局变量会一直存在,包括static静态变量。

3.3 为什么字符串常量只读

字符串常量是和代码编译在一起的,都是只读(因为代码就是只读)

而当你想修改常量值(w),必须用虚拟地址通过页表映射找到物理地址才能修改,在页表中会有专门权限限制你要访问的内容是可读还是可写(r/w)字符串常量区被页表映射时不让写入操作。

我们在定义字符串时前面会加const,const是约束编译器,让编译器进行写入检查,检查到就报错!

四、为什么要虚拟地址空间

(1)安全风险

有了虚拟地址就必须转换为物理地址才能访问。虚拟到物理之间的转换会进行安全审核,变相保护了物理内存安全,维护了独立性。

如果没有虚拟地址空间,用户直接在物理内存中用指针指来指去会很乱,可能改到其他数据造成不必要麻烦,缺乏独立性,进程之间反而会相互影响。

(2)无序变有序

物理内存的实际使用情况通常是杂乱无序!!!

我们可执行程序加载到内存,为它分配的物理内存可以是任意位置,但是虚拟地址空间分配是固定顺序的(哪一块是栈区,哪一块是堆区....已经标好)所以进程看自己代码全是有序的,不管你物理内存中怎么乱,都可以通过虚拟地址页表映射找到指定数据。

(3)解耦合

对进程管理和内存管理解耦合!使其在内存管理中增加空间不影响进程管理部分。

我们创建一个进程,要对其先描述后组织,那么一定要把进程的代码和数据立即加载进来吗?

不一定,可以一边执行一边加载!执行到某行代码发现内存空间内没有就开空间把数据加载进来。这种加载称为惰性加载,提高了内存使用率。上面提到的写实拷贝也是一种惰性申请,我们要改变数据了再开空间、拷数据然后建立映射关系。


完,期待下次的一起学习~

http://www.lqws.cn/news/112429.html

相关文章:

  • 嵌入式复习小练
  • 【散刷】二叉树基础OJ题(二)
  • 0518蚂蚁暑期实习上机考试题3:小红的字符串构造
  • 基于netmiko模块实现支持SSH or Telnet的多线程多厂商网络设备自动化巡检脚本
  • 采摘机器人项目
  • 北京大学肖臻老师《区块链技术与应用》公开课:07-BTC-挖矿难度
  • 【学习笔记】深度学习-过拟合解决方案
  • 光伏功率预测新突破:TCN-ECANet-GRU混合模型详解与复现
  • 前端(vue)学习笔记(CLASS 7):vuex
  • C++学者给您讲数学之——数列
  • 【Spring AI 1.0.0】Spring AI 1.0.0框架快速入门(1)——Chat Client API
  • Leetcode-7 寻找用户推荐人
  • C++中锁与原子操作的区别及取舍策略
  • 深入理解 JSX:React 的核心语法
  • JSON to Excel 3.0.0 版本发布 - 从Excel插件到Web应用的转变
  • 手撕HashMap!(JDK7版本)
  • 张雪峰为9岁女儿申请40个左右商标!
  • java反序列化:CC5利用链解析
  • Python应用continue关键字初解
  • Python数据分析及可视化中常用的6个库及函数(二)
  • BGP/MPLS IP VPN跨域解决方案
  • 《Pytorch深度学习实践》ch5-Logistic回归
  • ollama的安装及加速下载技巧
  • VBA模拟进度条
  • 缩量和放量指的是什么?
  • 二叉树(二)
  • Windows应用-音视频捕获
  • 嵌入式SDK技术EasyRTC音视频实时通话助力即时通信社交/教育等多场景创新应用
  • Win11系统不推送24H2/西数SSD无法安装24H2 - 解决方案
  • 6.4 note