linux之程序地址空间

1.概念回顾

从正文代码到命令行环境变量地址值一次增大。

在栈和堆之间有一大段镂空空间。

常量字符串其实是被存放到代码段中的,也就是被编码成代码的属性存放的。

static属性的局部变量是与全局变量存放在一起的,说白了static变量就是全局变量。

程序地址空间不是内存,其真实名字为进程地址空间或虚拟地址空间,他是一个系统的概念而非语言层的概念。

当在子父进程中对一个进行过写时拷贝的变量取地址,会发现两个进程中的地址相同,说明这里取出来的地址不是内存的真实地址而是虚拟地址(我们能用或见到的所有地址都是虚拟地址)。

2.虚拟地址空间

一个进程中的PCB对应一个虚拟地址空间,虚拟地址空间的宽度(一个变量的储存单位)是一个字节。(32位机器有2^32个地址(也就是4GB),64位机器有2^64个地址,后面都以32位机器为情景)

0~3GB的空间为用户空间,3~4GB的空间为内核空间。用户空间是用户拿着地址就能直接访问的,内核空间不行。

我们创建的变量名在变成二进制文件后就基本转换为地址了。

页表:

我们实际创建变量时,会在真实内存中开辟一块空间且有一个对应真实地址,然后在虚拟空间表中也会创建一个虚拟地址,然后OS就会将这两个地址放在页表中一一对应起来。

子父进程虚拟地址空间一样的原因:

task_struct中有指向其的虚拟地址空间的指针,因此子进程的虚拟地址空间由于拷贝的父进程的task_struct而拥有与父进程一样的虚拟地址空间。因此虽然发生了写实拷贝使子进程对应的真实内存地址发生了改变,但OS并没有改变其的虚拟地址。也就使虽然子父进程使用同一个虚拟地址,但在页表中二者对应的真实内存地址并不相同从而实现二者访问不同的空间。(因为进程具有独立性,因此修改子进程的页表并不会影响其他进程的页表)

两个进程运行同一段代码,一个变量有两个值的原因:

接收变量(假设叫id)的虚拟地址不变,但返回值时其已经处于两个进程中了,由于发生了修改因此会发生写实拷贝从而使两个进程中的id虚拟地址相同但实际二者对应的真实物理空间已经不同了。

3.虚拟地址空间的原理(画大饼)

让所有的进程都以为自己独占4GB的物理空间。不同的进程的虚拟地址空间并不完全相同,因此OS是需要采用先描述,在组织的方式管理所有的虚拟地址空间的。因此虚拟地址空间的本质就是一个结构体:struct mm_struct;(task_struct中存了一个struct mm_struct*的指针)。

4.区域划分(mm_struct的成员)

设计两个long类型变量分别存储说明为一个内存区间的开始和结尾,也就是每个不同的区间划分只需要定好每段区间的开始和结尾(本质就是两个地址)即可。

调整区域的方式就是对对应整形区间变量的值进行加减即可。

页表:

磁盘中的代码和数据拷贝到内存中后会在虚拟地址空间中生成对应数量的虚拟地址,二者在页表中一一对应形成映射关系。可以说页表是将物理空间地址转换为虚拟地址从而给用户使用的一个媒介。

mm_struct的初始化是在程序加载到内存的过程中进行的。

5.页表的意义

1.让内存和数据可以在物理空间中随便加载(既可以无序存储),只需要虚拟地址使用看上去有序即刻。

2.帮助OS自动通过页表来将虚拟地址转换为物理地址。

3.页表还存储着权限,用途就是当进程没有对应权限时,OS可以拒绝该进程的操作从而保护物理内存。(也就是在地址转换的过程中判断该进程的合法性从而保护物理内存)

4.野指针(后面说明不完全正确),野指针的虚拟地址在页表中没有对应的真实地址导致地址转换失败,OS发现后会直接杀死该进程。

cpp 复制代码
//往字符常量去写入会出错
char*str = "hello";*str = 'H';

原因就是页表中存着常量区的权限是只读,往其写入,页表发现矛盾,OS发现错误从而对该进程进行拦截。

6.进程管理和内存管理直接有一张页表隔开使二者的耦合度变低(进程只能管理到mm_struct而真实内存就能管理内存和磁盘)

6.缺页中断

当磁盘要传很多数据时,OS只会传一部分到真实内存中,但会在虚拟地址空间(mm_struct)中会说明真实的大小,当进程通过虚拟地址访问真实地址,发现无真实地址时,OS会判断然后动态加载剩余的数据到真实内存中并形成新的映射关系。(加载新内容时该进程会被中断,加载完后才会切换回该进程)

实际情况中一个新进程加入到调度队列时其实只加载了一个PCB,数据和代码都是通过缺页中断来处理的。

7.补充

挂机就是只保留PCB,物理内存中的数据与代码全部唤出到磁盘的swap空间中。

问题:多个malloc的堆区是离散的,为什么mm_struct的成员对堆区的管理只有一个开始和结尾?

(下图中的stack维护只有一个开始和结尾)

其实mm_struct中还有一个成员mmap(一个struct vm_area_struct*的指针)

每一个vm_area_struct也是存着所有区间的开始和结尾,只是更加的精细,即每个malloc出的区间都是存在这里的,且vm_area_struct的区间不一定连续。

可以总结出:mm_struct存的是每个区间的大致的范围,而vm_area_struct存的是所有精细的小区间。

总结所有进程具有独立性的原因

(1)PCB,虚拟地址,页表,每一个进程的都有自己的一份。

(2)不同的进程使用的物理空间不同。

相关推荐
云空7 小时前
《Three.js 3D实例大全》
开发语言·javascript·3d·three.js
techdashen7 小时前
Go 1.26 新增 `bytes.Buffer.Peek`:只看数据,不移动读取位置
开发语言·后端·golang
C137的本贾尼7 小时前
第七篇:消息队列(MQ)——就是个带存储的异步通信管道
java·开发语言·中间件
从零开始的代码生活_8 小时前
C++ list 原理与实践:双向链表、迭代器与简化实现
开发语言·c++·后端·学习·算法·链表·list
hy.z_7778 小时前
【C语言】11. 深入理解指针1
c语言·开发语言
charlie1145141919 小时前
现代C++工程实践——WeakPtr 第二关:核心骨架与控制块
开发语言·c++
ttwuai9 小时前
Go 后台接口 401/403 排查:JWT 过期、刷新请求和权限码怎么定位
开发语言·golang·状态模式
漫随流水10 小时前
Java——springboot web案例
java·开发语言·spring boot
从此以后自律10 小时前
Java Object 类常用方法全讲解
java·开发语言
XH华10 小时前
C++语言第二章类和对象(下)
开发语言·c++