November 30, 2017

操作系统学习-分页

概述

基于分页 (paging) 机制,完成线性地址(linear address)到物理地址(physical address)的转换。

分页的功能有两个:

  1. 虚拟内存管理:可以提供超过实际物理内存的访问空间
  2. 页级别的保护机制

页地址转换机制是可选的,由硬件(MMU:Memory Manager Unit)来进行转换,可由程序来配置转换规则,如映射关系、异常(page fault)处理

细节

物理地址 & 线性地址

线性地址也称虚拟地址,当 CR0 寄存器 PG 位为 1 时,指令访问虚拟地址,由 MMU 转换成实际的物理地址。

线性地址和物理地址被分为 4KByte 大小的最小单元,前者称为 Page,后者称为 Frame。

机制的关键在于控制 Page 到 Frame 的映射关系和映射过程:

  • 映射关系:Page 与 Frame 一一对应,但不一定顺序对应
  • 映射过程:Page 不一定时刻有 Frame 与之对应,当访问一个没有 Frame 的页时,产生一个 Page Fault 异常(14)

page 结构体

page 结构体包含两部分内容:

  1. 与 frame 的对应关系: frame 为 4k 内存单元,最多有 1M 个,用 20bit 来表示
  2. 保护/权限相关位

对于 4G 内存,若完全记录 page 与 frame 的对应关系,需要 4MByte 空间( 4 字节的 page 结构共 1M 个)

事实上,一般不会全部要用到 1M 个 page,设计额外的两层指针结构,减少对内存的占用

  1. 页目录 (page directory)
  2. 页表 (page table)

最终形成三级结构:

  1. page directory 中包含 1k 个 page table 对象
  2. page table 中包含 1k 个 page(entry)
  3. page 指向具体的 frame(每个为 4k 内存),并包含管理权限相关设置

映射方法

线性地址(32bit)被分为三部分看待

  1. page table index(10bit): 在 page dir(保存 1024 个 page table)提取 page table
  2. page entry index(10bit): 在 page table(保存 1024 个 page entry)提取 page
  3. page address(12bit): page 中包含实际的 frame 地址,与 page 地址相加,映射成为最终物理地址

CR0, CR2, CR3 寄存器

  • CR0 中 PG 位用于启用禁用分页功能
  • CR2 在 page fault 异常中存储出错的内存地址,以便处理函数加载相应的 frame
  • CR3 保存 system page directory,也称为 PDBR ( page directory base register ) 寄存器

异常

page 异常编号为 14,分为两类异常:

  1. 页存在
  2. 其它错误(主要是权限相关异常)

操作系统的异常处理会加载不存在的页,并重试指令

内存对齐

page 以 4k 为基本大小,因此在分配内存时应以 4k 对齐

流程

  • 正常启动时,系统未使用分页模式( CR0 PG位 禁用),此时访问的是物理内存
  • 操作系统需要开辟物理内存空间来存放分页相关的数据结构,主要包括三部分:
    1. page frames 的对应(占用情况)表: 用 bitset 来表示,最大 1M frame,需要占用 128k 空间
    2. 系统全局页目录(page directory)
    3. 将已经申请的物理内存,按原地址建立虚拟地址的映射关系。这占用物理内存与之前(以及本环节生成 page table)有关!!!
  • 注册 page fault 的异常处理函数,在该函数中应对交换到硬盘的 frame 进行加载
  • 启用分页功能:设置 Page directory 的物理地址到 CR3,并设置 CR0 PG 位启用

代码分析

dc5c38 版本 core/mem.cinclude/system.h 进行分析

结构定义

    // page 中有 20bit 来映射 1K 的 frame
 87 typedef struct page {
 88     uint32_t present : 1;
 89     uint32_t rw      : 1;
 90     uint32_t user    : 1;
 91     uint32_t accessed: 1;
 92     uint32_t dirty   : 1;
 93     uint32_t unused  : 7;
 94     uint32_t frame   : 20;
 95 } page_t;
 96 
 
    // page table 包含 1024 个 page ( entry )
 97 typedef struct page_table {
 98     page_t pages[1024];
 99 } page_table_t;
100 

    // page directory 包含 1024 个 page table
    //
    // 此外在任务切换中需要保存分页相关内容,需要对内存进行拷贝
    // 但切换分页配置后内存映射也将发生变化
    // 保存额外的 page directory 和 1024 个 page table 的物理地址,以方便拷贝操作
101 typedef struct page_directory {
102     page_table_t *tables[1024]; /* 1024 pointers to page tables... */
103     uintptr_t physical_tables[1024]; /* Physical addresses of the tables */
104     uintptr_t physical_address; /* The physical address of physical_tables */
105 } page_directory_t;

内存对齐

 11 /*
 12  * kmalloc() is the kernel's dumb placement allocator
 13  */
    // uintptr_t 为 unsigned long 整形,表示物理地址
    // align 是否按 4k 对齐
    // phys 不为空时,返回对应的物理地址。这里与 return 一样?
 14 uintptr_t
 15 kmalloc_real(                                                                                                                                              
 16         size_t size,
 17         int align,
 18         uintptr_t * phys
 19         ) {
        // 内存对齐
 20     if (align && (placement_pointer & 0xFFFFF000)) {
 21         placement_pointer &= 0xFFFFF000;
 22         placement_pointer += 0x1000;
 23     }
 24     if (phys) {
 25         *phys = placement_pointer;
 26     }
 27     uintptr_t address = placement_pointer;
 28     placement_pointer += size;
 29     return address;
 30 }

代码中 kmalloc* 为不对齐版本, kvmalloc* 为对齐版本。

bitset

    // frames 为 128k 字节的 bitset,表示 frame 是否被占用
    // nframe 为总的 frame 个数(因为是 int 型,所以是总内存除以 4)
 74 uint32_t *frames;
 75 uint32_t nframes;
 76 
 77 #define INDEX_FROM_BIT(b) (b / 0x20)
 78 #define OFFSET_FROM_BIT(b) (b % 0x20)
 79 
 80 static void
 81 set_frame(
 82         uintptr_t frame_addr
 83         ) {
 84     uint32_t frame  = frame_addr / 0x1000;
 85     uint32_t index  = INDEX_FROM_BIT(frame);
 86     uint32_t offset = OFFSET_FROM_BIT(frame);
 87     frames[index] |= (0x1 << offset);
 88 }
 89 
 90 static void
 91 clear_frame(
 92         uintptr_t frame_addr
 93         ) {
 94     uint32_t frame  = frame_addr / 0x1000;
 95     uint32_t index  = INDEX_FROM_BIT(frame);
 96     uint32_t offset = OFFSET_FROM_BIT(frame);
 97     frames[index] &= ~(0x1 << offset);
 98 }
 99 
100 static uint32_t
101 test_frame(
102         uintptr_t frame_addr
103         ) {
104     uint32_t frame  = frame_addr / 0x1000;
105     uint32_t index  = INDEX_FROM_BIT(frame);
106     uint32_t offset = OFFSET_FROM_BIT(frame);
107     return (frames[index] & (0x1 << offset));
108 }
109 
    // 找到第一个未被占用的 frame
110 static uint32_t
111 first_frame() {
112     uint32_t i, j;
113     for (i = 0; i < INDEX_FROM_BIT(nframes); ++i) {
114         if (frames[i] != 0xFFFFFFFF) {
115             for (j = 0; j < 32; ++j) {
116                 uint32_t test_frame = 0x1 << j;
117                 if (!(frames[i] & test_frame)) {
118                     return i * 0x20 + j;
119                 }
120             }
121         }
122     }
123     return -1;
124 }

内存分布

160 void
161 paging_install(uint32_t memsize) {

        // 物理内存开头存放 frame bitset,4G 内存共 128KB,未对齐(不需要对齐?)
162     nframes = memsize  / 4;
163     frames  = (uint32_t *)kmalloc(INDEX_FROM_BIT(nframes));
164     memset(frames, 0, INDEX_FROM_BIT(nframes));

        // 接下来是系统 page directory,初始没有可用的 page table
165     kernel_directory = (page_directory_t *)kvmalloc(sizeof(page_directory_t));
166     memset(kernel_directory, 0, sizeof(page_directory_t));
167     current_directory = kernel_directory;
168 

        // 对于已经占用的 128KB + sizeof( page_directory_t )
        // 建立相同地址的虚拟地址到物理地址的映射关系
        // 
        // 注意!!
        // 因为需要建立新的 page table 来存储,这部分 page table 也需要被上映射
        // while 循环直到生成足够多的 page 来保存 bitset + page directory + page tables
169     uint32_t i = 0;
170     while (i < placement_pointer) {
171         alloc_frame(get_page(i, 1, kernel_directory), 0, 0);
172         i += 0x1000;
173     }
174     isrs_install_handler(14, page_fault);
175     switch_page_directory(kernel_directory);
176 }


189 
190 page_t *
191 get_page(
192         uintptr_t address,
193         int make,
194         page_directory_t * dir
195         ) {

        // 物理地址 / 4k 得到对应的 page index
196     address /= 0x1000;

        // page index / 1024 得到对应的 page table index
197     uint32_t table_index = address / 1024;

        // 若 page table index 未被创建,则创建之(会占用更多的物理空间,增大 placement_pointer)
198     if (dir->tables[table_index]) {
199         return &dir->tables[table_index]->pages[address % 1024];
200     } else if(make) {
201         uint32_t temp;
202         dir->tables[table_index] = (page_table_t *)kvmalloc_p(sizeof(page_table_t), (uintptr_t *)(&temp));
203         memset(dir->tables[table_index], 0, 0x1000);
204         dir->physical_tables[table_index] = temp | 0x7; /* Present, R/w, User */
205         return &dir->tables[table_index]->pages[address % 1024];
206     } else {
207         return 0;
208     }
209 }

问题

page table 和 page directory 都在一个 page 中,寻址时使用物理地址还是虚拟地址?

物理或虚拟寻址的关键在于 CR0.PG 是否启用。

在 toaruos 中,默认的 page directory 和 page table 的线性地址与物理地址一致。设置完成后才打开 CR0.PG 开关。

此后操作 page directory 和 page table 时将使用虚拟地址。