进程地址空间
-
对于一个进程空间分布图如下:
-
引子:猜猜下面输出结果,为什么呢?
#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
printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
}else{ //parent
printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
sleep(1);
return 0;
}
输出结果:
parent[2995]: 0 : 0x80497d8
child[2996]: 0 : 0x80497d8
由上可以发现,父子进程变量值和地址一模一样,因为子进程是以父进程为模版,并且父子进程都没有对变量进行修改
修改一下代码(如下),看看结果
```
#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
printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
}else{ //parent
printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
sleep(1);
return 0;
}
输出结果:
child[3046]: 100 : 0x80497e8
parent[3045]: 0 : 0x80497e8
我们发现,父子进程,输出地址是一致的,但是变量内容不一样!
```
-
结合上面两个例子我们可以总结出以下结论:
1. 变量内容不一样,所以⽗子进程输出的变量绝对不是同⼀个变量
2. 但地址值是⼀样的,则该地址绝对不是物理地址
3. 在Linux地址下,这种地址叫做虚拟地址
4. 我们在⽤C/C++语⾔言所看到的地址,全部都是虚拟地址,物理地址,⽤户一概看不到,由OS统⼀管理 -
早期内存管理原理:
- 要运⾏一个程序,会把这些程序全都装⼊内存
- 当计算机同时运⾏多个程序时,必须保证这些程序用到的内存总量要⼩于计算机实际物理内存的⼤⼩问题:
- 进程地址空间不隔离。由于程序都是直接访问物理内存,所以恶意程序可以随意修改别的进程的内存数据,以达到破坏的目的
- 内存使⽤效率低。在 A 和 B 都运⾏的情况下,如果⽤户⼜运⾏了程序 C ,⽽程序 C 需要 15M ⼤ ⼩的内存才能运⾏,而此时系统只剩下 4M 的空间可供使⽤,所以此时系统必须在已运⾏的程序中 选择一个将该程序的数据暂时拷⻉到硬盘上,释放出部分空间来供程序 C 使⽤,然后再将程序 C 的数据全部装入内存中运⾏
- 程序运⾏的地址不确定。当内存中的剩余空间可以满足程序 C 的要求后,操作系统会在剩余空间中随机分配一段连续的 20M ⼤⼩的空间给程序 C 使⽤,因为是随机分配的,所以程序运⾏的地址是不确定的,这种情况下,程序的起始地址都是物理地址,⽽而物理地址都是在加载之后才能确定。
由于以上机制存在问题,于是后来使用分段来解决这些问题
-
分段
- 在编写代码的时候,只要指明了所属段,代码段和数据段中出现的所有的地址,都是从0零开始,映射关系完全由操作系统维护
- CPU将内存分割成了不同的段,于是指令和数据的有效地址并不是真正的物理地址⽽是相对于段⾸地址的偏移地址
解决问题:
- 因为段寄存器的存在,使得进程的地址空间得以隔离,越界问题很容易被判定出来
- 实际代码和数据中的地址,都是偏移量,所以第一条指令可以从0地址开始,系统会⾃动进⾏转化映射,也就解决了程序运⾏的地址不确定的问题。
- 可是,分段并没有解决性能问题,在内存空间不⾜的情况下,依旧要换⼊唤出整个程序或者整个段,⽆疑要造成内存和硬盘之间拷⻉⼤量数据的情况,进⽽导致性能问题。
分段仍然存在一些问题,于是引进了分页和虚拟地址概念
-
分页&虚拟地址空间
页表:实现从页号到物理块号的地址映射
虚拟内存基本思想:每个进程有用独立的逻辑地址空间,内存被分为大小相等的多个块,称为页(Page).每个页都是一段连续的地址。对于进程来看,逻辑上貌似有很多内存空间,其中一部分对应物理内存上的一块(称为页框,通常页和页框大小相等),还有一些没加载在内存中的对应在硬盘上
创建进程,虚拟地址和物理地址之间的映射关系
上面的图说明:同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!过程:当访问虚拟内存时,会访问MMU(内存管理单元)去匹配对应的物理地址,而如果虚拟内存的页并不存在于物理内存中,会产生缺页中断,从磁盘中取得缺的页放入内存,如果内存已满,还会根据某种算法将磁盘中的页换出。(MMU中存储页表,用来匹配虚拟内存和物理内存)
二级页表:因为页表中每个条目是4字节,现在的32位操作系统虚拟地址空间是
2^32
次方,假设每页分为4k,也需(2^32/(4*2^10))*4=4M
的空间,为每个进程建立一个4M的页表并不明智。因此在页表的概念上进行推广,产生二级页表,虽然页表条目没有减少,但内存中可以仅仅存放需要使用的二级页表和一级页表,大大减少了内存的使用。缺页中断:虚拟内存的页并不存在于物理内存中,会产生缺页中断。处理中断:地址转换➕更新(地址转换:有空闲块儿,调入页面,没有利用置换算法替换出去一个;更新页表,重启该命令,检索页表,命中物理块儿,运算得出物理地址)