
引言需少部分进程基础知识十分注意关键处图文并看原则。我相信通过阅读此文Linux地址与物理地址架构关系清晰无比。目录内存地址分布图问题为什么采用堆、栈增长方向不一致的设计内存地址的本质虚拟内存——物理内存1.1虚拟内存1.1.1一个地址问题1.1.2页表简单页表结构理解1.2单进程分析1.2.1进程-页表-磁盘联系1.3父子进程分析1.3.1内核代码引用☆☆☆1.3.2父子进程与物理内存的对应关系1.4超硬核解析虚拟内存与物理内存本质1.4.1mm_struct1.4.1.2mm_struct内部关键字段1.4.2辨析进程地址空间与虚拟地址空间1.4.2.1一个问题2.1计算机物理内存内存地址分布图常规的内存地址分布图以32位系统下4G内存为例由下到上地址逐渐增加栈向下增长增加成员地址渐小堆向上增长增加成员地址渐大。问题为什么采用堆、栈增长方向不一致的设计1.充分利用内存空间。2.减少管理复杂度。3.减少碰撞。内存地址的本质以上地址的区域分布均是虚拟内存地址表——并非真正的电脑上的物理内存。// 可以形象地理解为本地IDE是一款游戏而其虚拟内存就是人物血量。 既然是游戏人物血量耗完现实中你的血量会耗完吗 ps世界上最好玩的游戏Visual Studio虚拟内存——物理内存进程级内核分析1.1虚拟内存我们已经了解到虚拟内存并非真正的电脑内存。1.1.1一个地址问题我们使用父子进程来进行探讨——代码如下#include stdio.h #include unistd.h #include stdlib.h int g_val 0; int main() { pid_t id fork(); if (id 0) { perror(fork); return 0; } else if (id 0) { //child,⼦进程肯定先跑完也就是⼦进程先修改完成之后⽗进程 再读取 g_val 100; printf(child[%d]: %d : %p\n, getpid(), g_val, g_val); } else { //parent sleep(3); printf(parent[%d]: %d : %p\n, getpid(), g_val, g_val); } sleep(1); return 0; }输出结果//与环境相关观察现象即可 child[3046]: 100 : 0x80497e8 parent[3045]: 0 : 0x80497e8我们发现父子进程的地址输出是一样的但是输出结果却不一样。由此我们引入了进程间地址的探索接下来进行内核级进程块式分析。为解决以上问题我们猜想会不会存在多张虚拟内存表来存储不同进程内的数据呢如果是真的就实现了不同进程间数据管理的独立性。1.1.2页表探索虚拟内存必然要涉及对于物理内存的照应问题因此诞生了“页表”这一概念。功能作为中间组件实现物理地址到虚拟地址的映射。 因此可以猜想内部存在映射关系。定义页表作为操作系统内的一个核心数据结构用于实现物理地址到虚拟地址的映射它是现代操作系统内存管理的基石。简单页表结构理解1.2单进程分析在进程的知识中我们已经知道每个进程就是一个PCB进程控制块结构体维护。那么对应于虚拟地址表就是以下图示1.2.1进程-页表-磁盘联系☆☆☆规定一块PCB维护一张虚拟内存表内存内 存储相应数据虚拟内存表之间与物理内存表之间通过中间组件页表联系。 // 页表不是结构体据此诞生PCB---页表---物理内存结构示意图作为C程序员必须刻在骨子里的图片1.3父子进程分析//与环境相关观察现象即可 child[3046]: 100 : 0x80497e8 parent[3045]: 0 : 0x80497e8同一个虚拟地址映射两个不同的值1.3.1内核代码引用内核中的源代码相关 ps无需具体看懂看明白逻辑即可// 简化的逻辑 //分配新页表 new_mm-pgd pgd_alloc(); // 分配新页表 pgd_copy(new_mm-pgd, old_mm-pgd); // 复制父进程页表内容 //写时复制伪代码 page-_mapcount; // 增加引用计数 // SetPagePrivate(page); // 标记为 COW 页面查看源代码可知子进程在基于父进程创建时完完全全的进行了浅拷贝——即子进程在未发生“写”等改变原数据的前提下子进程PCB维护的表与父进程一致一旦发生“写”等改变原数据的的操作子进程独立创建新的虚拟表来维护。☆☆☆1.3.2父子进程与物理内存的对应关系父进程定义变量g_val100; 对应地址0x112233看图。为清晰展示进程间发生“写”等操作的区分图中对于子进程额外处理了变量g_val1的操作看图。即真实物理内存被进程虚拟内存表通过页表照应起来 因此我们“1”时就先子进程的新创建表后发生更改体现在物理内存中如上。1.4超硬核解析虚拟内存与物理内存本质前言………………在虚拟内存前必须由PCB入口分析源代码如下 ps看出task_struct内拥有的mm_struct即可struct task_struct { // ... 其他字段PID、状态、调度信息等 struct mm_struct *mm; // 进程的用户空间内存描述符 struct mm_struct *active_mm; // 内核线程借用active_mm // ... };1.4.1mm_struct成员strcut mm_struct就是整个虚拟内存没错这个结构体维护虚拟内存。简单来看内部有区域划分线 像代码区已初始化区、未初始化区等等很多成员用以维护虚拟地址内部成员便于代码操作。struct mm_struct { long code_start; long code_end; long init_start, init_end; long uninit_start, uninit_end; //……………… }1.4.1.2mm_struct内部关键字段mm_struct内部通过建立红黑树mm_rb链表struct vm_area_struct关联 管理 各个进程。Ⅰ通过struct vm_area_struct 链表mmap字段管理所有虚拟内存区域VMAⅡ通过红黑树rm_rb字段加速查找特定虚拟地址对应的VMA图解源码引用struct mm_struct { struct mm_struct { // 1. 虚拟内存区域VMA的管理 struct vm_area_struct *mmap; // VMA 双向链表的头 struct rb_root mm_rb; // VMA 红黑树的根 unsigned long mmap_base; // 内存映射区域的基地址 unsigned long task_size; // 进程虚拟地址空间大小 // 2. 页表相关 pgd_t *pgd; // 指向第一级页表Page Global Directory // 3. 代码、数据、堆、栈的边界 unsigned long start_code, end_code; // 代码段范围 unsigned long start_data, end_data; // 数据段范围 unsigned long start_brk, brk; // 堆的起始和当前结束 unsigned long start_stack; // 栈的起始地址 };1.4.2辨析进程地址空间与虚拟地址空间虚拟地址空间强调的是程序级分配对象没错就是程序级分配对象而进程地址空间强调的是以操作系统内核的视角——即内核给代码分配对应的物理内存物理内存通过页表映射到虚拟内存地址。 ps在实际工作中常常混用但本质对象就是struct mm_struct1.4.2.1一个问题在一个进程下可以同时拥有大量子进程各个子进程都有同样巨大的虚拟内存空间都需要物理内存映射那么物理内存是怎么做到的单个小物理内存却映射如此庞大数量繁杂的虚拟内存表呢答案是物理内存并没有被切分和虚拟内存一样大的块状而是被拆分为很多个小页框Page Frame。每个进程的虚拟页在“被需要时”才会被映射到这些小页框上并且同一个小页框可以被多个进程的虚拟地址映射。总结本文图片是基于知识而呈现出递进关系对Linux进程内核的框架了解有很大的帮助我相信拥有一些进程基础阅读此文是畅通无阻的。图片是精华所在细心打磨每张图片、排版文章框架。(*^▽^*)♬吐血整理求关注♬2.1计算机物理内存物理内存是计算机系统中实际的、可寻址的硬件存储介质通常是DRAM用于临时存放CPU当前正在执行的程序指令和处理的数据。其每个字节都有唯一一个物理地址Physical AddressPA。物理地址也同样的拥有真实的内存地址例如从0x00000000 到0xFFFFFFFF。像我的笔记本16G-0.3G就是实际上分配的物理内存大小。本质硬件资源由操作系统统一管理是所有进程的共享仓库。关机数据清空。