linux cgroups 概述
從 2.6.24 版本開始,linux 核心提供了一個叫做 cgroups(控制組)的特性。cgroups 就是 control groups 的縮寫,用來對一組程序所佔用的資源做限制、統計、隔離。也是目前輕量級虛擬化技術 lxc (linux container)的基礎之一。每一組程序就是一個控制組,也就是一個 cgroup。cgroups 分為幾個子系統,每個子系統代表一種設施或者說是資源控制器,用來排程某一類資源的使用,如 cpu 時鐘、記憶體、塊裝置 等。在實現上,cgroups 並沒有增加新的系統呼叫,而是表現為一個 cgroup 檔案系統,可以把一個或多個子系統掛載到某個目錄。如
mount -t cgroup -o cpu cpu /sys/fs/cgroup/cpu
就將 cpu 子系統掛載在了 /sys/fs/cgroup/cpu 。也可以在一個目錄上掛載多個子系統,甚至全部掛載到一個目錄也是可以的,不過我覺得,把每個子系統都掛載在不同目錄會有更好的靈活性。用 mount|awk '$5=="cgroup" {print $0}'
可以看到當前掛載的控制組。用 cat /proc/cgroups
可以看到當前所有控制組的狀態。下面這個指令碼,可以把全部子系統各種掛載到各自的目錄上去。
#!/bin/bash cgroot="${1:-/sys/fs/cgroup}" subsys="${2:-blkio cpu cpuacct cpuset devices freezer memory net_cls net_prio ns perf_event}" mount -t tmpfs cgroup_root "${cgroot}" for ss in $subsys; do mkdir -p "$cgroot/$ss" mount -t cgroup -o "$ss" "$ss" "$cgroot/$ss" done
看看那些目錄裡都有些啥,比如 ls 一下 /sys/fs/cgroup/cpu。
cgroup.event_control cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release tasks cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat release_agent
其中 “cpu.” 開頭的就是這個子系統裡特有的東西。其他的那些是每個子系統所對應目錄裡都有的。這些檔案就是用來讀取資源使用資訊和進行資源限制的。要建立一個控制組,就在需要的子系統裡建立一個目錄即可。如 mkdir /sys/fs/cgroup/cpu/foo
cgroups 的設定和資訊讀取是通過對那些檔案的讀寫來進行的。例如
# echo 2048 >/sys/fs/cgroup/cpu/foo/cpu.shares
就把 /foo 這個控制組的 cpu.shares 引數設為了 2048。
前面說,有些檔案是每個目錄裡共有的。那些就是通用的設定。其中,tasks 和 cgroups.procs 是用來管理控制組中的程序的。要把一個程序加入到某個控制組,把 pid 寫入到相應目錄的 tasks 檔案即可。如
# echo 5678 >/sys/fs/cgroup/cpu/foo/tasks
就把 5678 程序加入到了 /foo 控制組。那麼 tasks 和 cgroups.procs 有什麼區別呢?前面說的對“程序”的管理限制其實不夠準確。系統對任務排程的單位是執行緒。在這裡,tasks 中看到的就是執行緒 id。而 cgroups.procs 中是執行緒組 id,也就是一般所說的程序 id 。將一個一般的 pid 寫入到 tasks 中,只有這個 pid 對應的執行緒,以及由它產生的其他程序、執行緒會屬於這個控制組,原有的其他執行緒則不會。而寫入 cgroups.procs 會把當前所有的執行緒都加入進去。如果寫入 cgroups.procs 的不是一個執行緒組 id,而是一個一般的執行緒 id,那會自動找到所對應的執行緒組 id 加入進去。程序在加入一個控制組後,控制組所對應的限制會即時生效。想知道一個程序屬於哪些控制組,可以通過 cat /proc/<pid>/cgroup
檢視。
要把程序移出控制組,把 pid 寫入到根 cgroup 的 tasks 檔案即可。因為每個程序都屬於且只屬於一個 cgroup,加入到新的 cgroup 後,原有關係也就解除了。要刪除一個 cgroup,可以用 rmdir 刪除相應目錄。不過在刪除前,必須先讓其中的程序全部退出,對應子系統的資源都已經釋放,否則是無法刪除的。
前面都是通過檔案系統訪問方式來操作 cgroups 的。實際上,也有一組命令列工具。
lssubsys -am
可以檢視各子系統的掛載點,還有一組“cg”開頭的命令可以用來管理。其中 cgexec 可以用來直接在某些子系統中的指定控制組執行一個程式。如 cgexec -g "cpu,blkio:/foo" bash
。其他的命令和具體的引數可以通過 man 來檢視。
下面是個 bash 版的 cgexec,演示了 cgroups 的用法,也可以在不確定是否安裝命令列工具的情況下使用。
#!/bin/bash # usage: # ./cgexec.sh cpu:g1,memory:g2/g21 sleep 100 blkio_dir="/sys/fs/cgroup/blkio" memory_dir="/sys/fs/cgroup/memory" cpuset_dir="/sys/fs/cgroup/cpuset" perf_event_dir="/sys/fs/cgroup/perf_event" freezer_dir="/sys/fs/cgroup/freezer" net_cls_dir="/sys/fs/cgroup/net_cls" cpuacct_dir="/sys/fs/cgroup/cpuacct" cpu_dir="/sys/fs/cgroup/cpu" hugetlb_dir="/sys/fs/cgroup/hugetlb" devices_dir="/sys/fs/cgroup/devices" groups="$1" shift IFS=',' g_arr=($groups) for g in ${g_arr[@]}; do IFS=':' g_info=($g) if [ ${#g_info[@]} -ne 2 ]; then echo "bad arg $g" >&2 continue fi g_name=${g_info[0]} g_path=${g_info[1]} if [ "$g_path" == "${g_path#/}" ]; then g_path="/$g_path" fi echo $g_name $g_path var="${g_name}_dir" d=${!var} if [ -z "$d" ]; then echo "bad cg name $g_name" >&2 continue fi path="${d}${g_path}" if [ ! -d "$path" ]; then echo "cg not exists" >&2 continue fi echo "$$" >"${path}/tasks" done exec $*
cgroups 中的東西很多,本來打算只寫一篇的,後來覺著還是分成幾篇說得更明白些。之後還會寫一些具體使用的東西。