漫長而黑暗的史前時代終於到了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以下的內存了。