因为我们的学习的内容都是基于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号