当前位置: 首页 > 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.xdnf.cn/news/790273.html

相关文章:

  • 太阳敏感器:卫星姿态控制的“指南针
  • istringstream
  • qt 事件顺序
  • Windows安装PostgreSQL(16.9)
  • 半导体行业-研发设计管理数字化转型案例分享
  • 【Typst】6.布局函数
  • c# 显示正在运行的线程数
  • lsinitramfs命令
  • 新德通科技:以创新驱动光通信一体化发展,赋能全球智能互联
  • Vue3.5 企业级管理系统实战(二十二):动态菜单
  • 代码随想录60期day56
  • 海盗64位GameServer的使用体验
  • 【自动思考记忆系统】demo (Java版)
  • 记一次sql按经纬度计算距离
  • 市面上有真正的静态住宅ip吗?
  • android NDK 的 -> 是什么意思
  • LRC and VIP
  • mac环境下的python、pycharm和pip安装使用
  • C++核心编程_ 函数调用运算符重载
  • PPO: Proximal Policy Optimization Algorithms
  • 全面解析 Windows CE 定制流程:从内核到设备部署
  • 基于MATLAB的FTN调制和硬判决的实现
  • 手把手教你用Appsmith打造企业级低代码平台:从部署到性能调优实战
  • PPO和GRPO算法
  • 大模型的外围关键技术
  • 【面试】音视频面试
  • 亮数据网页解锁器:让数据触手探索亮数据解锁工具:打破网页数据采集的局限
  • GPIO的内部结构与功能解析
  • Spring Boot Actuator未授权访问漏洞修复
  • RS232/RS485 光电隔离转换器DAM-3210A