主要内容
- 格式化Swap设备及挂载
- 实现Swap.c相关函数,给Linux0.11增加更多虚拟地址功能
- 测试程序
一,格式化Swap设备及挂载
1.格式化Swap设备
a. 第一个扇区用作MBR分区表 ,且第一个扇区最后有Magic Number: 55aa,表示分区有效
b. 接下来一页用作bit_map。Swap分区一共32MB,8092页。对应8092位,即1024个字节
c. 该页最后四个字节为签名SWAP
MBR由三部分构成: 1.主引导程序代码,占446字节 2.硬盘分区表DPT,占64字节,最多支持4个分区 3.主引导扇区结束标志AA55H
这里使用新添加的一块硬盘作为swap分区。
这块硬盘总大小64MB,这里只分了一个32MB的分区,作为Swap分区。
从下图的MBR表中可以看出,这一分区开始于0磁头,3扇区,0柱面。82标识Linux Swap分区。结束于15磁头,38扇区,107柱面。共1538107-2=65662。
逻辑上始于第2扇区,总共65662扇区。
0x1fe处还有Magic Number: 0xaa55,表示MBR有效
从0x400开始,也就是第1kb处,即第三个扇区,是bitmap。
可以看出第1位已被标识成0,表示第一页已作它用,不能参与swap。
之后直到0x800都为1,表示可用,正好是1024字节,即8096位,对应8096个页面,也就是32MB的Swap分区。
超出32MB不可用,因此0x800之后到这一页结束,都置为0。
注意在第一页的结尾有一个签名,SWAP,用来标识这个Swap设备。
2.Swap设备挂载到Bochs
在bochsrc.bxrc
中添加
ata0-slave:type=disk,path="$OS_PATH/newdisk.img",mode=flat,cylinders=204, heads=16, spt=38
204*16*38*512=63.5M(注意这里1MB=1000KB,生成.img文件时)
二,Swap.c相关函数
1.Linux初始化时读取Swap设备信息并输出
在hd.c函数里面的setup系统调用可以读取系统挂载的所有硬盘的信息。输出效果如下图:
读取信息并输出结束后,会调用init_swapping函数。
2.Init_Swapping函数
输出所有硬盘的大小及分区后,就要执行swap分区的初始化。即读取swap分区的大小,看其大小是否符合规范。还要从swap分区第一页获得bitmap,并检查其是否合法
bitmap所在页需要满足以下条件:
-
页的最后四个字节为SWAP;
-
bitmap的第一位为0,即不可用(被bitmap占用了);
-
超过swap_size的那些位应为0,即不可用。比如这里swap分区只有32M,对应8092个页,只需要8092位,也就是1024字节去表示。还剩3072字节,应该全置 为0,防止访问到32MB之外。
最后,该函数要统计一下swap分区可用的页数有多少。如果可用的页数为0,则该swap分区已不可用。
注意这里我们采用第二块硬盘的第一个分区作为swap分区,因此SWAP_DEV=0x306
对于硬盘这类块设备,Linux0.11中只实现了ll_rw_block(),即按块读写的函数
对于Swap分区的读写是按页进行的,为了方便我们要先实现一个**ll_rw_page()**函数。即每次访问作为一个request,添加到硬盘队列中去,这个request每次读8个扇区,即4KB
对于swap设备,它的设备号是固定的,因此我们可以用宏专门来进行swap设备的读写,减少参数
- get_swap_page() 扫描bitmap,若找到有一位是1,就把它置为不可用,并返回它的序号,表示找到了这一页free page
- swap_free(int nr) 检查第nr位是否是0,如果是,就置为1并返回。否则报错。
- swap_in() 从swap设备调一页到内存中
- swap_out() 从将内存中的一页移出到swap设备中
Swap out()
该函数在get_free_page()中被调用。原来0.11中,get_free_page()是从16MB物理空间的尾部开始查找空闲的物理页。如果没有空闲的就返回0。但是这里有了swap功能,如果扫描一遍没找到空闲物理页,就要调用swap_out()函数,然后再次申请页面。
int swap_out(void)
{
static int dir_entry = FIRST_VM_PAGE>>10;
static int page_entry = -1;
int counter = VM_PAGES;
int pg_table;
while (counter>0) {
pg_table = pg_dir[dir_entry];
if (pg_table & 1)
break;
counter -= 1024;
dir_entry++;
if (dir_entry >= 1024)
dir_entry = FIRST_VM_PAGE>>10;
}
pg_table &= 0xfffff000;
while (counter-- > 0) {
page_entry++;
if (page_entry >= 1024) {
page_entry = 0;
repeat:
dir_entry++;
if (dir_entry >= 1024)
dir_entry = FIRST_VM_PAGE>>10;
pg_table = pg_dir[dir_entry];
if (!(pg_table&1))
if ((counter -= 1024) > 0)
goto repeat;
else
break;
pg_table &= 0xfffff000;
}
if (try_to_swap_out(page_entry + (unsigned long *) pg_table)){
printk("I'm swapping out at %u\n\r",page_entry + (unsigned long *) pg_table);
return 1;
}
}
printk("Out of swap-memory\n\r");
return 0;
}
查找可被换出的页面过程如下:
从线性空间中的任务1的第一页开始往后找查找页目录,找最低位为1的项->查找页表,找到最低位为1的项-> 尝试换出try_to_swap_out()。若无法换出,继续查找如果成功换出,则将swap_nr左移1位存放到table_ptr中,以备将来使用
Try_to_swap_out():
在swap_out()函数中,找到的只是有可能被换出的页(有对应物理内存的页)。但这些页未必真的能换出,try_to_swap_out()还会做进一步检查
- 再次检查page最低位是否为1
- page大小是否越界
- 如果该页未经修改过,则直接释放,而不用往swap设备上写
- 如果该内存页未被使用(异常),或无法获得swap_nr(swap设备已满),则不能交换
int try_to_swap_out(unsigned long * table_ptr)
{
unsigned long page;
unsigned long swap_nr;
page = *table_ptr;
if (!(PAGE_PRESENT & page)) //page地址最右如果为1,说明是一个有效页表项(有对应的物理内存)。0x01&page
return 0;
if (page - LOW_MEM > PAGING_MEMORY)
return 0;
if (PAGE_DIRTY & page) { //只有当这个页面是被修改过时,才需要被换出。
//如果这个页面没被修改过,说明它肯定是从别处复制过来的,肯定还有副本。
//这时就不必换出,直接使用即可。
page &= 0xfffff000;
if (mem_map[MAP_NR(page)] != 1)
return 0;
if (!(swap_nr = get_swap_page()))
return 0;
*table_ptr = swap_nr<<1; //swap_nr左移一位,使最右位为0。这样table_ptr最低位也为0,
//系统就会知道这一页表项是没有对应物理内存的。
//*table_ptr中储存的是原来那页在swap分区中的编号*2。
invalidate();
write_swap_page(swap_nr, (char *) page);
free_page(page);
return 1;
}
*table_ptr = 0;
invalidate();
free_page(page);
return 1;
}
Swap_in():
在发生缺页中断时被调用。Swap_out()时已将该页在swap设备上的页码储存在table_ptr中。通过它可以获得swap_nr,通过read_swap_page()将其读回内存。读回后,要修改对应的bitmap位。还要修改table_ptr成新的物理内存地址,并将标志位置为已修改过。
三,测试程序
不断malloc申请内存,并往内存中写入任意内容(如果不写的话,由于写时复制机制,系统不会真的分配物理内存)
在原始的Linux0.11系统上,申请2960页内存会发生oom错误
添加Swap后,运行效果如下:申请了2960页,超出16MB,发生换出操作,在执行换出操作时打印了待换出的物理内存地址
一次换入过多,会发生奇怪的错误
四,目录
├── bochs Bochs模拟器相关文件
│ ├── BIOS-bochs-latest Bochs BIOS镜像文件
│ ├── bochs-dbg Bochs调试版本
│ ├── bochs-gdb 支持GDB调试的Bochs版本
│ ├── bochsrc-gdb.bxrc GDB调试用的Bochs配置文件
│ ├── bochsrc.bxrc 默认Bochs配置文件
│ ├── bochsrc.bxrc~ Bochs配置文件备份
│ └── vgabios.bin VGA BIOS文件
├── bochsout.txt Bochs运行输出日志
├── 操作系统内核竞赛文档.pdf 竞赛文档
├── dbg-asm 汇编调试文件
├── dbg-c C语言调试文件
├── gdb GDB调试相关文件
├── gdb-cmd.txt GDB命令脚本
├── hdc 硬盘文件目录
│ └── umounted 未挂载的硬盘文件
├── hdc-0.11.img Linux 0.11系统硬盘镜像
├── linux-0.11 Linux 0.11源码目录
│ ├── Image 编译生成的内核镜像
│ ├── Makefile 主构建文件
│ ├── System.map 内核符号表
│ ├── boot 启动引导代码
│ │ ├── bootsect 引导扇区代码
│ │ ├── bootsect.o 引导扇区目标文件
│ │ ├── bootsect.s 引导扇区汇编代码
│ │ ├── head.o 初始化目标文件
│ │ ├── head.s 初始化汇编代码
│ │ ├── setup 引导程序代码
│ │ ├── setup.o 引导程序目标文件
│ │ └── setup.s 引导程序汇编代码
│ ├── fs 文件系统相关代码
│ │ ├── Makefile 文件系统模块构建文件
│ │ ├── *.c 文件系统功能实现(如inode、读写、打开文件等)
│ │ ├── *.o 文件系统模块目标文件
│ │ └── ... 文件系统代码的其他支持文件
│ ├── include 头文件目录
│ │ ├── asm 汇编相关头文件
│ │ ├── linux Linux内核头文件
│ │ └── sys 系统调用相关头文件
│ ├── init 内核初始化代码
│ │ ├── main.c 内核初始化主程序
│ │ └── main.o 内核初始化目标文件
│ ├── kernel 内核核心功能实现
│ │ ├── Makefile 内核模块构建文件
│ │ ├── blk_drv 块设备驱动
│ │ ├── chr_drv 字符设备驱动
│ │ ├── math 数学仿真模块
│ │ ├── *.c 核心功能实现(如进程调度、系统调用等)
│ │ ├── *.o 核心模块目标文件
│ │ └── ... 其他内核模块
│ ├── lib 库函数实现
│ │ ├── Makefile 库函数模块构建文件
│ │ ├── *.c 标准库函数实现(如malloc、字符串处理等)
│ │ └── *.o 标准库函数目标文件
│ ├── mm 内存管理模块
│ │ ├── Makefile 内存管理模块构建文件
│ │ ├── memory.c 内存管理核心代码
│ │ ├── swap.c 交换分区管理代码
│ │ ├── *.o 内存管理模块目标文件
│ │ └── ... 内存管理的其他支持文件
│ ├── tags 代码标签文件,用于代码导航
│ └── tools 工具目录
│ ├── build 内核构建工具
│ ├── build.c 内核构建工具源码
│ └── system 系统构建工具
├── mount-hdc 挂载硬盘脚本
├── newdisk.img 新硬盘镜像文件
├── run 启动Linux 0.11的脚本
├── rungdb 使用GDB调试Linux 0.11的脚本
└── swaptest.c 测试交换分区功能的代码