首先来看一个现象
父子进程的全局变量的地址是一样的, 但是变量内容不一样! 能得出如下结论:
OS负责将虚拟地址转化为物理地址。
我们在学习C语言的时候想必大家对这张图并不陌生, 这里可以理解为每一个进程都认为自己是独占系统物理内存大小, 进程之间,彼此不知道, 不关心对方的存在, 从而实现一定程度隔离!那么操作系统是如何进行管理的呢? 先描述在组织。所谓的进程虚拟地址空间, 本质是一个内核数据结构(类似PCB)。
区域划分的本质就是使用一个结构体变量, 存储开始和结束即可。
a. 地址本质上就是一个数字, 可以被保存在unsigned long(4字节 32位)
b. 空间范围内的地址, 我们可以随便用, 暂时不需要记录它的地址。
描述linux下进程的地址空间的所有信息的结构体是mm_struct(内存描述符)。每个进程只有一个mm_struct结构,在每个进程的task_struct结构中, 有一个指向该进程的结构。
mm_struct结构是对整个用户空间的描述。每一个进程都会有自己独立的mm_struct,这样每一个进程都会有自己独立的地址空间才能互不干扰。 先来看看有task_struct到mm_struct,进程的地址空间分布的情况。
虚拟内存的管理方案 = struct mm_struct + 页表, 通过页表像物理地址空间进行映射。物理内存的地址是由操作系统进行管理, 用户是看不到这里的, 未初始化的数据 初始化数据和正文代码都是由OS自主完成, 写时拷贝,所以我们刚刚看到的两个进程所对应的地址是一样的其实是虚拟地址, 如果数据只读, 那么子进程就和父进程是共享的, 如果是写入, 则写入时操作系统会另外在物理内存开辟一块地址, 所以物理地址是不一样的。
而对于堆区,栈区堆区是被操作系统动态创建的, 并没有在物理内存中, 而是在虚拟内存中进行区域的扩大和缩小, 而虚拟内存中的堆区和栈区是怎么知道要划分多少区域的大小呢, 其实在进程创建的时候, 进程中的各个区域大小已经被记录下来了, 可以使用test等指令进行查看。
如果一个很大的进程要被加载进来, 从磁盘到内存中是分批加载的, 通过页表的标记为来标记某段代码是否在内存中存在, 另外还有标记为使用来记录执行权限的, 比如code区域为什么是只读取, 其实是页表的rwx标记为标记为只读, 其实对于物理内存并没有权限的概念, 都是由操作系统进行管理的。
那操作系统为什么要搞一套虚拟地址空间呢?
其实本质上是为了保护我们的内存, 让进程管理和内存管理在系统层面进行解耦合了, 让进程以统一的视角看待物理内存, 代码和数据可以加载到物理内存的任意地方。
地址空间本质上是一个struct mm_struct 所有的内容都是OS系统进行自动完成的, 其实只要把进程管理好,地址空间就管理好了。
那为什么全局变量字符串常量具有全局性呢,因为在地址空间中,他们会随着进程一直存在,全局变量的虚拟地址,一直被大家看到,而栈区和堆区都是由操作系统进行动态管理的。