linux0.11內存管理
描述linux 0.11的內存管理主要內容。
1:內存初始化
linux 0.11最大支持16MB的物理內存。
main函數和mem_init函數對內存進行了初始化。
主要使用數組mem_map[]來標記相應的內存頁是否被占用。
memory_end是用BIOS中斷調用得到的實際內存大小。
if (memory_end > 16 * 1024 * 1024)
memory_end = 16 * 1024 * 1024; # 因此最大只支持16MB內存
if (memory_end > 12*1024*1024)
buffer_memory_end = 4 * 1024 * 1024; # buffer_memory_end為高速緩存末端地址,其大小於機器總內存大小相關
else if (memory_end > 6 * 1024 * 1024)
buffer_memory_end = 2 * 1024 * 1024
else
buffer_memory_end = 1*1024*1024
main_memory_start = buffer_memory_end;
mem_init(main_memory_start, memory_end);
在mem_init中會對mem_map[]數組進行初始化。
在1MB~16MB之間,共有(15 * 1024 * 1024) >> 12 = 3840頁。
定義數組mem_map[3840]對應於這段內存的每一頁,在main_memory_start和memory_end之間的頁,相應的mem_map[i]的值
初始化為0,表示未使用,其余的項初始化為100,表示被占用。
2:基本頁面分配函數
有幾個基本的頁面分配和釋放的函數。
get_free_page():返回一個空閒頁面的物理地址, 該函數就是在mem_map數組中查找值為0的項,然後轉換成頁面物理地址返回。
free_page(phy_addr):釋放phy_addr指向的頁面, 釋放操作就是將phy_addr在mem_map中對應項的值進行減1。
get_empty_page(line_addr):該函數的參數指定的是線性地址,要求獲得一頁物理內存,並用line_addr指向這頁內存。
void get_empty_page(unsigned long address)
{
unsigned long tmp;
if (!(tmp=get_free_page()) || !put_page(tmp, address)) {
free_page(tmp);
oom(); # 內存不夠
}
}
明顯該函數先調用get_free_page獲得一頁物理內存, 然後用put_page對這頁物理內存的物理地址和線性地址之間建立映射。
建立映射的過程:
對於32位的線性地址, 高10位表示頁目錄索引, 中間10位為頁表索引, 低12位為頁內偏移。
因此給定一個線性地址,我們就能通過頁目錄基地址和線性地址高10位來確定它的頁目錄項。
在Linux 0.11中,頁目錄基地址就是0。
put_page:
page_table = (unsigned long *) ((line_addr >> 20) & 0xffc) # 獲得頁目錄項的指針
如果該頁目錄項所指向的頁表是存在的, 則利用線性地址的中間10位,定位到頁表中的相應頁表項,
並將物理地址保存進去即可建立映射。 若頁表不存在,則用get_free_page首先分配一頁作為頁表,再去建立映射。
不管是建立映射還是進行頁表拷貝,都是先考慮頁目錄,再考慮頁表。
3:sys_fork與copy_page_tables函數
在sys_fork時會調用copy_process,copy_process中會調用copy_mem函數, 該函數會將父進程的頁表拷貝給子進程,
這是父進程和子進程會共享相應的代碼和數據段,只有當父進程或子進程對共享的內存進行寫操作時,才會為子進程分配內存,即為寫時復制。
copy_mem是調用copy_page_table進行頁表拷貝的。
copy_page_table(old_data_base, new_data_base, data_limit)
將父進程的線性地址old_data_base ~ old_data_base+data_limit對應的頁表,拷貝給子進程。
在拷貝過程中,將每個頁表項設定為只讀, 並且執行相應的mem_map[i]++(相當於添加引用計數)
4:頁出錯異常處理
有兩種不同的頁出錯:
i)頁表項指向的頁不存在,即頁表項的存在位的值為0.
ii)頁保護機制。 寫只讀頁面時出錯。
但出現頁錯誤時,會發生int 14中斷。系統會執行_page_fault:異常處理代碼。
該代碼會根據兩種不同的頁錯誤,分別執行do_no_page和do_wp_page
do_wp_page(error_code, address)
該函數主要是實現了寫時復制功能, 在copy_page_table時,將父進程和子進程的頁表都設定成了只讀,當訪問了其中一個頁面後,
會觸發中斷並執行do_wp_page函數。
此函數會分配一頁新的物理內存, 並將相應的頁表項設成可讀寫。
do_no_page(error_code, address)
該函數可處理兩種情況:
i)在應用分配內存時,內核並不會實際分配物理內存, 只有在訪問相應內存時,才會分配。
ii)在執行exec系列函數時,同樣只有在訪問相應內存時,才會去讀文件,
對於第一種情況, do_no_page直接調用get_empty_page函數,獲取一頁內存。
對於第二種情況,有兩步過程。
i)調用share_page函數。 該函數的主要目的是共享代碼和數據段。 如果一個可執行文件,已經有一個進程實例在執行,那麼
新進程可以和其他進程共享該可執行文件的代碼和數據段。
ii)如果只執行過一次該文件, 那就先調用get_free_page函數,將文件內容讀入內存,然後用put_page函數建立映射。