因为我们的学习的内容都是基于32位保护模式CPU的工作环境,所以CPU的32根内存寻址线最多只能识别和使用4G大小的物理内存。在今天看来4G的物理内存并不算什么,但对于32位架构的计算机系统来说已经是最大可用的了。我们只需要通过32位架构的计算机体系来学习它内部的工作原理,所以在32位寻址模式对于学习来说很有典型意义。我们为虚拟机分配的物理内存为512M大小,并预计为每一个任务分配4G大小的逻辑内存,也就是说每个任务在执行时可以访问4G大小的内存区域。如果有多个任务同时运行,它们都可以独立的使用4G大小的内存区域。
在上一节我们学习了内存分页机制,每个任务的可用内存状态都存放在页目录和页表当中,事实上,每个任务都可以有独立的页目录和页表,也就是说每个程序都可以使用独立的逻辑内存。我们打算这样来实现:
操作系统内核程序:
可识别并使用全部的物理内存512M。
访问的逻辑地址与物理地址一致。
普通程序:
可拥有4G的逻辑内存。
0 ~ 512M内存与物理内存一致,前16M内存与内核程序共享。
16M ~ 4G内存中为程序分配pcb、栈、页目录所使用的页面。其它页面均不在内存中。
当出现缺失异常时再为程序分配当前所需页面。
缺页异常处理流程如下:
按照上述流程编写缺页异常的具体逻辑。首先来看一下0 ~ 16M物理内存的规划,系统内核程序所使用的物理内存为:[0, 1M);位图mmap所在的固定内存区域为 [0x100000, 0x200000);还要定义一个位图用来记录每一个内存页的使用者,这个位图叫作map_process,它所占内存空间为 [0x200000, 0x600000),其数组大小与MAP大小永远一致;内核页目录和页表是一次性分配的,内核可以使用全部物理内存页,页目录开始于 [0x600000, 0x601000) ,大小为1024个(每个4字节)共4096字节,页表开始于 [0x601000, 0xa01000) ,大小为4M;另外在boot.h中修改了系统内核所使用的栈地址为#define _SEG_KERNEL_DATA_OFFSET 0x1000000也就是16M以下的物理内存均被使用且不可以被换出到外存。前16M物理内存的分配图如下:
定义一些宏,用来指定一此固定数值:
//内存总大小以M为单位 #define MEM_SIZE (512) #define MEM_SIZE_LOGIC (4096) //位图状态标识 #define MM_SWAP_TYPE_CAN (1) #define MM_SWAP_TYPE_NO (0) //未使用 #define MM_FREE (0x0 << 0) //已使用 #define MM_USED (0x1 << 0) //动态分配 #define MM_NO_SWAP (0x0 << 1) #define MM_CAN_SWAP (0x1 << 1) //内存页大小4096B #define MM_PAGE_SIZE (4 * 1024) //内存总页数 #define MAP_SIZE (0x20000) //(MEM_SIZE * 1024 * 1024 / MM_PAGE_SIZE) #define MAP_SIZE_LOGIC (0x100000) //(MEM_SIZE_LOGIC * 1024 * 1024 / MM_PAGE_SIZE) //内核程序大小256页 256*4096 = 1MB #define KERNEL_SIZE (0x100) /* * MMAP所在内存地址为 [0x100000, 0x200000) * MAP使用情况内存地址为 [0x200000, 0x600000) * Kernel所在页目录及页表地址 [0x600000, 0xa01000) * 从0x1000000以下为0x1000个内存页 */ #define MMAP_USED_SIZE (0x1000) //map起始地址 #define MMAP (0x100000) //map_process起始地址 #define MMAP_PRO (0x200000)
修改install_alloc中已使用的内存为0x1000个内存页(前16M):
//安装申请内存位图 void install_alloc() { //位图所在的固定内存区域为 [0x100000, 0x200000) mmap = (u8 *) MMAP; for (u32 i = 0; i < MAP_SIZE_LOGIC; i++) { //16M以下均为已使用 if (i < MMAP_USED_SIZE) { //设定内核所占用的1MB内存为已使用 mmap[i] = (MM_USED | MM_NO_SWAP); } //剩下的内存为未使用 else { mmap[i] = (MM_FREE | MM_CAN_SWAP); } } }
初始化内存页的使用者,初始时将所有内存页的使用者设置为0,也就是默认的内核程序。
void install_used_map() { map_process = (u32 *) MMAP_PRO; /* * 初始化map_process,因为map和map_process的大小永远都是一样的所以用MAP_SIZE * map_process所占内存空间为 [0x200000, 0x600000) */ for (u32 i = 0; i < MAP_SIZE_LOGIC; i++) { map_process[i] = 0; } }
定义几个用来存取mmap和map_process状态函数:
u8 mmap_status(u32 page_no) { return mmap[page_no]; } u32 map_process_id(u32 page_no) { return map_process[page_no]; } void set_mmap_status(u32 page_no, u8 status) { mmap[page_no] = status; } void set_map_process_id(u32 page_no, u32 pid) { map_process[page_no] = pid; }
在int.S中处理缺页异常,在处理缺页异常时首先需要弹出因缺页错误而被压栈的内容,程序运行时使用%eax作为变量赋值的临时存放区,这时如果采用popl %eax的方式来处理,当此中断执行完毕iret执行之后%eax的值并没有被还原为原来的值,所以程序不正确。所以要使用popl %edx来使用%edx寄存器来存放缺页错误码:
//页错误 _int_0x0e: //出栈,取得缺页错误码 popl %edx pushal pushfl cli //C函数中参数和变量所用的栈 push %ebp mov %esp, %ebp sub $0x28, %esp movl %edx, (%esp) //调用C函数来处理这个异常 call int_page_error leave sti popfl popal iret
在int_page_error中调用page.c中的page_error函数来处理缺页异常,并传入当前缺页程序号和缺页错误码:
//页错误 void int_page_error(u32 error_code) { set_ds(GDT_INDEX_KERNEL_DS); page_error(pcb_current->pid, error_code); set_ds(0xf); }
缺页处理的过程比较复杂,按照本节开始时画的流程图,来一步步的处理:
//取得页面错误地址 u32 v_cr3 = cr3(); set_cr3(PAGE_DIR); u32 error_addr = cr2(); //页号 u32 page_no = error_addr / 0x1000; //面目录 u32 *page_dir = (u32 *) v_cr3; //页目录索引 u32 page_dir_index = (error_addr >> 22) & 0x3ff; //页表索引 u32 page_table_index = (error_addr >> 12) & 0x3ff; //页表 u32 *tbl = NULL; //如果页表尚未分配则申请一个页表 if ((page_dir[page_dir_index] & 0x1) == 0) { //申请一个普通可用页面 tbl = alloc_page(pid, 1, 0); //如果申请失败 if (tbl == NULL) { //申请一个可用的物理页面 u32 ph_page_no = alloc_page_ph(pid); //如果申请成功 if (ph_page_no != 0) { tbl = (u32 *) (ph_page_no * 0x1000); } //如果申请失败 else { printf("Segmentation fault.\n"); hlt(); } } //如果申请失败 else { printf("Segmentation fault.\n"); hlt(); } //处理新分配的页表所表示的页面均不在内存中 for (int i = 0; i < 1024; i++) { tbl[i] = 6; } //设置页目录 page_dir[page_dir_index] = (u32) tbl | 7; } //如果页表已分配 else { //取得页表地址 tbl = (u32 *) (page_dir[page_dir_index] & 0xfffff000); } //如果此页面并没有被换出,swap状态为0 if ((tbl[page_table_index] >> 9 & 0x1) == 0) { u32 ph_page_no = 0; u32 shared = 0; u32 share_addr = 0; //如果缺页申请失败 if (alloc_page_no(pid, page_no, &ph_page_no, &shared, &share_addr) == 0) { printf("Segmentation fault.\n"); hlt(); } else { u32 address = ph_page_no * 0x1000; if (shared == 1) { //printf("share: %x\n", share_addr); address = share_addr; } tbl[page_table_index] = address | 7; } } //如果此页面被已经换出则要从外存换回此页面到内存 else { u32 sec_no = tbl[page_table_index] >> 12; u32 ph_page_no = 0; /* * 如果换入成功,在执行page_swap_in时, * tbl[page_table_index]的前20位存放的是此页被换出到 * 外存的逻辑扇区号sec_no * 在读取外存扇区时sec_no要乘以8,因为是4k对齐, * 8个512B的扇区大小为4096B=4K */ if (page_swap_in(page_no, sec_no, pid, &ph_page_no) == 1) { /* * 这里的一条赋值语句有以下3个作用: * 1.修改页表前20位为线性地址 * 2.修改swap位为0,表示没有被换入 * 3.修改后3位为在内存中,并可读写 * 这里需要修改: * 对于物理内存内的地址按物理地址分配, * 对于大于物理内存的地址按alloc_page申请的地址来分配 */ u32 address = ph_page_no * 0x1000; tbl[page_table_index] = address | 7; } else { printf("Segmentation fault.\n"); hlt(); } } //恢复cr3 set_cr3(v_cr3);
上面就是缺页申请的工作流程,其中使用了几个重要的函数:
缺页申请:
/* * 向内核申请一个指定页号的页面,如果些页在MMAP中为“可用”则可申请, * 如果为“已使用”再判断此页是否可以被换出 * 如果此页不可以被换出,则返回0,如果可以被换出则尝试换出, * 如果换出失败则返回0,如果换出成功则返回1 */ int alloc_page_no(u32 pid, u32 page_no, u32 *page_no_ret, u32 *shared, u32 *share_addr) { *shared = 0; *share_addr = 0; *page_no_ret = page_no; //取得此页的状态 u8 status = mmap_status(page_no); //如果第0位为0,说明页面未使用 if ((status & 0x1) == 0) { //设置此页为“已使用”、“可换出” set_mmap_status(page_no, MM_USED | MM_CAN_SWAP); //设置此页的使用者id,即pid set_map_process_id(page_no, pid); //如果超出物理页面数 if (page_no >= MAP_SIZE) { //申请一个可用的物理页面号 u32 ph_page_no = alloc_page_ph(pid); if (ph_page_no == 0) { return 0; } *page_no_ret = ph_page_no; } //返回成功 return 1; } //如果第0位为1,说明页面已使用 else { //尝试将此页面换出到外存 if (page_swap_out(page_no) == 0) { //如果页面共享失败 if (page_share(page_no, share_addr) == 0) { return 0; } else { *shared = 1; return 1; } } else { //设置此页为“已使用”、“可换出” set_mmap_status(page_no, MM_USED | MM_CAN_SWAP); //设置此页的使用者id为0 set_map_process_id(page_no, pid); //如果超出物理页面数 if (page_no >= MAP_SIZE) { //申请一个可用的物理页面号 u32 ph_page_no = alloc_page_ph(pid); if (ph_page_no == 0) { return 0; } *page_no_ret = ph_page_no; } return 1; } } }
换出页面:
/* * 换出页面,向交换分区申请一个可用外存页面号 * 将当前页面数据写入交换分区 */ int page_swap_out(u32 page_no) { //取得此页的状态 u8 status = mmap_status(page_no); //如果未使用 if ((status & 0x1) == 0) { //直接返回成功 return 1; } //如果页面不可以换出 if (((status >> 1) & 0x1) == 0) { //返回失败 return 0; } //如果页面可以换出 else { //向外存申请8个扇区用来存放一个4k的页面 u32 sec_no = swap_alloc_sec(); //如果申请扇区失败 if (sec_no == 0xffffffff) { //返回失败 return 0; } else { //取得正在使用这一页面的pid_used u32 pid_used = map_process_id(page_no); //取得页目录及页表,为换出做准备 u32 *page_dir = pcb_page_dir(pid_used); u32 d_ind = page_no / 1024; u32 t_ind = page_no % 1024; u32 *tbl = (u32 *) (page_dir[d_ind] & 0xfffff000); //取得页面地址4k对齐 void* page_data = (void *) (tbl[t_ind] & 0xfffff000); //将页面数据写入外存 swap_write_page(sec_no, page_data); /* * 设置原任务的此页面失效 * 将页表中的地址域放入逻辑扇区号 * 将swap状态位置为1表示此页面被换出到外存 */ tbl[t_ind] = 6; tbl[t_ind] |= (0x1 << 9); tbl[t_ind] |= sec_no << 12; //设置此页为“未使用”、“可换出” set_mmap_status(page_no, MM_FREE | MM_CAN_SWAP); //设置此页的使用者id为0 set_map_process_id(page_no, 0); //如果超出物理页面数 if (page_no >= MAP_SIZE) { //计算物理页面号 u32 ph_page_no = (u32) page_data / 0x1000; //设置此页为“未使用”、“可换出” set_mmap_status(ph_page_no, MM_FREE | MM_CAN_SWAP); //设置此页的使用者id为0 set_map_process_id(ph_page_no, 0); } //返回成功 return 1; } } }
换回页面:
/* * 换回页面,将交换分区的数据换回至内存 */ int page_swap_in(u32 page_no, u32 sec_no, u32 pid, u32 *page_no_ret) { *page_no_ret = page_no; void* page_data = (void *) (page_no * 0x1000); //取得此页的状态 u8 status = mmap_status(page_no); //如果页面不可以换出 if (((status >> 1) & 0x1) == 0) { //返回失败 return 0; } //如果第0位为0,说明页面未使用 if ((status & 0x1) == 0) { //如果超出物理页面数 if (page_no >= MAP_SIZE) { //申请一个可用的物理页面号 u32 ph_page_no = alloc_page_ph(pid); if (ph_page_no == 0) { return 0; } *page_no_ret = ph_page_no; page_data = (void *) (ph_page_no * 0x1000); } //将页面数据从外存读入到内存 swap_read_page(sec_no, page_data); //释放扇区 swap_free_sec(sec_no); //返回成功 return 1; } //如果第0位为0,说明页面已使用 if ((status & 0x1) == 1) { //如果换出失败 if (page_swap_out(page_no) == 1) { //如果超出物理页面数 if (page_no >= MAP_SIZE) { //申请一个可用的物理页面号 u32 ph_page_no = alloc_page_ph(pid); if (ph_page_no == 0) { return 0; } *page_no_ret = ph_page_no; page_data = (void *) (ph_page_no * 0x1000); } //将页面数据从外存读入到内存 swap_read_page(sec_no, page_data); //释放扇区 swap_free_sec(sec_no); //设置此页为“已使用”、“可换出” set_mmap_status(page_no, MM_USED | MM_CAN_SWAP); //设置此页的使用者id,即pid set_map_process_id(page_no, pid); //返回成功 return 1; } } return 0; }
共享页面:
/* * 共享内存页,共享时只读,不能写入,以避免出现内存数据被其它程序破坏 */ int page_share(u32 page_no, u32 *share_addr) { //取得正在使用这一页面的pid_used u32 pid_used = map_process_id(page_no); //取得页目录及页表,为换出做准备 u32 *page_dir = pcb_page_dir(pid_used); u32 d_ind = page_no / 1024; u32 t_ind = page_no % 1024; u32 *tbl = (u32 *) (page_dir[d_ind] & 0xfffff000); //取得页面地址4k对齐 *share_addr = (u32) (tbl[t_ind] & 0xfffff000); return 1; }
下面还有一个非常重要的函数,叫alloc_page_ph。它的功能是申请一个物理内存页。它与alloc_page的区别是:alloc_page只查找逻辑内存页找到free的页面将其地址返回给程序,并且可以一次申请多个连续页面,而 alloc_page_ph一次只能申请一个页面,但这个页面是一个物理页面,它存在于真实的物理内存中,如果物理内存中已经没有了可以使用的free页面,它会找到一个允许被换出的页面并调用page_swap_out函数,将这个页面换出,再将这一页的身手物理内存页号返回给程序:
/* * 申请物理内存页 */ u32 alloc_page_ph(u32 pid) { u32 ret = 0; //从未被分配内存页的地方开始查找 for (u32 i = MMAP_USED_SIZE; i < MAP_SIZE && ret == 0; i++) { //如果找到空闲页 if ((mmap[find_index] & 0x3) == (MM_FREE | MM_CAN_SWAP)) { set_mmap_status(find_index, MM_USED | MM_CAN_SWAP); set_map_process_id(find_index, pid); ret = find_index; } find_index++; if (find_index >= MAP_SIZE) { find_index = MMAP_USED_SIZE; } } //如果没有free页面,则再找可换出页面 for (u32 i = MMAP_USED_SIZE; i < MAP_SIZE && ret == 0; i++) { //如果找到可换出页面 if ((mmap[find_index] & 0x3) == (MM_USED | MM_CAN_SWAP)) { //将此物理页面换出 if (page_swap_out(find_index) == 0) { return 0; } set_mmap_status(find_index, MM_USED | MM_CAN_SWAP); set_map_process_id(find_index, pid); ret = find_index; } find_index++; if (find_index >= MAP_SIZE) { find_index = MMAP_USED_SIZE; } } return ret; }
另外,交换分区的实现方式为磁盘的读写,我们为虚拟机分配一个20G大小的磁盘,实际上我们只会用到前4G再多0x100个扇区的空间,将磁盘设置为IDE Primary Master,将光驱设置为IDE Primary Slave如下图:
安装交换分区,此时尚未真正的处理磁盘设备,这里只是使用了一个4G大小的物理磁盘,将4G的物理磁盘作为swap交换分区,因为页表中只有高20位可以存放地址数据,也就是为什么在分页时要对内存做4K对齐,当页面被换出到交换分区之后,页表中的高20位转变为存放20位的磁盘扇区号,系统启动时,初始化交换分区,将交换分区的map初始化为0:
//安装交换分区 void install_swap() { u8 bitmap[0x200]; for (int i = 0; i < 0x200; i++) { bitmap[i] = 0; } //map大小为: 4G / 4096 / 8 = 0x20000 u32 bitmap_size = 0x20000; for (u32 j = 0; j < bitmap_size / 0x200; j++) { hd_rw(j, HD_WRITE, bitmap); } }
向交换分区申请一个可用的磁盘号:
u32 swap_alloc_sec() { u8 bitmap[0x200]; //map大小为: 4G / 4096 / 8 = 0x20000 u32 bitmap_size = 0x20000; for (u32 j = 0; j < bitmap_size / 0x200; j++) { hd_rw(j, HD_READ, bitmap); for (u32 k = 0; k < 0x200; k++) { for (u32 l = 0; l < 8; l++) { if (((bitmap[k] >> l) & 0x1) == 0) { bitmap[k] |= (0x1 << l); hd_rw(j, HD_WRITE, bitmap); u32 sec_no = (j * 0x200 * 8) + k * 8 + l; return sec_no; } } } } return 0xffffffff; }
向交换分区释放一个磁盘号:
void swap_free_sec(u32 sec_no) { u8 bitmap[0x200]; u32 ind = sec_no / 8 / 0x200; hd_rw(ind, HD_READ, bitmap); bitmap[(sec_no % (0x200 * 8)) / 8] &= ~(0x1 << ((sec_no % (0x200 * 8)) % 8)); hd_rw(ind, HD_WRITE, bitmap); }
将内存数据写入交换分区:
void swap_write_page(u32 sec_no, void *page_data) { //计算物理扇区号 sec_no *= 8; //跳过map所在扇区 sec_no += 0x100; //写入8个扇区 for (int i = 0; i < 8; i++) { hd_rw(sec_no + i, HD_WRITE, page_data); page_data += 0x200; } }
从交换分区读入数据到内存:
void swap_read_page(u32 sec_no, void *page_data) { //计算物理扇区号 sec_no *= 8; //跳过map所在扇区 sec_no += 0x100; //读出8个扇区 for (int i = 0; i < 8; i++) { hd_rw(sec_no + i, HD_READ, page_data); page_data += 0x200; } }
另外,交换分区函数都调用了一个void hd_rw(u32 lba, u8 com, void *buf);函数,此函数的功能是读写磁盘扇区。关于它的具体内容和功能我们将在文件系统一章来学习,这里不作太多的解释。读者只需要了解可以调用这个函数来读写磁盘扇区即可。一次为任务申请16个页面,为的是将pbc、可执行代码、栈、页目录和页表内容都存放到一个内存区域,这样在初始化页表时方便为其指定页面号。
int pages = 16; //任务ID号 u32 process_id = 1; //空任务 s_pcb *pcb_empty = NULL; void* mm_pcb = alloc_page(process_id, pages, 0); pcb_empty = (s_pcb *) mm_pcb; init_process(mm_pcb, pcb_empty, process_id, NULL); process_id++; //任务A void* mm_pcb_A = alloc_page(process_id, pages, 0); pcb_A = (s_pcb *) mm_pcb_A; init_process(mm_pcb_A, pcb_A, process_id, &run_A); process_id++; //任务B void* mm_pcb_B = alloc_page(process_id, pages, 0); pcb_B = (s_pcb *) mm_pcb_B; init_process(mm_pcb_B, pcb_B, process_id, &run_B); process_id++; //载入TSS addr_to_gdt(GDT_TYPE_TSS, (u32) &pcb_empty->tss, &gdts[4], GDT_G_BYTE, sizeof(s_tss) * 8); //载入LDT addr_to_gdt(GDT_TYPE_LDT, (u32) pcb_empty->ldt, &gdts[5], GDT_G_BYTE, sizeof(s_gdt) * 2 * 8); //载入tss和ldt load_tss(GDT_INDEX_TSS); load_ldt(GDT_INDEX_LDT);
初始化任务:
/* * create_task : 创建tts任务 * - int type : tts任务类型TASK_TYPE_NOR、TASK_TYPE_SPE * return : void */ void init_process(void *mm_pcb, s_pcb *pcb, u32 process_id, void *run_addr) { //s_tss pcb->tss.back_link = 0; pcb->tss.ss0 = GDT_INDEX_KERNEL_DS; pcb->tss.esp1 = 0; pcb->tss.ss1 = 0; pcb->tss.esp2 = 0; pcb->tss.ss2 = 0; pcb->tss.cr3 = 0; pcb->tss.eflags = 0x3202; pcb->tss.eax = 0; pcb->tss.ecx = 0; pcb->tss.edx = 0; pcb->tss.ebx = 0; pcb->tss.ebp = 0; pcb->tss.esi = 0; pcb->tss.edi = 0; pcb->tss.eip = 0; pcb->tss.esp = 0; pcb->tss.esp0 = 0; pcb->tss.es = USER_DATA_SEL; pcb->tss.cs = USER_CODE_SEL; pcb->tss.ss = USER_DATA_SEL; pcb->tss.ds = USER_DATA_SEL; pcb->tss.fs = USER_DATA_SEL; pcb->tss.gs = USER_DATA_SEL; pcb->tss.ldt = GDT_INDEX_LDT; pcb->tss.trace_bitmap = 0x0; //设置多任务的gdt描述符 addr_to_gdt(LDT_TYPE_CS, 0, &(pcb->ldt[0]), GDT_G_KB, 0xfffff); addr_to_gdt(LDT_TYPE_DS, 0, &(pcb->ldt[1]), GDT_G_KB, 0xfffff); //设置pcb相关值 pcb->pid = process_id; pcb->tss.eip = (u32) mm_pcb + 0x1000; pcb->tss.esp = (u32) mm_pcb + 0x2000; pcb->tss.esp0 = (u32) mm_pcb + 0x3000; pcb->tss.cr3 = (u32) mm_pcb + 0x4000; pcb->swap = NULL; //copy可执代码到eip位置 mmcopy(run_addr, (void *) (pcb->tss.eip), 0x1000); //页目录 u32 *page_dir = (u32 *) (pcb->tss.cr3); //页表 u32 *page_tbl = (u32 *) ((u32) mm_pcb + 0x5000); //地址 u32 address = 0; /* * 前16M系统内存为已使用 * 实际上16M系统内存是不应该让普通程序可写的 * 但为了能让普通程序直接操作0xb8000显示缓冲区 */ for (int i = 0; i < 4; i++) { for (int j = 0; j < 1024; j++) { page_tbl[j] = address | 7; address += 0x1000; } page_dir[i] = ((u32) page_tbl | 7); page_tbl += 1024; } //mm_pcb所在内存 address = (u32) mm_pcb; //mm_pcb所在内存页目录索引 u32 page_dir_index = (address >> 22) & 0x3ff; //mm_pcb所在内存页表索引 u32 page_table_index = (address >> 12) & 0x3ff; //mm_pcb所在内存页表 page_tbl = (u32 *) alloc_page(process_id, 1, 0); for (int i = 0; i < 1024; i++) { //设置mm_pcb所在内存的页表 if (i >= page_table_index && i <= (page_table_index + 16)) { page_tbl[i] = address | 7; address += 0x1000; } else { page_tbl[i] = 6; } } //设置mm_pcb所在内存的页目录 page_dir[page_dir_index] = ((u32) page_tbl | 7); //设置16个页面剩余页 if (page_table_index + 16 >= 1024) { page_tbl = (u32 *) alloc_page(process_id, 1, 0); for (int i = 0; i < 1024; i++) { if (i < (10 - (1024 - page_table_index))) { page_tbl[i] = address | 7; address += 0x1000; } else { page_tbl[i] = 6; } } page_dir[page_dir_index + 1] = ((u32) page_tbl | 7); } }
最后修改任务B的代码,让其访问0 ~ 4G的内存地址:
void run_B() { char *ps = (char *) 0; char ch; while ((u32) ps <= 0xFFFFFFFF) { ch = *ps; ps += 0x1000; } char *p = (char *) 0xb8000; p += ((23 * 80 + 76)) * 2; int i = 99; while (1) { *p = i; if (++i >= 127) { i = 33; } } }
在Makefile中加入处理交换分区功能函数的hd.c、swap.c两个文件,编译运行并查看结果:
可以看到0 ~ 0x20000000(512M)内存的访问速度非常快,因为访问的实际物理内存,从0x20000000开始之后内存访问的速度变的很慢,原因是使用了交换分区一直在做内存与外存的数据交换,由于外存的读写速度远远小于内存,所以才会这样慢。同时也可以看到在访问512M以上内存时虚拟机的磁盘一直处理工作状态,也就是说我们的交换分区正在工作:
源代码的下载地址为:
https https://github.com/magicworldos/lidqos.git git git@github.com:magicworldos/lidqos.git subverion https://github.com/magicworldos/lidqos branch v0.16
Copyright © 2015-2023 问渠网 辽ICP备15013245号