從前面的博文中我們已經知道,把一塊存放slab結構的記憶體區對映到一組連續的物理頁是最好的選擇,這樣會充分利用快取記憶體並獲得較低的平均訪問時間。
不過,上面的方式主要是針對那些使用非常頻繁的核心資料結構——如task_struct、inode來設計的。如果對記憶體區的請求不是很頻繁,那麼,通過連續的線性位址,而不是實體地址來訪問非連續的物理頁框這樣一種分配模式就會很有意義了。
這種模式的主要優點是避免了外碎片,而缺點是必須打亂核心頁表。此外,非連續記憶體區的大小必須是4096 的倍數。linux 在幾個方面使用非連續記憶體區:為活動的交換區分配資料結構,為模組分配空間,或者給某些i/o 驅動程式分配緩衝區等。此外,非連續記憶體區還提供了另一種使用高階記憶體頁框的方法(參見前面的「高階記憶體對映 」博文)。
vmalloc()函式給核心分配乙個非連續記憶體區。引數size表示所請求記憶體區的大小。如果這個函式能夠滿足請求,就返回新記憶體區的起始位址;否則,返回乙個null 指標(mm/ vmalloc.c):
void * vmalloc(unsigned long size)
memset(area->pages, 0, array_size);
for (i=0; inr_pages; i++)
}if (map_vm_area(area, _ _pgprot(0x63), &pages) )
goto fail;
return area->addr;
}函式首先將引數size 設為4096(頁框大小)的整數倍。然後,vmalloc()呼叫get_vm_area()來建立乙個新的描述符,並返回分配給這個記憶體區的線性位址。描述符的flags 欄位被初始化為vm_alloc標誌,該標誌意味著通過使用vmalloc()函式,非連續頁框將被對映到乙個線性位址區間。然後vmalloc()函式呼叫kmalloc()來請求一組連續頁框,這組連續頁框足夠包含乙個頁描述符指標陣列。呼叫memset()函式來將所有這些指標設為null。接著重複呼叫alloc_page()函式,每一次為區間中nr_pages個頁的每乙個分配乙個頁框,並把對應頁描述符的位址存放在area->pages陣列中。注意,必須使用area->pages 陣列是因為頁框可能屬於zone_highmem記憶體管理區,所以此時它們不必被對映到乙個線性位址上。
簡要介紹一下memset(area->pages, 0, array_size)的實現函式:
static inline void * __memset_generic(void * s, char c,size_t count)
現在到了棘手的部分。直到這裡,已經得到了乙個新的連續線性位址區間,並且已經分配了一組非連續頁框來對映這些線性位址。最後至關重要的步驟是修改核心使用的頁表項 ,以此表明分配給非連續記憶體區的每個頁框現在對應著乙個線性位址,這個線性位址被包含在vmalloc()產生的非連續線性位址區間中。這就是map_vm_area()所要做的,下面來詳細說說:
map_vm_area()函式使用以下3 個引數:
area:指向記憶體區的vm_struct 描述符的指標。
prot:已分配頁框的保護位。它總是被置為0x63,對應著present、accessed、read/write 及dirty。
pages:指向乙個指標陣列的變數的位址,該指標陣列的指標指向頁描述符(因此,structpage *** 被當作資料型別使用!)。
函式首先把記憶體區的開始和末尾的線性位址分別分配給區域性變數address和end:
address = area->addr;
end = address + (area->size - page_size);
請記住,area->size存放的是記憶體區的實際位址加上4kb 記憶體之間的安全區間。然後函式使用pgd_offset_k巨集來得到在主核心頁全域性目錄中的目錄項,該項對應於記憶體區起始線性位址,然後獲得核心頁表自旋鎖:
pgd = pgd_offset_k(address);
spin_lock(&init_mm.page_table_lock);
然後,函式執行下列迴圈:
int ret = 0;
for (i = pgd_index(address); i < pgd_index(end-1); i++)
spin_unlock(&init_mm.page_table_lock);
flush_cache_vmap((unsigned long)area->addr, end);
return ret;
每次迴圈都首先呼叫pud_alloc()來為新記憶體區建立乙個頁上級目錄,並把它的實體地址寫入核心頁全域性目錄的合適表項。然後呼叫alloc_area_pud()為新的頁上級目錄分配所有相關的頁表。接下來,把常量230(在pae被啟用的情況下,否則為222)與address的當前值相加(230 就是乙個頁上級目錄所跨越的線性位址範圍的大小),最後增加指向頁全域性目錄的指標pgd。
迴圈結束的條件是:指向非連續記憶體區的所有頁表項全被建立。
map_area_pud()函式為頁上級目錄所指向的所有頁表執行乙個類似的迴圈:
do while (address < end);
map_area_pmd()函式為頁中間目錄所指向的所有頁表執行乙個類似的迴圈:
do while (address < end);
pte_alloc_kernel()函式分配乙個新的頁表,並更新頁中間目錄中相應的目錄項。接下來,map_area_pte()為頁表中相應的表項分配所有的頁框。address值增加222(222 就是乙個頁表所跨越的線性位址區間的大小),並且迴圈反覆執行。
map_area_pte()的主迴圈為:
do while (address < end);
將被對映的頁框的頁描述符位址page 是從位址pages 處的變數指向的陣列項讀得的。通過set_pte和mk_pte巨集,把新頁框的實體地址寫進頁表。把常量4096(即乙個頁框的長度)加到address上之後,迴圈又重複執行。
注意,map_vm_area()並不觸及當前程序的頁表。因此,當核心態的程序訪問非連續記憶體區時,缺頁發生,因為該記憶體區所對應的程序頁表中的表項為空。然而,缺頁處理程式要檢查這個缺頁線性位址是否在主核心頁表中(也就是init_mm.pgd頁全域性目錄和它的子頁表)。一旦處理程式發現乙個主核心頁表含有這個線性位址的非空項,就把它的值拷貝到相應的程序頁表項中,並恢復程序的正常執行。這種機制將在「缺頁異常處理程式」博文描述。
除了vmalloc()函式之外,非連續記憶體區還能由vmalloc_32()函式分配,該函式與vmalloc()很相似,但是它只從zone_normal和zone_dma記憶體管理區中分配頁框。
linux 2.6 還特別提供了了乙個vmap()函式,它將對映非連續記憶體區中已經分配的頁框:本質上,該函式接收一組指向頁描述符的指標作為引數,呼叫get_vm_area()得到乙個新vm_struct描述符,然後呼叫map_vm_area()來對映頁框。因此該函式與vmalloc()相似,但是它不分配頁框。
vfree()函式釋放vmalloc()或vmalloc_32()建立的非連續記憶體區,而vunmap()函式釋放vmap()建立的記憶體區。兩個函式都使用同乙個引數 —— 將要釋放的記憶體區的起始線性位址address;它們都依賴於__vunmap()函式來做實質性的工作。
__vunmap()函式接收兩個引數:將要釋放的記憶體區的起始位址的位址addr,以及標誌deallocate_pages,如果被對映到記憶體區內的頁框應當被釋放到分割槽頁框分配器(呼叫vfree())中,那麼這個標誌被置位,否則被清除(vunmap()被呼叫)。該函式執行以下操作:
1. 呼叫remove_vm_area()函式得到vm_struct 描述符的位址area,並清除非連續記憶體區中的線性位址對應的核心的頁表項。
2. 如果deallocate_pages 被置位,函式掃瞄指向頁描述符的area->pages指標陣列;對於陣列的每乙個元素,呼叫__free_page()函式釋放頁框到分割槽頁框分配器。此外,執行kfree(area->pages)來釋放陣列本身。
3. 呼叫kfree(area)來釋放vm_struct 描述符。
例如,假定核心態的程序訪問乙個隨後要釋放的非連續記憶體區。程序的頁全域性目錄項等於主核心頁全域性目錄中的相應項,由於「缺頁異常處理程式」博文中所描述的機制,這些目錄項指向相同的頁上級目錄、頁中間目錄和頁表。unmap_area_pte()函式只清除頁表中的項(不**頁表本身)。程序對已釋放非連續記憶體區的進一步訪問必將由於空的頁表項而觸發缺頁異常。但是,缺頁處理程式會認為這樣的訪問是乙個錯誤,因為主核心頁表不包含有效的表項。
處理非連續記憶體區訪問
回憶一下 缺頁異常處理程式 當出現缺頁異常,並且是程序處於核心態,即do page fault 中的那個if unlikely address task size 分支語句後,將通過vmalloc fault address 判斷該發生缺頁異常的位址address是否處於非連續記憶體區 arch i...
初始化非連續記憶體區
回到 mm init 函式,繼續走,下乙個函式 pgtable cache init 不知道咋的,是個空函式,也許是保留著以後開發吧。最後乙個函式是 vmalloc init 來自mm vmalloc.c 1088void init vmalloc init void 1089 1101 1102 ...
OS 非連續記憶體分配
利用調整程序占用的分割槽位置,來減少或避免分割槽碎片。2.分割槽對換 通過搶占並 處於等待狀態的程序分割槽,增大可用記憶體空間,將等待狀態的程序掛起。在連續記憶體分配的過程中,不可避免的要產生記憶體碎片,從而使得連續記憶體分配難以實現,並且記憶體利用效率較低。從而需要對記憶體碎片進行更好的利用 非連...