【版權所有,轉載請註明出處。出處:http://www.cnblogs.com/joey-hua/p/5597705.html 】 Linux內核因為使用了記憶體分頁機制,所以相對來說好理解些。因為記憶體分頁就是為了方便管理記憶體。 說到記憶體分頁,最根部的要屬頁目錄表了,head.h中: 然後再看head ...
【版權所有,轉載請註明出處。出處:http://www.cnblogs.com/joey-hua/p/5597705.html 】
Linux內核因為使用了記憶體分頁機制,所以相對來說好理解些。因為記憶體分頁就是為了方便管理記憶體。
說到記憶體分頁,最根部的要屬頁目錄表了,head.h中:
extern unsigned long pg_dir[1024]; // 記憶體頁目錄數組。每個目錄項為4 位元組。從物理地址0 開始。
然後再看head.s:
/* * head.s 含有32 位啟動代碼。 * 註意!!! 32 位啟動代碼是從絕對地址0x00000000 開始的,這裡也同樣是頁目錄將存在的地方, * 因此這裡的啟動代碼將被頁目錄覆蓋掉。 */ .text .globl _idt,_gdt,_pg_dir,_tmp_floppy_area _pg_dir: # 頁目錄將會存放在這裡。 ...
頁目錄存放的地方是從絕對地址0開始的,接下來分配頁表:
/* Linus 將內核的記憶體頁表直接放在頁目錄之後,使用了4 個表來定址16 Mb 的物理記憶體。 * 如果你有多於16 Mb 的記憶體,就需要在這裡進行擴充修改。 */ # 每個頁表長為4 Kb 位元組(1 頁記憶體頁面),而每個頁表項需要4 個位元組,因此一個頁表共可以存放 # 1024 個表項。如果一個頁表項定址4 Kb 的地址空間,則一個頁表就可以定址4 Mb 的物理記憶體。 # 頁表項的格式為:項的前0-11 位存放一些標誌,例如是否在記憶體中(P 位0)、讀寫許可(R/W 位1)、 # 普通用戶還是超級用戶使用(U/S 位2)、是否修改過(是否髒了)(D 位6)等;表項的位12-31 是 # 頁框地址,用於指出一頁記憶體的物理起始地址。 .org 0x1000 # 從偏移0x1000 處開始是第1 個頁表(偏移0 開始處將存放頁表目錄)。 pg0: .org 0x2000 pg1: .org 0x3000 pg2: .org 0x4000 pg3: ...
分配了4個頁表空間,因為一個頁表項對應的是一個頁也就是4K,所以一個頁表就是4K*1024=4M,這裡有4個頁表所以就是16M。並且註意這裡是從.org 0x1000開始的,前面4K的空間是留給整個頁目錄的。
下麵是設置四個頁目錄項的屬性,因為只有4個頁表,所以相應的就是4個頁目錄項:
# 下麵4 句設置頁目錄表中的項,因為我們(內核)共有4 個頁表所以只需設置4 項。 # 頁目錄項的結構與頁表中項的結構一樣,4 個位元組為1 項。參見上面113 行下的說明。 # "$pg0+7"表示:0x00001007,是頁目錄表中的第1 項。 # 則第1 個頁表所在的地址 = 0x00001007 & 0xfffff000 = 0x1000; # 第1 個頁表的屬性標誌 = 0x00001007 & 0x00000fff = 0x07,表示該頁存在、用戶可讀寫。 movl $pg0+7,_pg_dir /* set present bit/user r/w */ movl $pg1+7,_pg_dir+4 /* --------- " " --------- */ movl $pg2+7,_pg_dir+8 /* --------- " " --------- */ movl $pg3+7,_pg_dir+12 /* --------- " " --------- */
註意這裡+7的意思是,先看PDE 頁目錄項格式 可知,每個頁目錄項的0-11位是屬性,12-31位才是基址,7對應的二進位是111,也就是P、R/W、U/S位都為1.
好,現在頁目錄和4個頁表都分配好了。接下來進入初始化程式mem_init(main_memory_start, memory_end);在main.c和memory.c中:
static long memory_end = 0; // 機器具有的物理記憶體(位元組數)。 static long buffer_memory_end = 0; // 高速緩衝區末端地址。 static long main_memory_start = 0; // 主記憶體(將用於分頁)開始的位置。 memory_end = (1 << 20) + (EXT_MEM_K << 10); // 記憶體大小=1Mb 位元組+擴展記憶體(k)*1024 位元組。 memory_end &= 0xfffff000; // 忽略不到4Kb(1 頁)的記憶體數。 if (memory_end > 16 * 1024 * 1024) // 如果記憶體超過16Mb,則按16Mb 計。 memory_end = 16 * 1024 * 1024; if (memory_end > 12 * 1024 * 1024) // 如果記憶體>12Mb,則設置緩衝區末端=4Mb buffer_memory_end = 4 * 1024 * 1024; else if (memory_end > 6 * 1024 * 1024) // 否則如果記憶體>6Mb,則設置緩衝區末端=2Mb buffer_memory_end = 2 * 1024 * 1024; else buffer_memory_end = 1 * 1024 * 1024; // 否則則設置緩衝區末端=1Mb main_memory_start = buffer_memory_end; // 主記憶體起始位置=緩衝區末端; // 如果定義了記憶體虛擬盤,則初始化虛擬盤。此時主記憶體將減少。參見kernel/blk_drv/ramdisk.c。 #ifdef RAMDISK // 如果定義了記憶體虛擬盤,則主記憶體將減少。 main_memory_start += rd_init (main_memory_start, RAMDISK * 1024); #endif mem_init (main_memory_start, memory_end);
這裡結合下圖參考:
如果定義了記憶體虛擬盤,主記憶體區開始位置main_memory_start就從虛擬盤末端開始,否則就從高速緩衝區末端開始;而主記憶體區結尾memory_end則為16M。
/* 下麵定義若需要改動,則需要與head.s 等文件中的相關信息一起改變 */ // linux 0.11 內核預設支持的最大記憶體容量是16M,可以修改這些定義以適合更多的記憶體。 #define LOW_MEM 0x100000 // 記憶體低端(1MB)。 #define PAGING_MEMORY (15*1024*1024) // 分頁記憶體15MB。主記憶體區最多15M。 #define PAGING_PAGES (PAGING_MEMORY>>12) // 分頁後的物理記憶體頁數。相當於除以4096 #define MAP_NR(addr) (((addr)-LOW_MEM)>>12) // 指定記憶體地址映射為頁號。 #define USED 100 // 頁面被占用標誌,參見405 行。 static long HIGH_MEMORY = 0; // 全局變數,存放實際物理記憶體最高端地址。 // 記憶體映射位元組圖(1 位元組代表1 頁記憶體),每個頁面對應的位元組用於標誌頁面當前被引用(占用)次數。 static unsigned char mem_map[PAGING_PAGES] = { 0, }; //// 物理記憶體初始化。 // 參數:start_mem - 可用作分頁處理的物理記憶體起始位置(已去除RAMDISK 所占記憶體空間等)。 // end_mem - 實際物理記憶體最大地址。 // 在該版的linux 內核中,最多能使用16Mb 的記憶體,大於16Mb 的記憶體將不於考慮,棄置不用。 // 0 - 1Mb 記憶體空間用於內核系統(其實是0-640Kb)。 void mem_init (long start_mem, long end_mem) { int i; HIGH_MEMORY = end_mem; // 設置記憶體最高端。 for (i = 0; i < PAGING_PAGES; i++) // 首先置所有頁面為已占用(USED=100)狀態, mem_map[i] = USED; // 即將頁面映射數組全置成USED。 i = MAP_NR (start_mem); // 然後計算可使用起始記憶體的頁面號。 end_mem -= start_mem; // 再計算可分頁處理的記憶體塊大小。 end_mem >>= 12; // 從而計算出可用於分頁處理的頁面數。 while (end_mem-- > 0) // 最後將這些可用頁面對應的頁面映射數組清零。 mem_map[i++] = 0; }
首先註意PAGING_MEMORY,因為1M以下的記憶體空間是內核所用,不參與分頁,所以主記憶體大小最多為15M。所以PAGING_PAGES就是主記憶體大小除以4096(一頁就是4096位元組)就是記憶體頁面總數。
所以mem_map的作用就是記憶體頁面映射。
這裡註意一下MAP_NR(start_mem)這個巨集,用主記憶體區開始地址減去不參與分頁的1M空間,得到從可用於分頁的記憶體地址開始的容量,再除以4096(一頁就是4096位元組)就可以得到頁號。
然後計算主記憶體區的大小,並把mem_map中從主記憶體區start_mem開始的頁號置為0.
到這裡為止,記憶體管理的初始化就算結束了!