引出案例
看下面这段代码,思考为什么会出现这种现象?
#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,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取
g_val = 100;
printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
else
{ //parent
sleep(3);
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.相同地址中存储的数据不同
谜底:其实这里的地址并不是数据真正的物理地址,而是虚拟地址!!!
虚拟内存管理
- 现代操作系统普遍采用虚拟内存管理(Virtual Memory Management) 机制,这需要MMU(Memory Management Unit,内存管理单元) 的支持
- 两个概念:虚拟地址 和 物理地址
- 如果处理器没有MMU,或者有MMU但没有启用, CPU执行单元发出的内存地址将直接传到芯片引脚上,被内存芯片接收,这称为物理地址(Physical Address)
- 如果处理器启用了MMU, CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address,以下简称VA) ,而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址
- 由于有了虚拟内存管理机制,各进程不必担心自己使用的地址范围会不会和别的进程冲突,比如两个进程都使用了相同虚拟地址0x08048000,操作系统可以设置MMU的映射项把它们映射到不同的物理地址,它们通过同样的虚拟地址访问不同的物理页面,就不会冲突了。
- 虚拟内存不具备存储能力,数据依然存储在物理地址中
这就解释了上面引出案例中的现象是什么原因了:父子进程访问的数据分别位于不同的物理地址上,只不过他们的虚拟地址相同而已,通过映射对应不同的物理地址
为什么操作系统不让进程直接访问物理内存,而是弄了一个虚拟地址,来间接访问物理地址呢?
程序运行时,一般需要一块连续的地址,如果没有虚拟地址,直接使用物理地址,会导致空间利用率降低
无虚拟内存时
此时16M的内存被占用了13M,还剩余3M不连续空间没被利用,还有一个进程需要3M连续空间没法运行
有虚拟内存时
通过虚拟内存管理,每个程序都认为自己有一块16M大小的内存,通过虚拟内存映射来保证最大化程度利用物理内存,以减少物理内存的浪费
内存管理方式
分段式
在分段式内存管理系统中,进程的虚拟地址由段号和段内偏移量两部分组成。
- 虚拟地址 = 短号 + 段内偏移量
为了完成进程虚拟地址到物理地址的映射,处理器会查找内存中的段表,由段号得到段的首地址,加上段内偏移量,得到实际的物理地址。
- 物理地址 = 段首地址 + 段内偏移量
这个过程也是由处理器的硬件直接完成的,操作系统只需在进程切换时,将进程段表的首地址装入处理器的特定寄存器当中。这个寄存器一般被称作段表地址寄存器。
系统会为每一个进程建立一个映射表
图解
分页式
- 将程序的虚拟地址空间划分为固定大小的页(page),而物理内存划分为同样大小的页框(page frame)。
- 程序加载时,可将任意一页放人内存中任意一个页框,这些页框不必连续,从而实现了离散分配。
- 该方法需要CPU的硬件支持,来实现虚拟地址和物理地址之间的映射。
- 在页式存储管理方式中地址结构由两部构成,前一部分是页号,后一部分为页内偏移量。
每个进程有一个页表,描述该进程占用的物理页面及逻辑排列顺序
址结构由两部构成,前一部分是页号,后一部分为页内偏移量。
每个进程有一个页表,描述该进程占用的物理页面及逻辑排列顺序
优点:一个程序不必在物理内存上连续存放,实现了将程序运行所需的内存离散分配在物理内存上