Linux 进程地址空间(32位为例)进程地址空间是操作系统为每个运行中的进程分配的专属虚拟内存疆域是一个结构体对象。如同为进程构建的“独立王国”1️⃣疆域范围从低地址如0x00400000到高地址如0xFFFFFFFF覆盖完整的虚拟地址范围。2️⃣分区治理不同内存区域承担特定功能严格隔离如图中所见分区结构。3️⃣虚实映射看似连续的“领土”实际由操作系统通过页表MMU硬件动态映射到物理内存或磁盘。命令行参数与环境变量在栈区之上在Window系统中内存的分布并不是这个样子的但是大差不差一.前置知识系统的32位与64位32位与64位指的是CPU一次处理数据的宽度与寻址的范围.32位CPUCPU可以一次处理32bit(4字节)的数据范围以及寻址232byte(4GB)的范围.64位CPUCPU可以一次处理64bit(8字节)的数据范围以及寻址264byte(18.4亿GB)的范围.二.地址空间划分的基本验证可以观察到各个变量的地址的值可以发现它们的大小是遵从上面的地址空间划分图的三.地址空间中栈区、堆区的增长验证可以发现随着堆上内存的申请堆的地址是向上增长的随着栈上变量的创建变量的地址是减小的四.程序中的虚拟地址我们可以发现当子程序修改全局变量的时候父进程对于同一地址的全局变量的值是没有变化的其实也就是说父进程与子进程的全局变量的地址是假的不是真实的物理地址其实就是虚拟地址那么进程如何通过虚拟的地址找到物理地址的呢下面讲解五.页表知识补充虚拟内存≠实际内存虚拟地址空间32 位系统中进程认为有0x00000000~0xFFFFFFFF4GB地址可用.物理内存计算机实际安装的物理内存可能只有 1GB、2GB远小于 4GB.操作系统通过虚拟内存机制页表映射 换页让进程误以为自己独占整个 4GB 空间.对上总的来说在32位系统中每一个进程都有4G的进程地址空间是系统给进程画的大饼实际内存的申请是否合理由计算机判定在Linux内核中task_struct是描述一个进程或线程的核心数据结构而其中的mm_struct *mm成员是一个指向进程内存管理描述符的指针.其中 mm_struct 是用来管理用户内存空间分布的结构体其中包含了虚拟内存空间、页表、内存映射等信息下面我们展示一张图具体说明页表的作用.我们可以发现我们的进程中的数据都有一份虚拟地址进程可以通过这一份虚拟地址去页表中查找找到对应的数据存放的物理地址.其次子进程被父进程创建时子进程会复制一份父进程的页表也就是说子进程中的变量与父进程指向的是同一的物理地址.下面解释一下4.程序中的虚拟地址中的父子进程同一个g_val有两个值的原因.#includestdio.h#includeunistd.hintglobol_value100;intmain(){pid_tidfork();intcount5;if(id0){//子进程while(1){printf(I am child,pid %d,ppid %d,global %p,global_value %d\n,getpid(),getppid(),globol_value,globol_value);if(count0)globol_value200;elsecount--;sleep(1);}}elseif(id0){//父进程while(1){printf(I am father,pid %d,ppid %d,global %p,global_value %d\n,getpid(),getppid(),globol_value,globol_value);sleep(1);}}return0;}缺页中断写时复制Copy-on-Write, COW当一个进程创建子进程时例如 Linux 中的 fork()为了效率父子进程一开始会共享所有的内存页面但这些页面会被标记为“只读”。如果父进程或子进程中任何一个试图写入这些共享页面就会触发一个缺页中断。此时操作系统会为写入方复制一份该页面的私有副本让它去修改这个副本从而保证了父子进程内存空间的独立性。所以当我们的父进程创建了一个子进程之后我们的子进程就会继承我们父进程的页表此时我们修改子进程数据子进程会发生页表中断重新找到一个新的位置存放新的数据此时在页表中只有物理地址被修改虚拟地址没有被修改所以这就是同一个地址存储了不同的值的原因.题外话页表是在CPU中cr3寄存器中保存的当我们的程序编译之后我们的变量将是不存在的会全部转换为地址(虚拟地址)现代所有的语言都无法看到物理地址六.内核空间是什么用户态User Mode和内核态Kernel Mode是操作系统中进程运行的两种不同权限级别它们的本质区别在于对系统资源的访问权限和执行指令的能力。用户态​ 指进程在执行普通应用程序代码时所处的状态。在用户态下进程只能访问受限的内存空间不能直接操作硬件也不能执行特权指令如I/O操作、管理内存等。用户态的程序如果需要访问硬件或操作系统资源必须通过“系统调用”请求操作系统帮忙完成。内核态​ 指进程在执行操作系统内核代码时所处的状态。在内核态下进程拥有最高权限可以访问所有硬件资源、内存空间并能执行所有CPU指令。操作系统内核本身和驱动程序等都运行在内核态。内核空间存储着内核代码的虚拟地址同时内核空间也会有一个内核空间页表将虚拟的地址转化为物理地址给进程找到内核代码(调用系统接口时)50个进程会有50份页表但是一个操作系统的内核页表只有一份所有的进程通过这个页表转换后都可以找到内核代码去执行比如调用Printf函数当代码读取到printf时进程从用户态转变成内核态通过页表寻找我们的内核代码执行printf函数的代码随后再转换成用户态继续执行原来的代码七、虚拟地址到物理地址的转化我们在上面说过了在进程中我们能看到的都是虚拟地址我们需要通过页表来进行虚拟地址到物理地址的转化在32位系统下地址的大小为32bit(4byte)下面我们看图1. 虚拟地址的构成从图中可以看到32位虚拟地址被分解为三个部分高10位用于索引页目录中间10位用于索引二级页表低12位作为页内偏移量具体分解32 10 10 122. 转换流程详解第一步页目录查找使用虚拟地址的高10位作为索引在页目录中找到对应的页目录表项页目录有1024个条目210每个页目录表项包含一个指向二级页表的起始地址第二步二级页表查找使用虚拟地址的中间10位作为索引在二级页表中找到对应的页表表项每个二级页表也有1024个条目210每个页表表项包含一个指向物理内存中页框起始地址第三步物理地址计算从页表表项中获取页框的起始地址将虚拟地址的低12位偏移量直接加到页框起始地址上得到最终的物理地址3. 具体示例0000 0000 0000 0000 0000 0000 0000 0000转换过程高10位A→ 索引页目录中间10位B→ 索引二级页表低12位C→ 页内偏移量最终物理地址 页框起始地址 偏移量例如0x11223300 偏移量 物理地址4. 关键特点内存效率二级页表大部分情况都是不全的操作系统只为实际使用的虚拟地址空间分配页表节省了大量内存空间覆盖范围每个页目录条目可以映射1MB的虚拟地址空间计算4KB × 1024 1MB页大小每个页框大小为4KB212字节偏移量范围[0, 212-1]11223300 偏移量 物理地址4. 关键特点内存效率二级页表大部分情况都是不全的操作系统只为实际使用的虚拟地址空间分配页表节省了大量内存空间覆盖范围每个页目录条目可以映射1MB的虚拟地址空间计算4KB × 1024 1MB页大小每个页框大小为4KB212字节偏移量范围[0, 212-1]