文章目录
- 程序地址空间
- 进程地址空间
- 关于页表
- 早期内存的分配方式
程序地址空间
计算机得物理内存大小是固定的,就是计算机主板内存槽上的实际物理空间,CPU可以直接继续寻址,物理内存的容量是固定的,但是寻址的卡空间取决于CPU地址线的数量。32位系统上,线性地址空间可达4G,那么这4G的内存是如何分配的呢?一般情况下,是以3:1来分配的,用户进程配有3G的空间,而内核独自配有1G的内存。
在C语言的学习期间,大家都学习了这样得空间分布图:
写段代码来测试一下吧
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int val = 10;
int main()
{
pid_t id = fork();
if(id<0){
perror("fork");
return 0;
}
if(id == 0){
//child
printf("child[%d] val = %d &val = %p\n",getpid(),val,&val);
}
else{
//parent
printf("parent[%d] val = %d &val = %p\n",getpid(),val,&val);
}
sleep(1);
return 0;
}
//打印结果:
/*
parent[1297] val = 10 &val = 0x60104c
child[1298] val = 10 &val = 0x60104c
*/
我们发现,输出出来的变量和地址是一模一样的,难道是因为子进程是按照父进程位模板,父子并没有对变量进行任何修改?
那么我们换一个代码版本来看看。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int val = 10;
int main()
{
pid_t id = fork();
if(id<0){
perror("fork");
return 0;
}
if(id == 0){
//child
val = 100;
printf("child[%d] val = %d &val = %p\n",getpid(),val,&val);
}
else{
//parent
sleep(3);//让子进程先运行完
printf("parent[%d] val = %d &val = %p\n",getpid(),val,&val);
}
sleep(1);
return 0;
}
//打印结果
/*
child[1948] val = 100 &val = 0x60104c
parent[1947] val = 10 &val = 0x60104c
*/
什么!一样的地址居然存放着不同的值!这是不可能的。
那么真相就出来了。
- 变量内容不一样,所以父子进程输出的变量绝对不是一个同一个变量。
- 但是显示的地址相同,就说明了该地址绝对不是物理地址。
- 在Linux地址下,这种地址叫做虚拟地址。
- 我们在使用C/C++语言所看到的地址,全部都是虚拟地址,物理地址用户是看不到的,由OS统一管理。
操作系统OS赋值将虚拟地址转化位物理地址
进程地址空间
先前所说的程序的地址空间是不正确的,准确的应该说成进程地址空间,那该如何理解呢?
同一个变量,地址相同,其实是虚拟地址相同,内容不同是因为映射到了不同的物理地址。
关于页表
页表的概念:
- 页表是一个特殊的数据结构,放在内存空间的页表区。
- 每一个进程都有一个页表,PCB表中有指针指向页表。
- 页表用来存放逻辑地址于物理地址的对应关系,是否映射,是否缓存。
- 页表的每一个表项分为两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存的地址
- 当进程访问某个虚拟进程地址时,会去查页表,如果发现对应的数据没有在物理内存上,则会发现缺页异常。
- 缺页异常异常处理过程:将进程需要覆盖的数据从磁盘中拷贝到物理内存时,如果物理内存满了,没有空地方了,那就找一个页覆盖,当如果被覆盖的页曾经被修改过,就需要将此页写回磁盘。
页表的状态 - 如果页表的有效位置为1,那么就说明虚拟地址存储的内存存在物理页中了。
- 如果页表的有效位置为0,那么就说明虚拟地址存储的内存没有存储在物理页中,发生了缺页异常,需要处理这个异常。
页表的工作原理 - 当CPU想访问父进程的val时,会先根据虚拟地址找到虚拟页,根据页表,找出页表val对应的位置,查看该页表是否有效,有效则为1,DRMA换命中,根据物理页中的内容返回。
- 若无效(0),则参数缺页异常,调用内核缺页异常处理程序,内核会选择一个无理由作为牺牲页,将该页的内容刷新到磁盘空间,然后将val映射到物理页上面,然后页表中该表有效位置1,第二位存对应的物理页的地址内容。
- 缺页处理完成后,返回中断前的指令,重新指向,此时缓存命中,执行1.
- 将找到的内容映射到告诉缓存中,CPU从告诉缓存中获取该值,结束。
早期内存的分配方式
在早期的时候,计算机还没有虚拟机制,程序指令所访问的内存地址就是物理地址,所以就要将所有程序都加载到内存中,但是我们实际的物理内存是有限的,那么就会出现一些问题:
- 当多个程序重新运行时,必须保证这些内存用到的内存总量小于计算机实际的物理内存的大小。
- 内存使用效率低,内存空间不足,就需要将其他程序暂时拷贝到硬盘中,然后重新将新的程序装入内存,但是由于大量的数据转入与转出,内存的使用效率会非常低。
- 进程地址空间不隔离,由于空间时直接访问物理内存的,所以每一个进程都可以修改其他进程的内存数据,设置修改内核地址空间的数据,那么可能会导致一些恶意程序可以随意修改别的进程,就会造成一些破坏。
- 程序运行地址的不确定,因为内存地址是随机分配的,所以程序运行的地址也是不正确的。
为此才会引入虚拟地址。用户进程间的地址互补可见,互不影响。