文章目录
- 程序地址空间
- 地址空间与物理内存
- 什么是程序地址空间
- 管理程序地址空间
- 虚拟地址与物理地址的映射
- 页表的结构及其作用
- 程序地址空间的作用
程序地址空间
我们之前学习内存的时候,有说内存的分布大概是这样的
其中堆由下而上,栈由上而下
除此之外,实际上C/C++的常量字符串处于正文代码区,因此常量字符串是不允许修改的
其次虽然栈是由上而下的,但是数组和结构体的地址是从下而上开辟的,简单说就是a[0]的地址比a[1]的地址低
地址空间与物理内存
我们考虑下面这样的代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{ int id=fork(); int tmp=10; if(id==0)//子进程执行的代码 { tmp=20; while(1) { printf("子进程,tmp: %d,&tmp: %p\n",tmp,&tmp); sleep(1); } } if(id>1)//父进程执行的代码 { while(1) { printf("父进程,tmp: %d,&tmp: %p\n",tmp,&tmp); sleep(1); } } return 0;
}
简单说就是先开一个进程,分别声明一个tmp,再父子进程分别打印tmp的值和地址
这里我们发现很奇怪,父子进程的值不同,因为我们改了,但是为什么地址却相同呢,同一个地址还能存两个不同的值吗
如果按照我们之前所学理解这样肯定是不行的,非要说的话,那也只是这两个值相同,实际存储的地方一定不同
出现这样的现象主要是因为这样的地址信息并非真正的物理地址,而是我们所说的程序地址空间的的地址
什么是程序地址空间
我们刚刚已经知道了,程序显示给我们的地址并非真正的物理地址,其实是虚拟地址,也称为线性地址
直接扯概念就太难受了,我们形象的来解释
操作系统是内存资源的管理者,而每一个进程都想获取操作系统手上的内存资源
但是操作系统又不好提前设定每个进程的内存大小,就只好告诉他们,我目前有32G(全部并有限)的内存,你可以来申请
这里的32G就是全部的物理内存,但是有那么多进程,不可能每个都有32G,那明显不够分的,但是每个进程又以为自己有32G
这就像老板给你画的饼,你好好努力就可以年薪百万,事实上并没有
所以每一个进程都有自己的程序地址空间,都认为他拥有全部的物理地址
管理程序地址空间
我们说管理一定是先描述后组织的
操作系统想要管理这个程序地址空间也一定是基于这个逻辑的
那么本质上来说,程序地址空间也就是一个struct结构体
也就是用一个结构体管理这么一个空间
也就是开辟一整个大空间,确定每一个分区的起止位置,然后分别用指针管理就行
那么大概就是长这个样子的
struct addr_room
{int code_start; // 代码区起始int code_end; // 代码区结束int init_start; // 初始化区起始int init_end; // 初始化区结束int heap_start; // 堆区起始int heap_end; // 堆区结束// ......// 其他属性
};
那么想要对应的分区大一点或者小一点,其实也就是让他们的起止位置进行变化即可
虚拟地址与物理地址的映射
那么我们了解了虚拟地址之后,知道了两个相同的地址变量是有可能的,但似乎还是没有理解到,所有虚拟地址的大小的和明明是大于物理地址的,这是怎么办到的呢
实际上在虚拟地址和物理地址之间并非一一对应的映射关系,而是通过页表的哈希映射关系,而且每一个进程都有一个对应的页表来进行映射,如果你访问非法地址,那么页表层就会阻止你进行访问了
那么问题就容易解释多了
当我们创建子进程之后,原来的子进程和父进程的数据是共享的,不光是数据共享,页表指向的内容也是相同的,当我们进行修改变量值的操作时,操作系统会重新为其开辟一份物理内存,并修改子进程页表映射的后半段内容
也就是说整个访问数据过程分为两步,第一步获取虚拟地址,第二步获取页表中对应虚拟地址的物理地址,这样就能取到对应的值了
而对于这两个进程来说,他们各自有两个页表,两个变量,只是这两个变量名称相同,通过查页表的地址才能获取这两个变量的值,他们的虚拟地址相同,物理地址不同
这也是操作系统写时拷贝的深层次逻辑
页表的结构及其作用
页表还有一个作用,他不仅仅是带了地址的映射关系,还带了每个地址的访问权限,具体起来就是读写权限
这也就能解释,为什么代码区的内存无法修改,常量字符串处于代码区,也无法修改,但是编译通过运行不通过,就是这个原因,因为操作系统不同意,在访问页表的时候,页表就给他拒绝了
这就给系统的安全性加上了一道锁
其次页表还有一个作用就是代替我们程序员进行数据的分类调序,在页表映射的时候,会将不同的数据类型进行划分,让其映射到物理内存中是一种相对有序的状态,这样做也可以节省内存资源嘛
程序地址空间的作用
- 保护物理内存
这一点我们刚刚有进行说明,因为页表可以控制权限,让操作系统识别到非法操作 - 降低操作系统耦合度
引入页表之后,物理内存所需要做的事情就只有提供地方存数据了,具体存什么,怎么存,都不关心,物理内存的分配和进程的管理几乎完全分开 - 保持进程的独立性
这样做每一个进程都认为自己拥有一整块大内存,所有复杂的操作都由操作系统以及页表完成,进程只负责用,物理内存只需要提供地方