🎁个人主页:工藤新一¹
🔍系列专栏:C++面向对象(类和对象篇)
🌟心中的天空之城,终会照亮我前方的路
🎉欢迎大家点赞👍评论📝收藏⭐文章
文章目录
- 虚拟进程地址空间
- 一、虚拟地址空间经典布局
- 二、页表
- 2.1 核心定义:什么是页表?
- 2.2 为什么需要页表?
- 三、虚拟地址空间
- 3.1 虚拟地址空间是什么?
- 3.2 如何管理虚拟地址空间?
- 3.3 为什么要有虚拟地址空间?
- 四、工业疑问
- a. 创建进程时,是否可以只创建 PCB/地址空间/页表?
- b. 创建进程现有task_struct,还是先加载数据/代码
- c. 如何理解进程挂起?
- 五、虚拟内存管理
虚拟进程地址空间
虚拟地址空间 是指一个 OS为每个运行中的进程(程序)提供的一个抽象的、独立的、连续的逻辑地址范围。这个空间是 “虚拟的”,这并不意味着物理内存中真的有这么一大块连续的区域,而是通过硬件(MMU
,内存管理单元)和 Kernel
的协作,将虚拟地址映射到分散的物理内存页上
一、虚拟地址空间经典布局
一个进程的典型内存地址空间布局如下图所示,它被划分为多个具有不同权限(读、写、执行)的段(Segments):
从高地址到低地址:
- 内核空间(Kernel Space)
- 通常位于地址空间的最高处(例如,在32位Linux中,0x C000 0000以上)。
- 存放操作系统内核的代码、数据和数据结构。
- 所有进程共享同一份内核映射。但这段空间受保护,用户态进程无法直接访问,必须通过系统调用(Syscall)进入内核态才能访问。
- 栈(Stack)
- 向下增长(向低地址方向)。
- 用于存储局部变量、函数参数、返回地址等。
- 每个函数被调用时,会在栈上分配一个新的“栈帧”。
- 它的增长是自动的,但大小有限(通常默认为几MB),溢出会导致“栈溢出”错误。
- 内存映射段(Memory Mapping Segment)
- 用于映射文件或匿名内存。
- 动态链接库(如
.so
、.dll
文件)就加载在这里。 - 也可以通过
mmap()
系统调用创建,用于大块内存的分配或进程间共享内存。
- 堆(Heap)
- 向上增长(向高地址方向)。
- 用于动态内存分配。当程序员使用
malloc()
、new
等申请内存时,内存就从这里分配。 - 堆的大小只受限于系统可用的虚拟内存总量,管理由程序员负责(分配和释放), improper management leads to memory leaks.
- BSS 段(.bss)
- 存放未初始化的全局变量和静态变量。
- 在程序开始执行前,操作系统会将此段初始化为零。
- 数据段(.data)
- 存放已初始化的全局变量和静态变量。
- 代码段(文本段)(.text)
- 存放程序的执行代码(机器指令)。
- 通常是只读和可执行的,以防止代码被意外修改。
- 保留区(Reserved)
- 通常是最低地址的一段空间(例如
0x0
到0x400000
),不允许访问,用于捕捉空指针等错误。
- 通常是最低地址的一段空间(例如
地址区域分布:
感受虚拟地址:
fork():
一次调用,两次返回
这是理解 fork()
最关键也是最反直觉的一点:
- 在父进程中,
fork()
返回新创建子进程的进程ID(PID)(一个大于0的数)。 - 在子进程中,
fork()
返回 0。 - 如果创建失败(例如系统资源不足),
fork()
返回 -1。
进程具有独立性:数据层面上,互不影响;此时就需写时拷贝,实现进程的个性化
相同地址,获取不同变量值
二、页表
2.1 核心定义:什么是页表?
页表是虚拟内存系统的核心数据结构,是连接 虚拟地址 和 物理地址 的“地图”或“翻译官”;是 Kernel
为每个进程维护的一个映射表,它记录了该进程的虚拟内存页对应到物理内存帧的映射关系
简单来说,它的工作就是回答这个问题:
“这个进程看到的虚拟地址 X,实际上在物理内存的哪个地方?”
OS 会将进程物理地址隐藏起来,我们只能观测到进程的虚拟地址
解决历史遗留问题:
2.2 为什么需要页表?
页表是实现虚拟地址空间这一抽象概念的技术基础。没有页表,虚拟内存就无法工作。它的存在是为了:
- 实现地址翻译:将程序使用的 虚拟地址转换 为硬件使用的 物理地址。
- 实施内存保护:通过页表项中的权限位,控制进程对内存的访问(可读?可写?可执行?)。
- 支持“换出”到磁盘:通过页表项中的“存在/不存在”位,操作系统可以知道某页是否在物理内存中,如果不在,它的数据存放在硬盘的哪个位置。
三、虚拟地址空间
3.1 虚拟地址空间是什么?
- **虚拟地址空间本质上就是 OS 给进程画的一张饼! **《大富翁例子》 让进程误以为其独占整个内存的相关资源
画大饼的作用:让每一个进程都认为自己有 4GB的物理内存空间,或者:让每一个进程都认为自己在独占物理内存空间
OS 为每个进程都画了大饼,所以我们也要把这张大饼管理起来
3.2 如何管理虚拟地址空间?
先描述,在组织!
将所有的 虚拟进程地址空间[饼],用链表的方式管理起来;因此,对饼的管理就转化为了对链表的增删查改
虚拟地址空间本质:Kernel当中为进程创建的结构体对象!
3.3 为什么要有虚拟地址空间?
1. 将无/乱序的物理地址转变为有序的虚拟地址!
- 对用户(进程)而言:虚拟地址空间是连续且有序的
- 对系统(操作系统)而言:物理内存[完全随机且不连续]的分配是灵活且混乱的
2. 地址转化过程中对地址与操作进行合法性判定,进而保护物理内存!
a. 什么是野指针?
b. char* str = “hello linux!”; *str = ‘H’;
为什么在字符常量区写入就会崩溃?
3. 缺页中断 与 按需调页
缺页中断是操作系统 “欺骗” 进程的基础,也是它管理内存的得力工具(画大饼)
4. 使进程管理与内存管理,进行一定程度的解耦合
进程管理 & 内存管理 –——> 直线脱钩
四、工业疑问
a. 创建进程时,是否可以只创建 PCB/地址空间/页表?
核心观点:惰性加载 (Lazy Loading) 与按需调页 (Demand Paging)
问题背后隐藏着一个关键思想:为什么要在进程一开始就把它可能永远用不到的东西全部加载好呢? 这太浪费了….它们遵循 “惰性”原则,只在真正需要时才分配资源。这个过程就是通过 **缺页中断 ** 来实现的
b. 创建进程现有task_struct,还是先加载数据/代码
一定先有管理结构: task_struct
和 mm_struct
,空页表[虚物]。滞后加载代码和数据[实物],发生在第一次访问时,由缺页中断机制驱动。并且,加载过程本身也是由这些数据结构指导
c. 如何理解进程挂起?
五、虚拟内存管理
虚拟内存管理是一种内存抽象机制。虚拟内存管理系统的任务,就是将进程使用的这些虚拟地址(Virtual Address)动态地映射到物理内存上的物理地址(Physical Address),或者必要时映射到磁盘上的交换空间(Swap Space)
这个过程主要由计算机的 内存管理单元(MMU) 和操作系统内核共同完成。
描述 Linux 下进程的地址空间的所有的信息的结构体是 mm_struct
(内存描述符)。每个进程只有⼀ 个 mm_struct
结构,在每个进程的 task_struct
结构中,有⼀个指向该进程的结构
struct mm_struct{/*...*/struct vm_area_struct *mmap; /* 指向虚拟区间(VMA)链表 */ struct rb_root mm_rb; /* red_black树 */ unsigned long task_size; /*具有该结构体的进程的虚拟地址空间的⼤⼩*/
/*...*/// 代码段、数据段、堆栈段、参数段及环境段的起始和结束地址。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;/*...*/
- Virtual Memory
进程具有独立性:
- 内核数据结构独立!
- 加载进内存的代码和数据独立!
目前,我们对于虚拟地址空间的理解只能做到局部性的逻辑自洽
🌟 各位看官好,我是工藤新一¹呀~
🌈 愿各位心中所想,终有所致!