cgroup的實現相對namespace要複雜一些,網上也有一些代碼分析,大家對代碼分析的興趣估計也不大,所以這裏就不放代碼分析了,主要對其使用進行說明,麼麼噠。
Cgroup是linux內核集成的資源控制機制,cgroup與用戶態交互通過特殊文件系統cgroup文件系統,進行交互,所有設置或者查看cgroup的動作都可以通過cgroup文件系統下的文件完成,因此除了編譯內核的時候需要打開特定的選項之外,還需在系統啓動之後掛載cgroup文件系統方能使用cgroup的控制功能。幾個重要概念:
Ø cgroup: 一組進程的行爲控制。要對某個進程進行資源限制,就將進程添加到cgroup中,一個cgroup可以有多個進程。
Ø hierarchy: 一組cgroup的集合,可以理解成cgroup的根,cgroup是hierarchy的結點。一個hierarchy的資源限制爲1,代表擁有系統所有資源,cgroup資源限制小於1,分配系統資源。
Ø subsystem: cgroup可以進行多種資源限制,某種資源限制就是一個subsystem,所有的subsystem構成了資源限制的所有資源種類。
三者之間的主要關係:
Ø 創建新hierarchy時,系統中的所有任務都是那個hierarchy的根cgroup的初始成員。
Ø 一個subsystem最多隻能附加到一個hierarchy。
Ø 一個hierarchy可以附加多個subsystem。
Ø 一個進程可以是多個cgroup的成員,但是這些cgroup必須在不同的hierarchy下。
Ø 創建子進程時,子進程自動成爲父進程所在 cgroup 的成員。可根據需要將該子進程移動到不同的 cgroup 中。
Cgroup包含了11個子系統,分別是:
Ø Blkio:控制塊設備的訪問,比如帶寬等。
Ø cpu :控制進程佔用cpu的多少。
Ø Cpuacct: 記錄cgroup 中進程使用的 CPU 情況。
Ø Cpuset:爲 cgroup 中的進程分配CPU和內存節點。
Ø Devices:控制進程對設備的訪問。
Ø Freezer:掛起或者恢復 cgroup 中的進程。
Ø Memory:設定 cgroup 中進程的內存限制,統計使用的內存資源。
Ø net_cls:使用等級識別符(classid)標記網絡數據包,使得Linux 流量控制程序(tc)識別具體 cgroup 中的數據包。
Ø Ns:命名空間子系統,默認創建cgroup的時候會創建命名空間,新的內核不支持。
Ø Debug:用於調試。
Ø Perf:按cgroup進行性能統計。
Cgroup與用戶態的交互通過cgroup文件系統完成,cgroup的每一控制箱在cgroup文件系統下都會對應一個文件,通過對文件的讀寫,來實現資源的控制。cgroup文件系統的定義:
static struct file_system_type cgroup_fs_type = {
.name = "cgroup",
.get_sb = cgroup_get_sb,
.kill_sb = cgroup_kill_sb,
};
定義了兩個函數指針,定義了一個文件系統必須實現了的兩個操作get_sb,kill_sb,即獲得超級塊和釋放超級塊。這兩個操作會在使用mount系統調用掛載cgroup文件系統時使用。
cgroup 超級塊的定義:
static const struct super_operations cgroup_ops = {
.statfs = simple_statfs,
.drop_inode = generic_delete_inode,
.show_options = cgroup_show_options,
.remount_fs = cgroup_remount,
};
cgroup 索引塊定義:
static const struct inode_operations cgroup_dir_inode_operations = {
.lookup = simple_lookup,
.mkdir = cgroup_mkdir,
.rmdir = cgroup_rmdir,
.rename = cgroup_rename,
};
在cgroup文件系統中,使用mkdir創建cgroup或者用rmdir刪除cgroup時,就會調用相應的函數指針指向的函數。
cgroup 文件操作定義:
static const struct file_operations cgroup_file_operations = {
.read = cgroup_file_read,
.write = cgroup_file_write,
.llseek = generic_file_llseek,
.open = cgroup_file_open,
.release = cgroup_file_release,
};
對cgroup目錄下的控制文件進行操作時,會調用該操作函數。而這些函數將會根據訪問的不同的文件調用其對應的操作函數。cgroup文件系統爲cgroups控制文件定義了一個cftype的結構體。cftype的定義:
struct cftype {
char name[MAX_CFTYPE_NAME];
int private; /*
mode_t mode;
size_t max_write_len;
int (*open)(struct inode *inode, struct file *file);
ssize_t (*read)(struct cgroup *cgrp, struct cftype *cft,
struct file *file,
char __user *buf, size_t nbytes, loff_t *ppos);
u64 (*read_u64)(struct cgroup *cgrp, struct cftype *cft);
s64 (*read_s64)(struct cgroup *cgrp, struct cftype *cft);
int (*read_map)(struct cgroup *cont, struct cftype *cft,
struct cgroup_map_cb *cb);
int (*read_seq_string)(struct cgroup *cont, struct cftype *cft,
struct seq_file *m);
ssize_t (*write)(struct cgroup *cgrp, struct cftype *cft,
struct file *file,
const char __user *buf, size_t nbytes, loff_t *ppos);
int (*write_u64)(struct cgroup *cgrp, struct cftype *cft, u64 val);
int (*write_s64)(struct cgroup *cgrp, struct cftype *cft, s64 val);
int (*write_string)(struct cgroup *cgrp, struct cftype *cft,
const char *buffer);
int (*trigger)(struct cgroup *cgrp, unsigned int event);
int (*release)(struct inode *inode, struct file *file);
int (*register_event)(struct cgroup *cgrp, struct cftype *cft,
struct eventfd_ctx *eventfd, const char *args);/*
void (*unregister_event)(struct cgroup *cgrp, struct cftype *cft,
struct eventfd_ctx *eventfd);
};
cftype中除了定義文件的名字和相關權限標記外,還定義了對文件進行操作的函數指針。不同的文件可以有不同的操作,因爲不同的文件涉及到的是內核不同的子系統不同的功能。
1.1.1. Cpu
cpuset 子系統爲 cgroup 分配獨立 CPU 和內存節點。
typedef enum {
CS_CPU_EXCLUSIVE,
CS_MEM_EXCLUSIVE,
CS_MEM_HARDWALL,
CS_MEMORY_MIGRATE, /*進行內存遷移*/
CS_SCHED_LOAD_BALANCE, /*該set下的cpu進行負載均衡*/
CS_SPREAD_PAGE, /*和後面的兩個標誌共同設置平均使用允許的內存節點*/
CS_SPREAD_SLAB,
} cpuset_flagbits_t;
如果系統支持熱插拔,那麼熱插拔之後如果cpuset的cpu集合或者內存節點集合爲空,該cpuset關聯的進程將移動到上層非空cpuset中。
同一層的task_group和進程被當成同樣的調度實體來選擇,當被選到的是task_group時,則對task_group的孩子節點重複這個過程,直到選到一個運行的進程。當設置一個cgroup的shares值時,該cgroup當作一個整體和剩下的進程或其他cgroup分享cpu時間。比如,在根cgroup下建立cgroup A,將其shares值設1024,再建立cgroup B,將其shares設爲2048,再將一些進程分別加入到這兩個cgroup中,則長期調度的結果應該是A:B:C=1:2:1(C是系統中未加入到A或B的進程)。
Cpu子系統則爲cgroup分配時間比例,cpuacct則統計cgroup中進程使用的時間。
項目 |
功能 |
說明 |
||||||||||||||
Cpuset.cpus |
限制進程能夠使用的cpu節點 |
|
||||||||||||||
Cpuset.mems |
限制進程能夠使用的內存節點 |
|
||||||||||||||
cpu.shares |
假設cgroup A的tasks的cpu.shares值爲1,cgroup B的tasks的cpu.shares值爲2,則cgroup B的進程佔用的cpu時間是cgroup A上進程的2倍 |
|
||||||||||||||
以微秒爲單位指定在某個時間段中 cgroup 中的實時任務對 CPU 資源的最長連續訪問時間 |
|
|||||||||||||||
cpu.rt_period_us |
以微秒爲單位指定在某個時間段中 cgroup 對 CPU 資源訪問重新分配的頻率 |
|
||||||||||||||
cpuset.cpu_exclusive |
是否共享該cgroup指定的cpu,設置爲1一個cpu只能出現在一個cgroup裏面 |
|
||||||||||||||
cpuset.mem_exclusive |
是否共享該cgroup指定的memory node |
|
||||||||||||||
cpuset.sched_load_balance |
是否對cgroup的所有CPU做負載平衡 |
|
||||||||||||||
cpuset.sched_relax_domain_level |
包含 -1 到小正數間的整數,它代表內核應嘗試平衡負載的 CPU 寬度範圍。
|
|
||||||||||||||
cpuset.memory_migrate |
cpuset.mems 中的值更改時是否應該將內存中的頁遷移到新節點的標籤(0 或者 1) |
|
||||||||||||||
cpuset.mem_hardwall |
是否應將內存頁面的內核分配限制在爲這個 cpuset 指定的內存節點 |
|
||||||||||||||
cpuset.memory_pressure |
cgroup產生的平均內存壓力 |
|
||||||||||||||
cpuset.memory_pressure_enabled |
是否計算cgroup中的內存壓力 |
|
||||||||||||||
cpuset.memory_spread_page |
是否將文件系統緩存平均分配到cgroup的內存節點上 |
|
||||||||||||||
cpuset.memory_spread_slab |
是否將用於文件輸入輸出緩衝平均分配到cgroup的內存節點上 |
|
新的實現增加了對cpu上限的設置,其值設置方法和命名都和cpu.rt_runtime_us和cpu.rt_period_us差不多,這個文檔早的時候寫的,這裏偷個懶就不補充了。
1.1.2. Mem
memory 子系統可以設定 cgroup 中進程使用的內存限制,並自動統計進程使用的內存資源。memory子系統通過linux的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指向父節點,這個變量用於處理層次性的資源管理。
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 intswappiness;
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中包含了兩個res_counter成員,分別用於管理memory資源和memory+swap資源,如果memsw_is_minimum爲true,則res.limit=memsw.limit,即當進程組使用的內存超過memory的限制時,不能通過swap來緩解。
Cgroup通過在分配內存,換入換出的時候進行計數來管理cgroup中內存。內核線程如果屬於某個cgroup,其使用內存同樣受到cgroup控制。頁高速緩存的內存也在cgroup控制之內。
項目 |
功能 |
說明 |
Memory.stat |
當前cgroup中的內存實時狀況 |
|
Memory.usage_in_bytes |
當前cgroup中所有進程使用的物理內存之和 |
單位bytes |
Memory.memsw.usage_in_bytes |
當前cgroup中所有進程使用的物理內存和swap之和 |
|
Memory.max.usage_in_bytes |
當前cgroup使用物理內存最大值 |
|
Memory.memsw.max_usage_in_bytes |
當前cgroup使用物理內存和swap之和的最大值 |
|
Memory.limit_in_bytes |
設置當前cgroup的物理內存的上限值(包括頁高速緩存) |
|
Memory.memsw.limit_in_bytes |
對物理內存和swap之和的限制 |
|
Memory.failcnt |
物理內存值達到所設置上限值的次數 |
|
Memory.memsw.failcnt |
物理內存和swap之和達到所設置上限值的次數 |
|
Memory.force_empty |
釋放cgroup使用的所有內存 |
只有在cgroup中沒有進程時才使用 |
Memory.swappines |
交換值,用於計算交換傾向經驗值 |
|
Memory.use_hierarchy |
指定內存回收是否按一個hierarchy來進行,如果設置爲1,內存子系統從其中一個超過限額的子cgroup中回收內存,如果設置爲0,則只是從當前cgroup中的進程回收 |
|
Memory.oom_control |
內存超過限制並且回收失敗後事後通過oom_killer機制處理 |
|
Memory.soft_limit_in_bytes |
內存軟限制 |
|
Memory.move_charge_at_immigrate |
頁遷移計數控制 |
|
Memory.numa.stat |
每個節點的內存控制信息 |
|
Memory.tcp.limit_in_bytes |
當前cgroup能使用的tcp內存總大小 |
|
Memory.tcp.usage_in_bytes |
當前cgroup所有進程使用的tcp內存量 |
|
Memory_kmem.tcp.filcnt |
當前cgroup中所有進程tcp緩存之和超過限制的次數 |
|
Memory.keme.tcp.max_usage_in_bytes |
當前cgroup中tcp使用緩存的峯值 |
|
1.1.3. Device
devices只控制塊設備和字符設備的訪問。有inode的設備文件通過在open中對比權限來控制cgroup中的進程能夠訪問的devices。inode不存在的設備就必須通過mknod中對比權限來控制進程對設備的訪問。
項目 |
功能 |
說明 |
Devices.allow |
允許cgroup中進程訪問的設備名 |
|
Devices.deny |
禁止cgroup中進程訪問的設備名 |
|
Devices.list |
Cgroup中進程訪問的設備 |
|
1.1.4. Net
網絡的資源控制通過kernel的tc實現。
項目 |
功能 |
說明 |
Net_cls.classid |
流量控制中cgroup分類器的id號 |
|
設置某個設備上的優先級 |
|
|
Net_prio.prioidx |
全網唯一的一個標識,只讀 |
|
1.1.5. Blkio
cgroup的blkio子系統管理塊設備訪問,有2種限制模式:
Ø throttle,限制每個進程能使用的iops或者bps。
Ø weight,限制每個進程能使用的iops的比例,必須通過CFQ調度器來實現。可以通過修改/sys/block/sda/queue/scheduler文件完成。io帶寬相對控制只是針對受控對象都很繁忙的時候控制其上限速度,在某個cgroup比較閒的時候,不再滿足這個規律,其他cgroup可能消耗掉更多的帶寬。
要使用blkio的weight限制需要注意:
Ø 必須direct io, 如果buffered io因爲最終寫IO的進程不是發起IO的進程,結果會有很大的偏差。
Ø 調度器必須是CFQ。
在CFQ調度器中,所有的異步請求都屬於根cgroup,不受子cgroup的限制,因此在子cgroup中對異步io進行資源限制沒有意義。
項目 |
功能 |
說明 |
blkio.weight |
Io訪問能力的相對權重 |
|
blkio.weight_device |
特定設備的io帶寬的相對權重 |
|
blkio.throttle.read_bps_device |
特定設備的讀bps上限值 |
|
blkio.throttle.write_bps_device |
特定設備的寫bps上限值 |
|
blkio.throttle.read_iops_device |
特定設備的讀iops上限值 |
|
blkio.throttle.write_iops_device |
特定設備的寫iops上限值 |
|
blkio.io_merged |
請求合並到I/O操作請求的次數 |
|
blkio.io_queued |
Cgroup I/O操作排隊的請求次數 |
|
blkio.io_service_bytes |
轉換到具體設備或者由具體設備中轉換出的字節數 |
|
blkio.io_serviced |
在具體設備中執行的I/O操作數 |
|
blkio.io_service_time |
在具體設備中的I/O操作請求發送和請求完成之間的時間 |
|
blkio.io_wait_time |
所有io操作在隊列中等待的時間總和 |
|
blkio.reset_stats |
重新設定在其它僞文件中記錄的統計數據 |
|
blkio.sectors |
轉換到具體設備或者由具體設備轉換出的扇區數 |
|
轉換到具體設備或者由具體設備中轉換出的字節數 |
|
|
blkio.throttle.io_serviced |
在具體設備中執行的I/O操作數 |
|
blkio.time |
對具體設備的I/O訪問時間 |
|
blkio.avg_queue_size |
I/O操作的平均隊列大小 |
|
blkio.group_wait_time |
每個隊列等待獲取時間片的等待時間總和 |
|
blkio.empty_time |
不包括等待時間在內的處理時間總和 |
|
blkio.idle_time |
Io調度程序等待更好的請求時空轉的時間 |
|
blkio.dequeue |
I/O操作請求被具體設備從隊列中移除的次數 |
|