看下面这段代码,思考为什么会出现这种现象?
#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.相同地址中存储的数据不同
谜底:其实这里的地址并不是数据真正的物理地址,而是虚拟地址!!!
这就解释了上面引出案例中的现象是什么原因了:父子进程访问的数据分别位于不同的物理地址上,只不过他们的虚拟地址相同而已,通过映射对应不同的物理地址
为什么操作系统不让进程直接访问物理内存,而是弄了一个虚拟地址,来间接访问物理地址呢?
程序运行时,一般需要一块连续的地址,如果没有虚拟地址,直接使用物理地址,会导致空间利用率降低
无虚拟内存时
此时16M的内存被占用了13M,还剩余3M不连续空间没被利用,还有一个进程需要3M连续空间没法运行
有虚拟内存时
通过虚拟内存管理,每个程序都认为自己有一块16M大小的内存,通过虚拟内存映射来保证最大化程度利用物理内存,以减少物理内存的浪费
在分段式内存管理系统中,进程的虚拟地址由段号和段内偏移量两部分组成。
虚拟地址 = 短号 + 段内偏移量为了完成进程虚拟地址到物理地址的映射,处理器会查找内存中的段表,由段号得到段的首地址,加上段内偏移量,得到实际的物理地址。
物理地址 = 段首地址 + 段内偏移量这个过程也是由处理器的硬件直接完成的,操作系统只需在进程切换时,将进程段表的首地址装入处理器的特定寄存器当中。这个寄存器一般被称作段表地址寄存器。
系统会为每一个进程建立一个映射表
图解
每个进程有一个页表,描述该进程占用的物理页面及逻辑排列顺序
址结构由两部构成,前一部分是页号,后一部分为页内偏移量。
每个进程有一个页表,描述该进程占用的物理页面及逻辑排列顺序
优点:一个程序不必在物理内存上连续存放,实现了将程序运行所需的内存离散分配在物理内存上