Linux Cgroups詳解(八)


memory子系統

memory 子系統可以設定 cgroup 中任務使用的內存限制,並自動生成由那些任務使用的內存資源報告。memory子系統是通過linuxresource counter機制實現的。下面我們就先來看一下resource counter機制。

resource counter是內核為子系統提供的一種資源管理機制。這個機制的實現包括了用於記錄資源的數據結構和相關函數。Resource counter定義了一個res_counter的結構體來管理特定資源,定義如下:

struct res_counter {

unsigned long long usage;

unsigned long long max_usage;

unsigned long long limit;

unsigned long long soft_limit;

unsigned long long failcnt; /*

spinlock_t lock;

struct res_counter *parent;

};

 

Usage用於記錄當前已使用的資源,max_usage用於記錄使用過的最大資源量,limit用於設置資源的使用上限,進程組不能使用超過這個限制的資源,soft_limit用於設定一個軟上限,進程組使用的資源可以超過這個限制,failcnt用於記錄資源分配失敗的次數,管理可以根據這個記錄,調整上限值。Parent指向父節點,這個變量用於處理層次性的資源管理。

除了這個關鍵的數據結構,resource counter還定義了一系列相關的函數。下面我們來看幾個關鍵的函數。

void res_counter_init(struct res_counter *counter, struct res_counter *parent)

{

spin_lock_init(&counter->lock);

counter->limit = RESOURCE_MAX;

counter->soft_limit = RESOURCE_MAX;

counter->parent = parent;

}

這個函數用於初始化一個res_counter。

第二個關鍵的函數是int res_counter_charge(struct res_counter *counter, unsigned long val, struct res_counter **limit_fail_at)。當資源將要被分配的時候,資源就要被記錄到相應的res_counter里。這個函數作用就是記錄進程組使用的資源。在這個函數中有:

for (c = counter; c != NULL; c = c->parent) {

spin_lock(&c->lock);

ret = res_counter_charge_locked(c, val);

spin_unlock(&c->lock);

if (ret < 0) {

*limit_fail_at = c;

goto undo;

}

}

在這個循環里,從當前res_counter開始,從下往上逐層增加資源的使用量。我們來看一下res_counter_charge_locked這個函數,這個函數顧名思義就是在加鎖的情況下增加使用量。實現如下:

{

if (counter->usage + val > counter->limit) {

counter->failcnt++;

return -ENOMEM;

}

 

counter->usage += val;

if (counter->usage > counter->max_usage)

counter->max_usage = counter->usage;

return 0;

}

首先判斷是否已經超過使用上限,如果是的話就增加失敗次數,返回相關代碼;否則就增加使用量的值,如果這個值已經超過歷史最大值,則更新最大值。

第三個關鍵的函數是void res_counter_uncharge(struct res_counter *counter, unsigned long val)。當資源被歸還到系統的時候,要在相應的res_counter減輕相應的使用量。這個函數作用就在於在於此。實現如下:

for (c = counter; c != NULL; c = c->parent) {

spin_lock(&c->lock);

res_counter_uncharge_locked(c, val);

spin_unlock(&c->lock);

}

從當前counter開始,從下往上逐層減少使用量,其中調用了res_counter_uncharge_locked,這個函數的作用就是在加鎖的情況下減少相應的counter的使用量。

有這些數據結構和函數,只需要在內核分配資源的時候,植入相應的charge函數,釋放資源時,植入相應的uncharge函數,就能實現對資源的控制了。

介紹完resource counter,我們再來看memory子系統是利用resource counter實現對內存資源的管理的。

memory子系統定義了一個叫mem_cgroup的結構體來管理cgroup相關的內存使用信息,定義如下:

struct mem_cgroup {

struct cgroup_subsys_state css;

struct res_counter res;

struct res_counter memsw;

struct mem_cgroup_lru_info info;

spinlock_t reclaim_param_lock;

int prev_priority;

int last_scanned_child;

bool use_hierarchy;

atomic_t oom_lock;

atomic_t refcnt;

unsigned int swappiness;

int oom_kill_disable;

bool memsw_is_minimum;

struct mutex thresholds_lock;

struct mem_cgroup_thresholds thresholds;

struct mem_cgroup_thresholds memsw_thresholds;

struct list_head oom_notify;

unsigned long  move_charge_at_immigrate;

struct mem_cgroup_stat_cpu *stat;

};

跟其他子系統一樣,mem_cgroup也包含了一個cgroup_subsys_state成員,便於taskcgroup獲取mem_cgroup

mem_cgroup中包含了兩個res_counter成員,分別用於管理memory資源和memory+swap資源,如果memsw_is_minimumtrue,則res.limit=memsw.limit,即當進程組使用的內存超過memory的限制時,不能通過swap來緩解。

use_hierarchy則用來標記資源控制和記錄時是否是層次性的。

oom_kill_disable則表示是否使用oom-killer

oom_notify指向一個oom notifier event fd鏈表。

另外memory子系統還定義了一個叫page_cgroup的結構體:

struct page_cgroup {

unsigned long flags;

struct mem_cgroup *mem_cgroup;

struct page *page;

struct list_head lru; /* per cgroup LRU list */

};

此結構體可以看作是mem_map的一個擴展,每個page_cgroup都和所有的page關聯,而其中的mem_cgroup成員,則將page與特定的mem_cgroup關聯起來。

我們知道在linux系統中,page結構體是用來管理物理頁框的,一個物理頁框對應一個page結構體,而每個進程中的task_struct中都有一個mm_struct來管理進程的內存信息。每個mm_struct知道它屬於的進程,進而知道所屬的mem_cgroup,而每個page都知道它屬於的page_cgroup,進而也知道所屬的mem_cgroup,而內存使用量的計算是按cgroup為單位的,這樣以來,內存資源的管理就可以實現了。

memory子系統既然是通過resource counter實現的,那肯定會在內存分配給進程時進行charge操作的。下面我們就來看一下這些charge操作:

1.page fault發生時,有兩種情況內核需要給進程分配新的頁框。一種是進程請求調頁(demand paging),另一種是copy on write。內核在handle_pte_fault中進行處理。其中,do_linear_fault處理pte不存在且頁面線性映射了文件的情況,do_anonymous_page處理pte不存在且頁面沒有映射文件的情況,do_nonlinear_fault處理pte存在且頁面非線性映射文件的情況,do_wp_page則處理copy on write的情況。其中do_linear_faultdo_nonlinear_fault都會調用__do_fault來處理。Memory子系統則__do_faultdo_anonymous_pagedo_wp_page植入mem_cgroup_newpage_charge來進行charge操作。

2.內核在handle_pte_fault中進行處理時,還有一種情況是pte存在且頁又沒有映射文件。這種情況說明頁面之前在內存中,但是后面被換出到swap空間了。內核用do_swap_page函數處理這種情況,memory子系統在do_swap_page加入了mem_cgroup_try_charge_swapin函數進行chargemem_cgroup_try_charge_swapin是處理頁面換入時的charge的,當執行swapoff系統調用(關掉swap空間),內核也會執行頁面換入操作,因此mem_cgroup_try_charge_swapin也被植入到了相應的函數中。

3.當內核將page加入到page cache中時,也需要進行charge操作,mem_cgroup_cache_charge函數正是處理這種情況,它被植入到系統處理page cacheadd_to_page_cache_locked函數中。

4.最后mem_cgroup_prepare_migration是用於處理內存遷移中的charge操作。

除了charge操作,memory子系統還需要處理相應的uncharge操作。下面我們來看一下uncharge操作:

1.mem_cgroup_uncharge_page用於當匿名頁完全unmaped的時候。但是如果該pageswap cache的話,uncharge操作延遲到mem_cgroup_uncharge_swapcache被調用時執行。

2.mem_cgroup_uncharge_cache_page用於page cacheradix-tree刪除的時候。但是如果該pageswap cache的話,uncharge操作延遲到mem_cgroup_uncharge_swapcache被調用時執行。

3.mem_cgroup_uncharge_swapcache用於swap cache從radix-tree刪除的時候。Charge的資源會被算到swap_cgroup,如果mem+swap controller被禁用了,就不需要這樣做了。

4.mem_cgroup_uncharge_swap用於swap_entry的引用數減到0的時候。這個函數主要在mem+swap controller可用的情況下使用的。

5.mem_cgroup_end_migration用於內存遷移結束時相關的uncharge操作。

Charge函數最終都是通過調用__mem_cgroup_try_charge來實現的。在__mem_cgroup_try_charge函數中,調用res_counter_charge(&mem->res, csize, &fail_res)對memory進行charge,調用res_counter_charge(&mem->memsw, csize, &fail_res)memory+swap進行charge

Uncharge函數最終都是通過調用__do_uncharge來實現的。在__do_uncharge中,分別調用res_counter_uncharge(&mem->res,PAGE_SIZE)和res_counter_uncharge(&mem->memsw, PAGE_SIZE)uncharge memorymemory+swap

跟其他子系統一樣,memory子系統也實現了一個cgroup_subsys。

struct cgroup_subsys mem_cgroup_subsys = {

.name = "memory",

.subsys_id = mem_cgroup_subsys_id,

.create = mem_cgroup_create,

.pre_destroy = mem_cgroup_pre_destroy,

.destroy = mem_cgroup_destroy,

.populate = mem_cgroup_populate,

.can_attach = mem_cgroup_can_attach,

.cancel_attach = mem_cgroup_cancel_attach,

.attach = mem_cgroup_move_task,

.early_init = 0,

.use_id = 1,

};

Memory子系統中重要的文件有

memsw.limit_in_bytes

{

.name = "memsw.limit_in_bytes",

.private = MEMFILE_PRIVATE(_MEMSWAP, RES_LIMIT),

.write_string = mem_cgroup_write,

.read_u64 = mem_cgroup_read,

},

這個文件用於設定memory+swap上限值。

Limit_in_bytes

{

.name = "limit_in_bytes",

.private = MEMFILE_PRIVATE(_MEM, RES_LIMIT),

.write_string = mem_cgroup_write,

.read_u64 = mem_cgroup_read,

},

這個文件用於設定memory上限值。

 

作者曰:memory子系統的實現相當復雜,這篇文章只是簡單分析了一下實現框架,沒有去分析具體細節。要完全懂memory子系統,首先就要懂linux的memory管理,這個就不容易了,作者本人也只是略知一二,故不能更深入地去分析memory子系統的細節了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM