摘要:本文主要講述linux如何處理ARM cortex A9多核處理器的記憶體管理部分。主要包括對頁面配置設定函數的介紹。
1.1.1 配置設定頁面
不同的上下文,對配置設定頁面有不同的限制。例如:在中斷上下文中,不允許睡眠,那麼配置設定記憶體時,就不能等待換頁或者其他可能引起排程的事件。在檔案系統代碼中,申請頁面時,需要使用GFP_NOFS标志,這樣在記憶體不足時,就不能通過檔案系統回寫檔案緩存,以免引起死鎖。Linux3.0提供了以下配置設定标志:
/**
* 類似于GFP_ATOMIC,但是沒有__GFP_HIGH标志。實際上就是0.
* __GFP_HIGH标志表示需要适當降低水線,以使用緊急記憶體池。
*/
#define GFP_NOWAIT (GFP_ATOMIC & ~__GFP_HIGH)
/* GFP_ATOMIC means both !wait (__GFP_WAIT not set) and use emergency pool */
* 原子配置設定。不允許阻塞,同時允許使用緊急記憶體池。
* 這個标志主要用于中斷上下文。
#define GFP_ATOMIC (__GFP_HIGH)
* 不允許IO操作。當記憶體不足時,可能需要将記憶體換出到外部裝置,這需要IO操作。
* 但是在進行IO操作的過程,需要申請記憶體時必須指定此标志,以免死鎖。
#define GFP_NOIO (__GFP_WAIT)
* 不允許檔案系統操作。在檔案系統代碼中,申請記憶體需要有此标志。也是為了避免死鎖。
#define GFP_NOFS (__GFP_WAIT | __GFP_IO)
* 當記憶體不足時,允許通過檔案系統、IO操作将頁面換出以釋放記憶體空間。
* 一般的系統調用代碼中,都使用此配置設定标志。
* 這也是最常見的配置設定标志。
#define GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS)
* 類似于GFP_KERNEL,并且在記憶體不足時,還允許進行記憶體回收。
#define GFP_TEMPORARY (__GFP_WAIT | __GFP_IO | __GFP_FS | \
__GFP_RECLAIMABLE)
* 配置設定用于使用者程序的頁面。與GFP_KERNEL相比,增加__GFP_HARDWALL。
* __GFP_HARDWALL表示可以在程序能夠運作的CPU節點上配置設定記憶體。
#define GFP_USER (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
* 配置設定用于使用者程序的頁面,并且可以在高端記憶體中配置設定記憶體。
#define GFP_HIGHUSER (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | \
__GFP_HIGHMEM)
* 配置設定用于使用者程序的頁面,并且可以在高端記憶體中配置設定記憶體,配置設定的頁面可能被遷移以減少記憶體外碎片。
*/
#define GFP_HIGHUSER_MOVABLE (__GFP_WAIT | __GFP_IO | __GFP_FS | \
__GFP_HARDWALL | __GFP_HIGHMEM | \
__GFP_MOVABLE)
* 如果記憶體不足,可以通過檔案系統和IO操作将頁面換出以增加可用記憶體。
#define GFP_IOFS (__GFP_IO | __GFP_FS)
* 配置設定的頁面用于透明巨頁。
#define GFP_TRANSHUGE (GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
__GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | \
__GFP_NO_KSWAPD)
* 隻允許在目前節點中配置設定。
#ifdef CONFIG_NUMA
#define GFP_THISNODE (__GFP_THISNODE | __GFP_NOWARN | __GFP_NORETRY)
#else
#define GFP_THISNODE ((__force gfp_t)0)
#endif
配置設定頁面的函數是alloc_pages,在NUMA系統中,它僅僅是對alloc_pages_current的一個簡單封裝。
* 配置設定頁面。
* gfp: 配置設定标志,如GFP_ATOMIC、GFP_KERNEL等等。
* order: 要配置設定的頁面數量為2^order,要配置設定一個頁面,指定order為0.
struct page *alloc_pages_current(gfp_t gfp, unsigned order)
{
/**
* 取目前程序的記憶體配置設定政策。
* 在支援NUMA的系統中,這個政策可以決定在哪些節點中配置設定記憶體。
*/
struct mempolicy *pol = current->mempolicy;
struct page *page;
* 如果存在以下情況,就使用系統預設的配置設定政策:
* 程序沒有指定特定的配置設定政策。
* 在中斷中,由于中斷要求快速執行,是以使用預設政策在目前節點中配置設定記憶體。
* 調用者顯示的要求在目前節點中配置設定記憶體。
if (!pol || in_interrupt() || (gfp & __GFP_THISNODE))
pol = &default_policy;
* 在使用程序配置設定政策前,必須調用get_mems_allowed,以防止系統修改其值。
get_mems_allowed();
/*
* No reference counting needed for current->mempolicy
* nor system default_policy
* numa_memory_policy.txt描述了記憶體配置設定政策,我也不清楚。
* 但是最後都會調用__alloc_pages_nodemask,是以我們接下來分析__alloc_pages_nodemask函數。
if (pol->mode == MPOL_INTERLEAVE)
page = alloc_page_interleave(gfp, order, interleave_nodes(pol));
else
page = __alloc_pages_nodemask(gfp, order,
policy_zonelist(gfp, pol, numa_node_id()),
policy_nodemask(gfp, pol));
* 允許進行記憶體政策的修改。
put_mems_allowed();
return page;
}
EXPORT_SYMBOL(alloc_pages_current);
頁面配置設定的核心算法由__alloc_pages_nodemask函數實作。
* 頁面配置設定的核心算法。
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
struct zonelist *zonelist, nodemask_t *nodemask)
* 根據傳入的标志,決定從哪個記憶體區開始配置設定記憶體。
* 例如,如果沒有指定高端記憶體标志,那麼就從normal開始,當normal中沒有可用記憶體時,再從DMA32、DMA中配置設定記憶體。
enum zone_type high_zoneidx = gfp_zone(gfp_mask);
struct zone *preferred_zone;
* 根據傳入的标志,決定配置設定頁面的遷移方式。
* 頁面遷移功能可以減少記憶體外碎片。
int migratetype = allocflags_to_migratetype(gfp_mask);
* 在系統運作的各個階段,某些标志不能使用。
* 如,沒有初始化檔案系統時,自然不能使用GFP_FS标志。這裡對傳入的标志進行整理,以适用系統各個階段的情況。
gfp_mask &= gfp_allowed_mask;
* 調試代碼,略過。
lockdep_trace_alloc(gfp_mask);
* 如果指定了__GFP_WAIT标志,那麼在配置設定頁面的過程中就可能會睡眠。
* 如果目前上下文不允許程序睡眠,并且指定了__GFP_WAIT标志,則會産生警告。這也是用于調試的代碼。
might_sleep_if(gfp_mask & __GFP_WAIT);
* 這裡是對參數進行進一步的檢查,當打開CONFIG_FAIL_PAGE_ALLOC調試配置選項才有用,略過。
if (should_fail_alloc_page(gfp_mask, order))
return NULL;
* Check the zones suitable for the gfp_mask contain at least one
* valid zone. It's possible to have an empty zonelist as a result
* of GFP_THISNODE and a memoryless node
* 如果沒有任何一個可用管理區,則直接傳回NULL。
* 如果指定了GFP_THISNODE标志并且目前節點沒有記憶體,則可能存在這種情況。
* 這一般要求上層調用者在發現NULL傳回值時,指定其他标志。
if (unlikely(!zonelist->_zonerefs->zone))
* 後續要求調用cpuset_current_mems_allowed,這個宏獲得目前程序可用的節點。
* 調用這個宏需要防止目前程序的記憶體政策被修改。get_mems_allowed用于此目的。
/* The preferred zone is used for statistics later */
* 根據傳入的參數,确定優先在哪個管理區中配置設定記憶體。
first_zones_zonelist(zonelist, high_zoneidx,
nodemask ? : &cpuset_current_mems_allowed,
&preferred_zone);
* 沒有可用的管理區。
if (!preferred_zone) {
put_mems_allowed();/* 與get_mems_allowed配對調用。 */
}
/* First allocation attempt */
* 快速配置設定路徑,在這個配置設定路徑中,指定了__GFP_HARDWALL和ALLOC_WMARK_LOW、ALLOC_CPUSET标志。
* 這樣,在配置設定時要考慮記憶體的CPU親和性,并且盡量在較高的水線上配置設定,防止某些記憶體區被過早的擊穿。
page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,
zonelist, high_zoneidx, ALLOC_WMARK_LOW|ALLOC_CPUSET,
preferred_zone, migratetype);
if (unlikely(!page))/* 快速路徑無法獲得記憶體,這樣就需要降低水線标準,或者啟動記憶體回收過程。 */
page = __alloc_pages_slowpath(gfp_mask, order,
zonelist, high_zoneidx, nodemask,
preferred_zone, migratetype);
/* 與get_mems_allowed配對調用。 */
/* 調試代碼。 */
trace_mm_page_alloc(page, order, gfp_mask, migratetype);
EXPORT_SYMBOL(__alloc_pages_nodemask);
頁面配置設定的主流程在get_page_from_freelist和__alloc_pages_slowpath函數中。
未完待續