操作系统之位图代码分析

位图:综合起来,位图就是用字节中的 1 位来映射其他单位大小(默认一页4K)的资源,按位与资源之间是一对一的对应关系。
那么一字节八位对应32K,40000*32K=
本质上是压缩存储来方便管理
在书中

struct bitmap {
   uint32_t btmp_bytes_len;
/* 在遍历位图时,整体上以字节为单位,细节上是以位为单位,所以此处位图的指针必须是单字节 */
   uint8_t* bits;
};

后续的内存管理中多处用到了位图
管理虚拟地址用到了位图:memory.h

/* 用于虚拟地址管理 */
struct virtual_addr {
/* 虚拟地址用到的位图结构,用于记录哪些虚拟地址被占用了。以页为单位。*/
   struct bitmap vaddr_bitmap;
/* 管理的虚拟地址 */
   uint32_t vaddr_start;
};
struct virtual_addr kernel_vaddr;	 // 此结构是用来给内核分配虚拟地址

用内存池pool管理物理地址,比虚拟地址复杂,分为kernel和user两个pool:memory.c

/* 内存池结构,生成两个实例用于管理内核内存池和用户内存池 */
struct pool {
   struct bitmap pool_bitmap;	 // 本内存池用到的位图结构,用于管理物理内存
   uint32_t phy_addr_start;	 // 本内存池所管理物理内存的起始地址
   uint32_t pool_size;		 // 本内存池字节容量
};

struct pool kernel_pool, user_pool;      // 生成内核内存池和用户内存池

32位虚拟地址的转换过程
1.高10位,页目录项 pde 的索引
2.中间 10 位是页表项 pte 的索引,用于在页表中定位 pte 。
3.剩下12位是偏移

页表:
之前在loader.S中建立了内核的页目标表和页表。

页表也有两类,内核页表和进程页表:每个进程都有自己的页表,同时内核也有自己的页表。进程页表中映射的线性地址包括两部分:用户态和内核态。其中内核态地址对应的表项来源自内核页表。

分页机制打开前要将页表地址加载到控制寄存器 cr3 中,这是启用分页机制的先决条件之一。
页表本身属于线性表结构,相当于页表项数组,访问其中任意页表项成员,只要知道该表页项的索引(下标)就够了。

CPU转换线性地址到物理地址的过程:
一级页表转换机制描述:
某进程A的页表本身存在内存的某个地方中(由cr3寄存器给出物理地址)
假设现在要寻找虚拟地址0x1234(0000 0000 0000 0000 0001 0010 0011 0100)的物理地址
分析一下这个虚拟地址,前20位为1。0x234是偏移
cr3寄存器给的物理地址是0x2000。所以读取物理地址0x2004(=2000+1*4)处的内容0x9000。
物理地址0x9234(=0x9000 + 0x234)正是虚拟地址0x1234的转换。
页表中存放的也始终是物理页面地址。

从一级页表到二级页表

如果是一级页表,就会有1M个页表项,一个页表项代表4K大小内容。总共1M4K=4G
自身占用空间:1M
4字节=4MB
注意每个进程都有自己的页表。进程一多,页表占空间就很大。

所以不要一次性地将全部页表项建好,需要时动态创建页表项 。
1M=1K*1K。如果页表项是四字节,那么一个页表是4KB。
所以一个页目录表,占4K大小。
页目录表中存储每个页表的物理页地址。

一千个二级页表,每个占4K
页表中存放物理页地址。

页目录表由寄存器cr3给出物理地址。经页目录表转换成页表的物理地址。再搜索

以上只叙述了CPU如何转换地址,在内核编写中,我们要去构造页表,势必要去访问页表项,并且改变页表项的值。
我们可以直接通过CPU转换虚拟地址访问其对应的物理地址,但是如果我们要去访问页表项和页目录项,对其进行修改呢?这就需要两个函数,pte_ptr,pde_ptr,返回能访问到vaddr所在pte和pde的新虚拟地址new_vaddr(这两个函数是根据虚拟地址转换的规则来得到)
有了new_addr地址,就可以修改对应的页目录项和页表项,所以这里两个函数是修改页表的核心。
当进程向操作系统申请新地址时,需要在页面中添加虚拟地址_vaddr与物理地址的映射,就需要这两个函数。

你可能感兴趣的:(操作系统之位图代码分析)