注意:在Linux0.12内核中,在内核代码地址空间(线性地址<1MB)执行fork()来创建进程时并没有采用
写时复制技术。因此,任务0在内核空间创建进程1(init进程)时将使用同一代码和数据段。
写时复制的优点:1) 节约内存(避免了不必要的内存页面复制的开销)
2) 加快创建进程速度
优点:大大提高程序的加载执行速度,无需等待多次的块设备I/O操作把整个执行文件加载到内存后才执行
缺点:对被加载执行目标文件的格式有一定的要求(ZMAGIC类型)
这样一来,程序的代码和数据都从"页面边界"开始存放,以适应内核以一个页面为单位读取代码或数据内容
page.s包括页异常中断处理程序(int 14),主要分两种情况处理:
1) 缺页引起异常,调用do_no_page(error_code, address)
2) 页写保护引起异常,调用do_wp_page(error_code, address)
备注:error_code错误码由CPU自动产生并压入堆栈。此外 address 线性地址从CR2中取得
.global _page_fault
_page_fault:
xchgl %eax,(%esp) # 取出错误码到eax
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10,%edx # 置内核数据段选择符
mov %dx,%ds
mov %dx,%es
mov %dx,fs
movl %cr2,%edx # 取引起页面异常的线性地址
pushl %edx # 将该线性地址和出错码压入栈中,作为调用函数的参数
pushl %eax
test1 $1,%eax # 测试页存在标志P(位0),如果不是缺页引起则跳转
jne 1f
call _do_no_page # 否则,调用缺页处理函数
jmp 2f
1: call _do_wp_page # 调用写保护处理函数
2: addl $8,%esp # 丢弃压入栈的两个参数,弹出栈中寄存器并退出中断
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
问:CPU如何诊断是哪种页异常并从中恢复运行呢?
两项信息:
1) 放在“堆栈“的出错码(32位,只用了最低3位)”
2:U/S——0 表示在超级用户模式下执行,1 表示在用户模式下执行
1:W/R——0 表示读操作,1 表示写操作
0:P—— 0 表示不存在,1 表示页级保护
2) CR2: 存放造成异常的32位线性地址。异常处理程序根据此地址定位相应的页目录和页表项。
如果页异常处理程序允许发生另一个异常,处理程序应该将CR2压入堆栈中
swap.c是内存交换管理程序,用于实现“虚拟内存交换”功能(即将暂时不用的内存页面内容临时保存到
磁盘(交换设备)上,腾出内存空间给急需的程序使用。若此后要再次使用已保存到磁盘上的内存页面内容
则本程序再负责将它们“放”到内存中去),采用了“”位图映射技术“
子程序:
get_swap_page()和swap_free()分别用于基于交换位图申请一页交换页面和释放交换设备中指定的页面
swap_out()和swap_in()分别用于把内存页面信息输出到交换设备上和把指定页面交换进内存中
read_swap_page()和write_swap_page()访问指定的交换设备,定义再inclue/linux/mm.h头文件中
#include
#include
#include
#include
#include
#define SWAP_BITS (4096<<3) //交换位图:32768个位,若一个位对于1页内存,则最多可管理32768个页,对于128MB内存容量
/*
位操作宏。通过给定不同的“op”,可定义对指定比特位进行测试、设置或清除三种操作。
参数addr是指定线性地址;nr是指定地址处开始的比特位偏移位。该宏把给定地址addr处第nr个比特位的值放入
进位标志CF中。设置或复位该比特位并返回进位标志值(即原位值)
当第一个指令随“op”字符的不同而组合形成不同的指令。
当op=“”时,就是指令 bt (Bit Test),测试并用原值设置进位位
当op="s"时,就是指令 bts(Bit Test and Set), 设置比特位值并用原值设置进位位
当op="r"时,就是指令 btr(BIt Test Reset) 复位比特位值并原值设置进位位
输入:%0(返回值__res) %1(位偏移(nr)) %2(基地址addr) %3(加操作寄存器初值0)
简单来讲,就是先把给定addr线性地址开始的某位保存在CF中,用bt/bts/btr之一测试CF后,设置或复位该比特位且恢复CF原来的值
adcl 是带进位加,用于根据CF设置操作数%0.若CF=1,则返回寄存器值=1,否则返回寄存器值=0
*/
#define bitop(name, op) \
static inline int name(char* addr, unsigned int nr) \
{ \
int __res; \
__asm__ __volatile__("bt" op " %1,%2; adcl $0, %0" \
: "=g" (__res) \
: "r" (nr), "m" (*(addr)),"0" (0)); \
return __res;
}
// 这里根据不同的op字符定义3个内嵌函数
bitop(bit,""); // bit(char* addr, unsigned int nt);
bitop(setbit,"s"); // setbit(char* addr, unsigned int nt);
bitop(clrbit,"r"); // clrbit(char* addr, unsigned int nt);
static char * swap_bitmap = NULL;
int SWAP_DEV = 0; // 内核初始化时设置的交换设备号
#define FIRST_VM_PAGE (TASK_SIZE>>12) // = 64MB/4KB = 16384 任务0末端(64MB)处开始的第一个虚拟内存页面
#define LAST_VM_PAGE (1024*1024) // = 4GB/4KB = 1048576
#define VM_PAGES (LAST_VM_PAGE - FIRST_VM_PAGE) // = 1032192(从0开始计)
/*
get_swap_page:申请1页交换页面
扫描整个交换映射位图(除对应位图本身的位0外),返回值为1的第一个比特位号,即目前空闲的交换页面号
若操作成功则返回交换页面号,否则返回0
*/
static int get_swap_page(void)
{
int nr;
if (!swap_bitmap)
return;
for(nr = 1; nr < 32768; nr++)
if(clrbit(swap_bitmap, nr))
return nr; // 返回目前空闲的交换页面号
return 0;
}
/*
swap_free:释放交换设备中指定的交换页面
在交换位图中设置指定页面号对应的位(置1).若原来该位就等于1,则表示交换设备中原来该页面就没被占用
或者位图出错。于是显示出错信息并返回。参数指定交换页面号
*/
void swap_free(int swap_nr)
{
if(!swap_nr)
return;
if(swap_bitmap && swap_nr < SWAP_BITS)
if(!setbit(swap_bitmap, swap_nr))
return;
printk("Swap-space bad (swap_free())\n\r");
return;
}
/*swap_in:把指定页面交换进内存中
把指定页表项的对应页面从交换设备中读入到新申请的内存页面中。修改交换位图中对应位(置位),
同时修改页表项的内容,让它指向该内存页面,并设置相应标志(如P存在位)
*/
void swap_in(unsigned long * table_ptr)
{
int swap_nr;
unsigned long page;
/*
首先检测交换位图和参数有效性。如果交换位图不存在,或者指定页表项对应的页面已存在于内存中,或者交换页面号为0
,则显示警告信息后退出。对于已放到交换设备中取的内存页面,相应页表项中存放的应是交换页面号*2,即swap_nr << 1
*/
if(!swap_bitmap){
printk("Trying to swap in without swap bit-map");
return;
}
if(1 & *table_ptr){
printk("trying to swap in present page\n\r");
return;
}
swap_nr = *table_ptr >> 1;
if(!swap_nr){
printk("No swap page in swap_in\n\r");
return 0;
}
/*
然后申请一页物理内存并从交换设备中读入页面号位swap_nr的页面。在把页面交换进来后,就把交换位图中对应比特位
置位。如果其原本就是置位的,说明此次是再次从交换设备中读入相同的页面,于是显示一下警告信息。最后让页表项
指向该物理页面,并设置页面已修改、用户可读写和存在标志(Dirty U/S R/W P)
*/
if(!(page = get_free_page()))
oom();
read_swap_page(swap_nr, (char*)page);
if(setbit(swap_bitmap, swap_nr))
printk("swapping in multiply from same page\n\r");
*table_ptr = page | (PAGE_DIRTY | 7);
}
/*
try_to_swap_out:尝试把页面交换出去
若页面没有被修改过则不必保存在交换设备中,因为对应页面还可以再直接从相应映像文件中读入
于是可以直接释放掉相应物理页面了事。否则就申请一个交换页面号,然后把页面交换出去。此时
交换页面号要保存再对应页表项中,并且仍需要保持页表项存在位P=0.参数是页表项指针。
页面交换或释放成功返回1,否则返回0
*/
int try_to_swap_out(unsigned long * table_ptr)
{
unsigned long page;
unsigned long swap_nr;
page = *table_ptr;
if(!(PAGE_PRESENT & 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;
invalidate();
write_swap_page(swap_nr, (char*)page);
free_page(page);
return 1;
}
*table_ptr = 0;
invalidate();
free_page(page);
return 1;
}
/*
swap_out:把内存页面放到交换设备中
从线性地址64MB对应的目录项(FIRST_VM_PAGE >>10)开始,搜索整个4GD线性空间,对有效页目录二级页表的页表项
指定的物理内存页面执行交换到交换设备中去的尝试。一旦成功低交换出一个页面,就返回1,否则返回0
该函数会在get_free_page()中被调用
*/
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;
}
try_to_swap_out();
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_tabe))
return 1;
}
printk("Out of swap-memory\n\r");
return 0;
}
unsigned long get_free_page(void)
{
register unsigned long __res asm("ax");
repeat:
__asm__("std ; repne ; scasb\n\t"
"jne 1f\n\t"
"movb $1,1(%%edi)\n\t"
"sall $12,%%ecx\n\t"
"addl %2,%%ecx\n\t"
"movl %%ecx,%%edx\n\t"
"movl $1024,%%ecx\n\t"
"leal 4092(%%edx),%%edi\n\t"
"rep ; stosl\n\t"
"movl %%edx,%%eax\n"
"1:"
:"=a" (__res)
:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
"D" (mem_map+PAGING_PAGES-1)
:"di","cx","dx");
if (__res >= HIGH_MEMORY)
goto repeat;
if (!__res && swap_out())
goto repeat;
return __res;
}
void init_swapping(void)
{
extern int *blk_size[];
int swap_size,i,j;
if (!SWAP_DEV)
return;
if (!blk_size[MAJOR(SWAP_DEV)]) {
printk("Unable to get size of swap device\n\r");
return;
}
swap_size = blk_size[MAJOR(SWAP_DEV)][MINOR(SWAP_DEV)];
if (!swap_size)
return;
if (swap_size < 100) {
printk("Swap device too small (%d blocks)\n\r",swap_size);
return;
}
swap_size >>= 2;
if (swap_size > SWAP_BITS)
swap_size = SWAP_BITS;
swap_bitmap = (char *) get_free_page();
if (!swap_bitmap) {
printk("Unable to start swapping: out of memory :-)\n\r");
return;
}
read_swap_page(0,swap_bitmap);
if (strncmp("SWAP-SPACE",swap_bitmap+4086,10)) {
printk("Unable to find swap-space signature\n\r");
free_page((long) swap_bitmap);
swap_bitmap = NULL;
return;
}
memset(swap_bitmap+4086,0,10);
for (i = 0 ; i < SWAP_BITS ; i++) {
if (i == 1)
i = swap_size;
if (bit(swap_bitmap,i)) {
printk("Bad swap-space bit-map\n\r");
free_page((long) swap_bitmap);
swap_bitmap = NULL;
return;
}
}
j = 0;
for (i = 1 ; i < swap_size ; i++)
if (bit(swap_bitmap,i))
j++;
if (!j) {
free_page((long) swap_bitmap);
swap_bitmap = NULL;
return;
}
printk("Swap device ok: %d pages (%d bytes) swap-space\n\r",j,j*4096);
}
参考资料
《Linux内核完全剖析——基于0.12内核》第13章 内存管理