Linux内核代码之初始化内核临时页表---经典至极

http://www.douban.com/note/57007577/

Linux内核代码之初始化内核临时页表---经典至极

2010-01-15 18:51:04
漫长而黑暗的史前时代终于到了setup。在setup汇编函数中,linux通过设置cr0寄存器的PE位(从实模式切换到保护模式)完成了史前文明到现代文明的转变。在setup时期,linux已经了解到世上可用的内存资源远远不止1MB。此时linux的欲望开始膨胀,最为满足它欲望的第一步,它开始抢占内存资源的前8MB。

初始化临时内核页表是在startup_32汇编语言函数中完成的。在ULK所述中,假设内核能容纳于RAM的前8MB空间,然后对RAM的前8MB进行恒等映射(例如用户地址0x00003000映射物理地址0x00003000,0xc0003000映射到物理地址0x00003000),来初始化临时页全局目录swapper_pg_dir和相应的页表。映射8MB只需要填充swapper_pg_dir中第0项,1项,768项和769项。前两项是给用户线性地址映射,后两项给内核线性地址映射。用页全局目录里的两项就能对8MB映射的理由是2×1024(页表有1024项)×4K(一页的大小)=8M。实际上初始化内核页表来对RAM的前8MB映射不是个硬性的规定。这取决于你的内核的配置(我认为大多数情况下是对8MB映射)。在 startup_32中可以看到,对多少内存进行映射是通过pg0动态判断的。

linux/arch/i386/kernel/head.S

page_pde_offset = (__PAGE_OFFSET >> 20);

/*__PAGE_OFFSET是0xc0000000,内核线性空间的起始地址。

page_pde_offset=0xc00(十进制为3072)*/

movl $(pg0 - __PAGE_OFFSET), %edi

/*pg0的线性地址可以在/boot/System.map文件中找到。我的Ubuntu8.04机器上是0xc04f4000。

减去0xc0000000就是pg0的物理地址(004f4000),放入edi中。*/


movl $(swapper_pg_dir - __PAGE_OFFSET), %edx

/*swapper_pg_dir的线性地址也可以在/boot/System.map文件中找到。我机器上是0xc047d000。

减去0xc0000000就是swapper_pg_dir的物理地址(0047d000),放入edx中。*/


movl $0x007, %eax /* 0x007 = PRESENT+RW+USER */

/*页目录项和页表项的低12位是标志位,把标志位0x007放入eax中。*/


10:
leal 0x007(%edi),%ecx /* Create PDE entry */

/*第一循环时把edi指向的pg0的物理地址加上0x007放入ecx中。

第二次循环时把edi指向的物理地址0x4f5000加上0x007放入ecx中。*/


movl %ecx,(%edx) /* Store identity PDE entry */

/*第一次循环时把ecx中的内容放入swapper_pg_dir的第0项里。

第二次循环时把ecx中的内容放入swapper_pg_dir的第1项里。*/


movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry */

/*第一次循环时把ecx中的内容放入swapper_pg_dir的第768项里。因为前面算出page_pde_offset的值为3072,而swapper_pg_dir中每项是4个字节,所以3072/4=768。

第二次循环时把ecx中的内容放入swapper_pg_dir的第769项里。*/


addl $4,%edx

/*第一次循环时,此时edx指向swapper_pg_dir的第1项。

第二次循环时,此时edx指向swapper_pg_dir的第2项。*/


movl $1024, %ecx

/*为初始化1024个页表项设置计数*/


11:
stosl

/*把eax中的内容放入edi指向的物理地址中,然后edi+4。*/


addl $0x1000,%eax
loop 11b

/*跳到上面的11处循环。

第一次执行1024次后,从pg0物理地址(0x4f4000)开始存放的是0x007,0x1007,0x2007,...,0x3ff007,也就是当前能够映射到物理地址从0x000到0x3fffff处。此时edi中的值为0x4f5000。

第二次执行1024次后,从物理地址(0x4f5000)开始存放的是0x400007,0x401007,0x402007,...,7ff007,也就是当前能够映射到物理地址0x000到7fffff处,正好8MB。此时edi中的值为0x4f6000。*/


leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp

/*INIT_MAP_BEYOND_END的值为128k,在此文件中的一个宏定义。把edi指向的物理地址加上128k加上0x007放入edp中。*/


cmpl %ebp,%eax

/*在第一次循环中ebp中的值为0x515007,eax中的值为0x400007小于0x515007。当前所映射到的最大物理地址为0x3fffff没有包含0x515007,所以没有映射完。

在第二次循环中ebp中的值为0x516007,eax中的值为0x800007大于0x516007。当前所映射到的最大物理地址为0x7fffff包含了0x516007,所以8MB物理地址映射完毕。*/


jb 10b

/*第一次循环做完时跳到上面的10处继续循环

第二次循环做完时跳出循环。*/


movl %edi,(init_pg_tables_end - __PAGE_OFFSET)

/*最后把0x4f6000放入init_pg_tables_end 所表示的物理地址中。也在/boot/System.map中。*/

此时的linux胃口越来越大,8MB的资源已不能满足它的胃口了。它的黑手开始慢慢伸向896MB以下的内存了。

Linux初始化内核临时页表之后的setup_memory函数

话说在linux内核初始化完内核临时页表来映射前8MB内存后,在欲望的驱使下,linux开始着手抢占896MB以下的内存了。然而抢夺这896MB内存绝非易事。linux还有许多麻烦的事要做。比如说进攻和占领伊拉克等等。于是setup_memory函数开始为linux策划和筹备此事。主要任务是建立一张与物理内存页框号对应的位图。如果该页框被占用则对应位图中的位被置1,否则置0。在伙伴系统建立前对内存进行临时管理。在初始化内核最终页表时alloc_bootmem_low_pages内存分配函数就用到了这个位图。

核心数据结构:

typedef struct  bootmem_data {          unsigned long node_boot_start; /* 起始页框号0 */          unsigned long node_low_pfn; /* 如果物理内存大于896MB,node_low_pfn为0x37FFF。 如果物理内存小于等于896MB,node_low_pfn为物理内存的最大页框号。*/          void *node_bootmem_map; /* 位图的起始地址 */ /* 下面三个变量的作用用于alloc_bootmem_low_pages函数 */          unsigned long last_offset;          unsigned long last_pos;          unsigned long last_success;     }  bootmem_data_t;

创建位图的函数流程:

start_kernel->setup_arch->setup_memory->init_bootmem->init_bootmem_core 。

linux/arch/i386/kernel/setup.c

在setup_memory()中执行

start_pfn = PFN_UP(init_pg_tables_end);

/* start_pfn是指映射8MB所用页表之后第一个页框号。PFN_UP的作用是把地址按页的大小进行对齐。也就是地址是4kb的整数倍大小。*/

find_max_pfn();

/* 找到物理内存中最大页框号。放入max_pfn中。*/

max_low_pfn=find_max_low_pfn();

/* 如果物理地址小于等于896MB,max_low_pfn=max_pfn。大于896MB时,max_low_pfn为896MB内存大小的最大页框号 */

bootmap_size = init_bootmem(start_pfn,max_low_pfn);

进入init_bootmem中

unsigned long  __init  init_bootmem (unsigned long  start, unsigned long pages) {          max_low_pfn = pages;          min_low_pfn =  start;         return( init_bootmem_core( NODE_DATA(0),  start, 0, pages)); }

再从init_bootmem进入init_bootmem_core中,此函数创建位图

static unsigned long  __init  init_bootmem_core ( pg_data_t *pgdat,          unsigned long mapstart, unsigned long  start, unsigned long  end) {           bootmem_data_t *bdata = pgdat->bdata;          unsigned long mapsize = (( end -  start)+7)/8;/* mapsize为要创建的位图的大小 */          pgdat->pgdat_next =  pgdat_list;           pgdat_list = pgdat;          mapsize = (mapsize + (sizeof(long) - 1UL)) & ~(sizeof(long) - 1UL);/* 按照4个字节大小进行对齐 */          bdata->node_bootmem_map =  phys_to_virt(mapstart <<  PAGE_SHIFT);/* bdata->node_bootmem_map 为位图的起始位置的线性地址 */          bdata->node_boot_start = ( start <<  PAGE_SHIFT);/* 第一个页框的起始地址,就是0号页框的起始地址0x00000000 */          bdata->node_low_pfn =  end;           memset(bdata->node_bootmem_map, 0xff, mapsize); /* 把位图中的每一位都置1,表示为占用状态。在接下去的函数中,会把内核可以使用的页框号对应的位置0。 */         return mapsize;}

回到setup_memory中,接着执行

register_bootmem_low_pages(max_low_pfn);

此函数的主要的功能是把内核可以使用的页框号(最大为max_low_pfn)在位图中对应的位置0。

由于有些页已经被内核数据占用,所以还要把这些页再置为1。就是调用

reserve_bootmem( HIGH_MEMORY, ( PFN_PHYS(start_pfn) +                          bootmap_size +  PAGE_SIZE-1) - ( HIGH_MEMORY));

此函数把从1MB开始(HIGH_MEMORY定义为1024*1024)到位图结束所占用的页框号在位图中对应的位置1。

然后根据不同的硬件配置再做一些扫尾的工作。

return max_low_pfn;

setup_memory函数结束。

接着linux就会调用paging_init来完成它对896MB内存的抢夺了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值