天天看點

《LINUX3.0核心源代碼分析》第四章:記憶體管理(2)

摘要:本文主要講述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函數中。

未完待續

繼續閱讀