cgroup和namespace類似,也是將進程進行分組,但它的目的和namespace不一樣,namespace是為了隔離進程組之間的資源,而cgroup是為了對一組進程進行統一的資源監控和限制。
cgroup分v1和v2兩個版本,v1實現較早,功能比較多,但是由於它里面的功能都是零零散散的實現的,所以規划的不是很好,導致了一些使用和維護上的不便,v2的出現就是為了解決v1中這方面的問題,在最新的4.5內核中,cgroup v2聲稱已經可以用於生產環境了,但它所支持的功能還很有限,隨着v2一起引入內核的還有cgroup namespace。v1和v2可以混合使用,但是這樣會更復雜,所以一般沒人會這樣用。
本系列只介紹v1,因為這是目前大家正在用的版本,包括systemd,docker等。如果對v1比較熟悉的話,適應v2也不是問題。
本篇所有例子都在ubuntu-server-x86_64 16.04下執行通過
為什么需要cgroup
在Linux里,一直以來就有對進程進行分組的概念和需求,比如session group, progress group等,后來隨着人們對這方面的需求越來越多,比如需要追蹤一組進程的內存和IO使用情況等,於是出現了cgroup,用來統一將進程進行分組,並在分組的基礎上對進程進行監控和資源控制管理等。
什么是cgroup
術語cgroup在不同的上下文中代表不同的意思,可以指整個Linux的cgroup技術,也可以指一個具體進程組。
cgroup是Linux下的一種將進程按組進行管理的機制,在用戶層看來,cgroup技術就是把系統中的所有進程組織成一顆一顆獨立的樹,每棵樹都包含系統的所有進程,樹的每個節點是一個進程組,而每顆樹又和一個或者多個subsystem關聯,樹的作用是將進程分組,而subsystem的作用就是對這些組進行操作。cgroup主要包括下面兩部分:
-
subsystem 一個subsystem就是一個內核模塊,他被關聯到一顆cgroup樹之后,就會在樹的每個節點(進程組)上做具體的操作。subsystem經常被稱作"resource controller",因為它主要被用來調度或者限制每個進程組的資源,但是這個說法不完全准確,因為有時我們將進程分組只是為了做一些監控,觀察一下他們的狀態,比如perf_event subsystem。到目前為止,Linux支持12種subsystem,比如限制CPU的使用時間,限制使用的內存,統計CPU的使用情況,凍結和恢復一組進程等,后續會對它們一一進行介紹。
-
hierarchy 一個hierarchy可以理解為一棵cgroup樹,樹的每個節點就是一個進程組,每棵樹都會與零到多個subsystem關聯。在一顆樹里面,會包含Linux系統中的所有進程,但每個進程只能屬於一個節點(進程組)。系統中可以有很多顆cgroup樹,每棵樹都和不同的subsystem關聯,一個進程可以屬於多顆樹,即一個進程可以屬於多個進程組,只是這些進程組和不同的subsystem關聯。目前Linux支持12種subsystem,如果不考慮不與任何subsystem關聯的情況(systemd就屬於這種情況),Linux里面最多可以建12顆cgroup樹,每棵樹關聯一個subsystem,當然也可以只建一棵樹,然后讓這棵樹關聯所有的subsystem。當一顆cgroup樹不和任何subsystem關聯的時候,意味着這棵樹只是將進程進行分組,至於要在分組的基礎上做些什么,將由應用程序自己決定,systemd就是一個這樣的例子。
如何查看當前系統支持哪些subsystem
可以通過查看/proc/cgroups(since Linux 2.6.24)知道當前系統支持哪些subsystem,下面是一個例子
#subsys_name hierarchy num_cgroups enabled
cpuset 11 1 1
cpu 3 64 1
cpuacct 3 64 1
blkio 8 64 1
memory 9 104 1
devices 5 64 1
freezer 10 4 1
net_cls 6 1 1
perf_event 7 1 1
net_prio 6 1 1
hugetlb 4 1 1
pids 2 68 1
從左到右,字段的含義分別是:
-
subsystem的名字
-
subsystem所關聯到的cgroup樹的ID,如果多個subsystem關聯到同一顆cgroup樹,那么他們的這個字段將一樣,比如這里的cpu和cpuacct就一樣,表示他們綁定到了同一顆樹。如果出現下面的情況,這個字段將為0:
-
當前subsystem沒有和任何cgroup樹綁定
-
當前subsystem已經和cgroup v2的樹綁定
-
當前subsystem沒有被內核開啟
-
-
subsystem所關聯的cgroup樹中進程組的個數,也即樹上節點的個數
-
1表示開啟,0表示沒有被開啟(可以通過設置內核的啟動參數“cgroup_disable”來控制subsystem的開啟).
如何使用cgroup
cgroup相關的所有操作都是基於內核中的cgroup virtual filesystem,使用cgroup很簡單,掛載這個文件系統就可以了。一般情況下都是掛載到/sys/fs/cgroup目錄下,當然掛載到其它任何目錄都沒關系。
這里假設目錄/sys/fs/cgroup已經存在,下面用到的xxx為任意字符串,取一個有意義的名字就可以了,當用mount命令查看的時候,xxx會顯示在第一列
-
掛載一顆和所有subsystem關聯的cgroup樹到/sys/fs/cgroup
mount -t cgroup xxx /sys/fs/cgroup
掛載一顆和cpuset subsystem關聯的cgroup樹到/sys/fs/cgroup/cpuset
mkdir /sys/fs/cgroup/cpuset
mount -t cgroup -o cpuset xxx /sys/fs/cgroup/cpuset
掛載一顆與cpu和cpuacct subsystem關聯的cgroup樹到/sys/fs/cgroup/cpu,cpuacct
mkdir /sys/fs/cgroup/cpu,cpuacct
mount -t cgroup -o cpu,cpuacct xxx /sys/fs/cgroup/cpu,cpuacct
掛載一棵cgroup樹,但不關聯任何subsystem,下面就是systemd所用到的方式
mkdir /sys/fs/cgroup/systemd
mount -t cgroup -o none,name=systemd xxx /sys/fs/cgroup/systemd
在很多使用systemd的系統中,比如ubuntu 16.04,systemd已經幫我們將各個subsystem和cgroup樹關聯並掛載好了
dev@ubuntu:~$ mount|grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
創建並掛載好一顆cgroup樹之后,就有了樹的根節點,也即根cgroup,這時候就可以通過創建文件夾的方式創建子cgroup,然后再往每個子cgroup中添加進程。在后續介紹具體的subsystem的時候會詳細介紹如何操作cgroup。
注意
-
第一次掛載一顆和指定subsystem關聯的cgroup樹時,會創建一顆新的cgroup樹,當再一次用同樣的參數掛載時,會重用現有的cgroup樹,也即兩個掛載點看到的內容是一樣的。
#在ubuntu 16.04中,systemd已經將和cpu,cpuacct綁定的cgroup樹掛載到了/sys/fs/cgroup/cpu,cpuacct
dev@ubuntu:~$ mount|grep /sys/fs/cgroup/cpu,cpuacct
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct,nsroot=/)
#創建一個子目錄,用於后面的測試
dev@ubuntu:~$ sudo mkdir /sys/fs/cgroup/cpu,cpuacct/test
dev@ubuntu:~$ ls -l /sys/fs/cgroup/cpu,cpuacct/|grep test
drwxr-xr-x 2 root root 0 Oct 9 02:27 test
#將和cpu,cpuacct關聯的cgroup樹重新mount到另外一個目錄
dev@ubuntu:~$ mkdir -p ./cgroup/cpu,cpuacct && cd ./cgroup/
dev@ubuntu:~/cgroup$ sudo mount -t cgroup -o cpu,cpuacct new-cpu-cpuacct ./cpu,cpuacct
#在新目錄中看到的內容和/sys/fs/cgroup/cpu,cpuacct的一樣,
#說明我們將同一顆cgroup樹mount到了系統中的不同兩個目錄,
#這顆cgroup樹和subsystem的關聯關系不變,
#這點類似於mount同一塊硬盤到多個目錄
dev@ubuntu:~/cgroup$ ls -l ./cpu,cpuacct/ |grep test
drwxr-xr-x 2 root root 0 Oct 9 02:27 test
#清理
dev@ubuntu:~/cgroup$ sudo umount new-cpu-cpuacct
掛載一顆cgroup樹時,可以指定多個subsystem與之關聯,但一個subsystem只能關聯到一顆cgroup樹,一旦關聯並在這顆樹上創建了子cgroup,subsystems和這棵cgroup樹就成了一個整體,不能再重新組合。以上面ubuntu 16.04為例,由於已經將cpu,cpuacct和一顆cgroup樹關聯並且他們下面有子cgroup了,所以就不能單獨的將cpu和另一顆cgroup樹關聯。
#嘗試將cpu subsystem重新關聯一顆cgroup樹並且將這棵樹mount到./cpu目錄
dev@ubuntu:~/cgroup$ mkdir cpu
dev@ubuntu:~/cgroup$ sudo mount -t cgroup -o cpu new-cpu ./cpu
mount: new-cpu is already mounted or /home/dev/cgroup/cpu busy
#由於cpu和cpuacct已經和一顆cgroup樹關聯了,所以這里mount失敗
#嘗試將devices和pids關聯到同一顆樹上,由於他們各自已經關聯到了不同的cgroup樹,所以mount失敗
dev@ubuntu:~/cgroup$ mkdir devices,pids
dev@ubuntu:~/cgroup$ sudo mount -t cgroup -o devices,pids new-devices-pids ./devices,pids
mount: new-devices-pids is already mounted or /home/dev/cgroup/devices,pids busy
但由於/sys/fs/cgroup/hugetlb和/sys/fs/cgroup/perf_event下沒有子cgroup,我們可以將他們重新組合。一般情況下不會用到這個功能,一但最開始關聯好了之后,就不會去重新修改它,也即我們一般不會去修改systemd給我們設置好的subsystem和cgroup樹的關聯關系。
#/sys/fs/cgroup/hugetlb和/sys/fs/cgroup/perf_event里面沒有子目錄,說明沒有子cgroup
dev@ubuntu:~$ ls -l /sys/fs/cgroup/hugetlb|grep ^d
dev@ubuntu:~$ ls -l /sys/fs/cgroup/perf_event|grep ^d
#直接mount不行,因為perf_event,hugetlb已經被系統單獨mount過了
dev@ubuntu:~$ sudo mount -t cgroup -operf_event,hugetlb xxx /mnt
mount: xxx is already mounted or /mnt busy
#先umount
dev@ubuntu:~$ sudo umount /sys/fs/cgroup/perf_event
dev@ubuntu:~$ sudo umount /sys/fs/cgroup/hugetlb
#如果系統默認安裝了lxcfs的話,lxcfs會將它們掛載在自己的目錄,
#所以需要umount lxcfs及下面這兩個目錄,否則就沒有真正的umount掉perf_event和hugetlb
dev@ubuntu:~$ sudo umount lxcfs
dev@ubuntu:~$ sudo umount /run/lxcfs/controllers/hugetlb
dev@ubuntu:~$ sudo umount /run/lxcfs/controllers/perf_event
#再mount,成功
dev@ubuntu:~$ sudo mount -t cgroup -operf_event,hugetlb xxx /mnt
dev@ubuntu:~$ ls /mnt/
cgroup.clone_children cgroup.sane_behavior hugetlb.2MB.limit_in_bytes hugetlb.2MB.usage_in_bytes release_agent
cgroup.procs hugetlb.2MB.failcnt hugetlb.2MB.max_usage_in_bytes notify_on_release tasks
#清理
dev@ubuntu:~$ sudo reboot
可以創建任意多個不和任何subsystem關聯的cgroup樹,name是這棵樹的唯一標記,當name指定的是一個新的名字時,將創建一顆新的cgroup樹,但如果內核中已經存在一顆一樣name的cgroup樹,那么將mount已存在的這顆cgroup樹
#由於name=test的cgroup樹在系統中不存在,所以這里會創建一顆新的name=test的cgroup樹
dev@ubuntu:~$ mkdir -p cgroup/test && cd cgroup
dev@ubuntu:~/cgroup$ sudo mount -t cgroup -o none,name=test test ./test
#系統為新創建的cgroup樹的root cgroup生成了默認文件
dev@ubuntu:~/cgroup$ ls ./test/
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
#新創建的cgroup樹的root cgroup里包含系統中的所有進程
dev@ubuntu:~/cgroup$ wc -l ./test/cgroup.procs
131 ./test/cgroup.procs
#創建子cgroup
dev@ubuntu:~/cgroup$ cd test && sudo mkdir aaaa
#系統已經為新的子cgroup生成了默認文件
dev@ubuntu:~/cgroup/test$ ls aaaa
cgroup.clone_children cgroup.procs notify_on_release tasks
#新創建的子cgroup中沒有任何進程
dev@ubuntu:~/cgroup/test$ wc -l aaaa/cgroup.procs
0 aaaa/cgroup.procs
#重新掛載這棵樹到test1,由於mount的時候指定的name=test,所以和上面掛載的是同一顆cgroup樹,於是test1目錄下的內容和test目錄下的內容一樣
dev@ubuntu:~/cgroup/test$ cd .. && mkdir test1
dev@ubuntu:~/cgroup$ sudo mount -t cgroup -o none,name=test test ./test1
dev@ubuntu:~/cgroup$ ls ./test1
aaaa cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
#清理
dev@ubuntu:~/cgroup$ sudo umount ./test1
dev@ubuntu:~/cgroup$ sudo umount ./test
dev@ubuntu:~/cgroup$ cd .. && rm -r ./cgroup
如何查看當前進程屬於哪些cgroup
可以通過查看/proc/[pid]/cgroup(since Linux 2.6.24)知道指定進程屬於哪些cgroup。
dev@ubuntu:~$ cat /proc/777/cgroup
11:cpuset:/
10:freezer:/
9:memory:/system.slice/cron.service
8:blkio:/system.slice/cron.service
7:perf_event:/
6:net_cls,net_prio:/
5:devices:/system.slice/cron.service
4:hugetlb:/
3:cpu,cpuacct:/system.slice/cron.service
2:pids:/system.slice/cron.service
1:name=systemd:/system.slice/cron.service
每一行包含用冒號隔開的三列,他們的意思分別是
-
cgroup樹的ID, 和/proc/cgroups文件中的ID一一對應。
-
和cgroup樹綁定的所有subsystem,多個subsystem之間用逗號隔開。這里name=systemd表示沒有和任何subsystem綁定,只是給他起了個名字叫systemd。
-
進程在cgroup樹中的路徑,即進程所屬的cgroup,這個路徑是相對於掛載點的相對路徑。
所有的subsystems
目前Linux支持下面12種subsystem
-
cpu (since Linux 2.6.24; CONFIG_CGROUP_SCHED)
用來限制cgroup的CPU使用率。 -
cpuacct (since Linux 2.6.24; CONFIG_CGROUP_CPUACCT)
統計cgroup的CPU的使用率。 -
cpuset (since Linux 2.6.24; CONFIG_CPUSETS)
綁定cgroup到指定CPUs和NUMA節點。 -
memory (since Linux 2.6.25; CONFIG_MEMCG)
統計和限制cgroup的內存的使用率,包括process memory, kernel memory, 和swap。 -
devices (since Linux 2.6.26; CONFIG_CGROUP_DEVICE)
限制cgroup創建(mknod)和訪問設備的權限。 -
freezer (since Linux 2.6.28; CONFIG_CGROUP_FREEZER)
suspend和restore一個cgroup中的所有進程。 -
net_cls (since Linux 2.6.29; CONFIG_CGROUP_NET_CLASSID)
將一個cgroup中進程創建的所有網絡包加上一個classid標記,用於tc和iptables。 只對發出去的網絡包生效,對收到的網絡包不起作用。 -
blkio (since Linux 2.6.33; CONFIG_BLK_CGROUP)
限制cgroup訪問塊設備的IO速度。 -
perf_event (since Linux 2.6.39; CONFIG_CGROUP_PERF)
對cgroup進行性能監控 -
net_prio (since Linux 3.3; CONFIG_CGROUP_NET_PRIO)
針對每個網絡接口設置cgroup的訪問優先級。 -
hugetlb (since Linux 3.5; CONFIG_CGROUP_HUGETLB)
限制cgroup的huge pages的使用量。 -
pids (since Linux 4.3; CONFIG_CGROUP_PIDS)
限制一個cgroup及其子孫cgroup中的總進程數。
上面這些subsystem,有些需要做資源統計,有些需要做資源控制,有些即不統計也不控制。對於cgroup樹來說,有些subsystem嚴重依賴繼承關系,有些subsystem完全用不到繼承關系,而有些對繼承關系沒有嚴格要求。
不同subsystem的工作方式可能差別較大,對系統性能的影響也不一樣,本人不是這方面的專家,后續文章中只會從功能的角度來介紹不同的subsystem,不會涉及到他們內部的實現。
結束語
本文介紹了cgroup的一些概念,包括subsystem和hierarchy,然后介紹了怎么掛載cgroup文件系統以及12個subsystem的功能。從下一篇開始,將介紹cgroup具體的用法和不同的subsystem。
本文將創建並掛載一顆不和任何subsystem綁定的cgroup樹,用來演示怎么創建、刪除子cgroup,以及如何往cgroup中添加和刪除進程。
由於不和任何subsystem綁定,所以這棵樹沒有任何實際的功能,但這不影響我們的演示,還有一個好處就是我們不會受subsystem功能的影響,可以將精力集中在cgroup樹上。
本篇所有例子都在ubuntu-server-x86_64 16.04下執行通過
掛載cgroup樹
開始使用cgroup前需要先掛載cgroup樹,下面先看看如何掛載一顆cgroup樹,然后再查看其根目錄下生成的文件
#准備需要的目錄
dev@ubuntu:~$ mkdir cgroup && cd cgroup
dev@ubuntu:~/cgroup$ mkdir demo
#由於name=demo的cgroup樹不存在,所以系統會創建一顆新的cgroup樹,然后掛載到demo目錄
dev@ubuntu:~/cgroup$ sudo mount -t cgroup -o none,name=demo demo ./demo
#掛載點所在目錄就是這顆cgroup樹的root cgroup,在root cgroup下面,系統生成了一些默認文件
dev@ubuntu:~/cgroup$ ls ./demo/
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
#cgroup.procs里包含系統中的所有進程
dev@ubuntu:~/cgroup$ wc -l ./demo/cgroup.procs
131 ./demo/cgroup.procs
下面是每個文件的含義:
-
cgroup.clone_children
這個文件只對cpuset(subsystem)有影響,當該文件的內容為1時,新創建的cgroup將會繼承父cgroup的配置,即從父cgroup里面拷貝配置文件來初始化新cgroup,可以參考這里 -
cgroup.procs
當前cgroup中的所有進程ID,系統不保證ID是順序排列的,且ID有可能重復 -
notify_on_release
該文件的內容為1時,當cgroup退出時(不再包含任何進程和子cgroup),將調用release_agent里面配置的命令。新cgroup被創建時將默認繼承父cgroup的這項配置。 -
release_agent
里面包含了cgroup退出時將會執行的命令,系統調用該命令時會將相應cgroup的相對路徑當作參數傳進去。 注意:這個文件只會存在於root cgroup下面,其他cgroup里面不會有這個文件。 -
tasks
當前cgroup中的所有線程ID,系統不保證ID是順序排列的
后面在介紹如何往cgroup中添加進程時會介紹cgroup.procs和tasks的差別。
創建和刪除cgroup
掛載好上面的cgroup樹之后,就可以在里面建子cgroup了
#創建子cgroup很簡單,新建一個目錄就可以了
dev@ubuntu:~/cgroup$ cd demo
dev@ubuntu:~/cgroup/demo$ sudo mkdir cgroup1
#在新創建的cgroup里面,系統默認也生成了一些文件,這些文件的意義和root cgroup里面的一樣
dev@ubuntu:~/cgroup/demo$ ls cgroup1/
cgroup.clone_children cgroup.procs notify_on_release tasks
#新創建的cgroup里沒有任何進程和線程
dev@ubuntu:~/cgroup/demo$ wc -l cgroup1/cgroup.procs
0 cgroup1/cgroup.procs
dev@ubuntu:~/cgroup/demo$ wc -l cgroup1/tasks
0 cgroup1/tasks
#每個cgroup都可以創建自己的子cgroup,所以我們也可以在cgroup1里面創建子cgroup
dev@ubuntu:~/cgroup/demo$ sudo mkdir cgroup1/cgroup11
dev@ubuntu:~/cgroup/demo$ ls cgroup1/cgroup11
cgroup.clone_children cgroup.procs notify_on_release tasks
#刪除cgroup也很簡單,刪除掉相應的目錄就可以了
dev@ubuntu:~/cgroup/demo$ sudo rmdir cgroup1/
rmdir: failed to remove 'cgroup1/': Device or resource busy
#這里刪除cgroup1失敗,是因為它里面包含了子cgroup,所以不能刪除,
#如果cgroup1包含有進程或者線程,也會刪除失敗
#先刪除cgroup11,再刪除cgroup1就可以了
dev@ubuntu:~/cgroup/demo$ sudo rmdir cgroup1/cgroup11/
dev@ubuntu:~/cgroup/demo$ sudo rmdir cgroup1/
添加進程
創建新的cgroup后,就可以往里面添加進程了。注意下面幾點:
-
在一顆cgroup樹里面,一個進程必須要屬於一個cgroup。
-
新創建的子進程將會自動加入父進程所在的cgroup。
-
從一個cgroup移動一個進程到另一個cgroup時,只要有目的cgroup的寫入權限就可以了,系統不會檢查源cgroup里的權限。
-
用戶只能操作屬於自己的進程,不能操作其他用戶的進程,root賬號除外。
#--------------------------第一個shell窗口----------------------
#創建一個新的cgroup
dev@ubuntu:~/cgroup/demo$ sudo mkdir test
dev@ubuntu:~/cgroup/demo$ cd test
#將當前bash加入到上面新創建的cgroup中
dev@ubuntu:~/cgroup/demo/test$ echo $$
1421
dev@ubuntu:~/cgroup/demo/test$ sudo sh -c 'echo 1421 > cgroup.procs'
#注意:一次只能往這個文件中寫一個進程ID,如果需要寫多個的話,需要多次調用這個命令
#--------------------------第二個shell窗口----------------------
#重新打開一個shell窗口,避免第一個shell里面運行的命令影響輸出結果
#這時可以看到cgroup.procs里面包含了上面的第一個shell進程
dev@ubuntu:~/cgroup/demo/test$ cat cgroup.procs
1421
#--------------------------第一個shell窗口----------------------
#回到第一個窗口,運行top命令
dev@ubuntu:~/cgroup/demo/test$ top
#這里省略輸出內容
#--------------------------第二個shell窗口----------------------
#這時再在第二個窗口查看,發現top進程自動和它的父進程(1421)屬於同一個cgroup
dev@ubuntu:~/cgroup/demo/test$ cat cgroup.procs
1421
16515
dev@ubuntu:~/cgroup/demo/test$ ps -ef|grep top
dev 16515 1421 0 04:02 pts/0 00:00:00 top
dev@ubuntu:~/cgroup/demo/test$
#在一顆cgroup樹里面,一個進程必須要屬於一個cgroup,
#所以我們不能憑空從一個cgroup里面刪除一個進程,只能將一個進程從一個cgroup移到另一個cgroup,
#這里我們將1421移動到root cgroup
dev@ubuntu:~/cgroup/demo/test$ sudo sh -c 'echo 1421 > ../cgroup.procs'
dev@ubuntu:~/cgroup/demo/test$ cat cgroup.procs
16515
#移動1421到另一個cgroup之后,它的子進程不會隨着移動
#--------------------------第一個shell窗口----------------------
##回到第一個shell窗口,進行清理工作
#先用ctrl+c退出top命令
dev@ubuntu:~/cgroup/demo/test$ cd ..
#然后刪除創建的cgroup
dev@ubuntu:~/cgroup/demo$ sudo rmdir test
權限
上面我們都是用sudo(root賬號)來操作的,但實際上普通賬號也可以操作cgroup
#創建一個新的cgroup,並修改他的owner
dev@ubuntu:~/cgroup/demo$ sudo mkdir permission
dev@ubuntu:~/cgroup/demo$ sudo chown -R dev:dev ./permission/
#1421原來屬於root cgroup,雖然dev沒有root cgroup的權限,但還是可以將1421移動到新的cgroup下,
#說明在移動進程的時候,系統不會檢查源cgroup里的權限。
dev@ubuntu:~/cgroup/demo$ echo 1421 > ./permission/cgroup.procs
#由於dev沒有root cgroup的權限,再把1421移回root cgroup失敗
dev@ubuntu:~/cgroup/demo$ echo 1421 > ./cgroup.procs
-bash: ./cgroup.procs: Permission denied
#找一個root賬號的進程
dev@ubuntu:~/cgroup/demo$ ps -ef|grep /lib/systemd/systemd-logind
root 839 1 0 01:52 ? 00:00:00 /lib/systemd/systemd-logind
#因為該進程屬於root,dev沒有操作它的權限,所以將該進程加入到permission中失敗
dev@ubuntu:~/cgroup/demo$ echo 839 >./permission/cgroup.procs
-bash: echo: write error: Permission denied
#只能由root賬號添加
dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 839 >./permission/cgroup.procs'
#dev還可以在permission下創建子cgroup
dev@ubuntu:~/cgroup/demo$ mkdir permission/c1
dev@ubuntu:~/cgroup/demo$ ls permission/c1
cgroup.clone_children cgroup.procs notify_on_release tasks
#清理
dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 839 >./cgroup.procs'
dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 1421 >./cgroup.procs'
dev@ubuntu:~/cgroup/demo$ rmdir permission/c1
dev@ubuntu:~/cgroup/demo$ sudo rmdir permission
cgroup.procs vs tasks
上面提到cgroup.procs包含的是進程ID, 而tasks里面包含的是線程ID,那么他們有什么區別呢?
#創建兩個新的cgroup用於演示
dev@ubuntu:~/cgroup/demo$ sudo mkdir c1 c2
#為了便於操作,先給root賬號設置一個密碼,然后切換到root賬號
dev@ubuntu:~/cgroup/demo$ sudo passwd root
dev@ubuntu:~/cgroup/demo$ su root
root@ubuntu:/home/dev/cgroup/demo#
#系統中找一個有多個線程的進程
root@ubuntu:/home/dev/cgroup/demo# ps -efL|grep /lib/systemd/systemd-timesyncd
systemd+ 610 1 610 0 2 01:52 ? 00:00:00 /lib/systemd/systemd-timesyncd
systemd+ 610 1 616 0 2 01:52 ? 00:00:00 /lib/systemd/systemd-timesyncd
#進程610有兩個線程,分別是610和616
#將616加入c1/cgroup.procs
root@ubuntu:/home/dev/cgroup/demo# echo 616 > c1/cgroup.procs
#由於cgroup.procs存放的是進程ID,所以這里看到的是616所屬的進程ID(610)
root@ubuntu:/home/dev/cgroup/demo# cat c1/cgroup.procs
610
#從tasks中的內容可以看出,雖然只往cgroup.procs中加了線程616,
#但系統已經將這個線程所屬的進程的所有線程都加入到了tasks中,
#說明現在整個進程的所有線程已經處於c1中了
root@ubuntu:/home/dev/cgroup/demo# cat c1/tasks
610
616
#將616加入c2/tasks中
root@ubuntu:/home/dev/cgroup/demo# echo 616 > c2/tasks
#這時我們看到雖然在c1/cgroup.procs和c2/cgroup.procs里面都有610,
#但c1/tasks和c2/tasks中包含了不同的線程,說明這個進程的兩個線程分別屬於不同的cgroup
root@ubuntu:/home/dev/cgroup/demo# cat c1/cgroup.procs
610
root@ubuntu:/home/dev/cgroup/demo# cat c1/tasks
610
root@ubuntu:/home/dev/cgroup/demo# cat c2/cgroup.procs
610
root@ubuntu:/home/dev/cgroup/demo# cat c2/tasks
616
#通過tasks,我們可以實現線程級別的管理,但通常情況下不會這么用,
#並且在cgroup V2以后,將不再支持該功能,只能以進程為單位來配置cgroup
#清理
root@ubuntu:/home/dev/cgroup/demo# echo 610 > ./cgroup.procs
root@ubuntu:/home/dev/cgroup/demo# rmdir c1
root@ubuntu:/home/dev/cgroup/demo# rmdir c2
root@ubuntu:/home/dev/cgroup/demo# exit
exit
release_agent
當一個cgroup里沒有進程也沒有子cgroup時,release_agent將被調用來執行cgroup的清理工作。
#創建新的cgroup用於演示 dev@ubuntu:~/cgroup/demo$ sudo mkdir test #先enable release_agent dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 1 > ./test/notify_on_release' #然后創建一個腳本/home/dev/cgroup/release_demo.sh, #一般情況下都會利用這個腳本執行一些cgroup的清理工作,但我們這里為了演示簡單,僅僅只寫了一條日志到指定文件 dev@ubuntu:~/cgroup/demo$ cat > /home/dev/cgroup/release_demo.sh << EOF #!/bin/bash echo \$0:\$1 >> /home/dev/release_demo.log EOF #添加可執行權限 dev@ubuntu:~/cgroup/demo$ chmod +x ../release_demo.sh #將該腳本設置進文件release_agent dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo /home/dev/cgroup/release_demo.sh > ./release_agent' dev@ubuntu:~/cgroup/demo$ cat release_agent /home/dev/cgroup/release_demo.sh #往test里面添加一個進程,然后再移除,這樣就會觸發release_demo.sh dev@ubuntu:~/cgroup/demo$ echo $$ 27597 dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 27597 > ./test/cgroup.procs' dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 27597 > ./cgroup.procs' #從日志可以看出,release_agent被觸發了,/test是cgroup的相對路徑 dev@ubuntu:~/cgroup/demo$ cat /home/dev/release_demo.log /home/dev/cgroup/release_demo.sh:/test
結束語
本文介紹了如何操作cgroup,由於沒有和任何subsystem關聯,所以在這顆樹上的所有操作都沒有實際的功能,不會對系統有影響。從下一篇開始,將介紹具體的subsystem。
本篇將介紹一個簡單的subsystem,名字叫pids,功能是限制cgroup及其所有子孫cgroup里面能創建的總的task數量。
注意:這里的task指通過fork和clone函數創建的進程,由於clone函數也能創建線程(在Linux里面,線程是一種特殊的進程),所以這里的task也包含線程,本文統一以進程來代表task,即本文中的進程代表了進程和線程
本篇所有例子都在ubuntu-server-x86_64 16.04下執行通過
創建子cgroup
在ubuntu 16.04里面,systemd已經幫我們將各個subsystem和cgroup樹綁定並掛載好了,我們直接用現成的就可以了。
#從這里的輸出可以看到,pids已經被掛載在了/sys/fs/cgroup/pids,這是systemd做的
dev@dev:~$ mount|grep pids
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
創建子cgroup,取名為test
#進入目錄/sys/fs/cgroup/pids/並新建一個目錄,即創建了一個子cgroup
dev@dev:~$ cd /sys/fs/cgroup/pids/
dev@dev:/sys/fs/cgroup/pids$ sudo mkdir test
#這里將test目錄的owner設置成dev賬號,這樣后續操作就不用每次都敲sudo了,省去麻煩
dev@dev:/sys/fs/cgroup/pids$ sudo chown -R dev:dev ./test/
再來看看test目錄下的文件
#除了上一篇中介紹的那些文件外,多了兩個文件
dev@dev:/sys/fs/cgroup/pids$ cd test
dev@dev:/sys/fs/cgroup/pids/test$ ls
cgroup.clone_children cgroup.procs notify_on_release pids.current pids.max tasks
下面是這兩個文件的含義:
-
pids.current: 表示當前cgroup及其所有子孫cgroup中現有的總的進程數量
#由於這是個新創建的cgroup,所以里面還沒有任何進程
dev@dev:/sys/fs/cgroup/pids/test$ cat pids.current
0
pids.max: 當前cgroup及其所有子孫cgroup中所允許創建的總的最大進程數量,在根cgroup下沒有這個文件,原因顯而易見,因為我們沒有必要限制整個系統所能創建的進程數量。
#max表示沒做任何限制
dev@dev:/sys/fs/cgroup/pids/test$ cat pids.max
max
限制進程數
這里我們演示一下如何讓限制功能生效
#--------------------------第一個shell窗口----------------------
#將pids.max設置為1,即當前cgroup只允許有一個進程
dev@dev:/sys/fs/cgroup/pids/test$ echo 1 > pids.max
#將當前bash進程加入到該cgroup
dev@dev:/sys/fs/cgroup/pids/test$ echo $$ > cgroup.procs
#--------------------------第二個shell窗口----------------------
#重新打開一個bash窗口,在里面看看cgroup “test”里面的一些數據
#因為這是一個新開的bash,跟cgroup ”test“沒有任何關系,所以在這里運行命令不會影響cgroup “test”
dev@dev:~$ cd /sys/fs/cgroup/pids/test
#設置的最大進程數是1
dev@dev:/sys/fs/cgroup/pids/test$ cat pids.max
1
#目前test里面已經有了一個進程,說明不能在fork或者clone進程了
dev@dev:/sys/fs/cgroup/pids/test$ cat pids.current
1
#這個進程就是第一個窗口的bash
dev@dev:/sys/fs/cgroup/pids/test$ cat cgroup.procs
3083
#--------------------------第一個shell窗口----------------------
#回到第一個窗口,隨便運行一個命令,由於當前pids.current已經等於pids.max了,
#所以創建新進程失敗,於是命令運行失敗,說明限制生效
dev@dev:/sys/fs/cgroup/pids/test$ ls
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: Resource temporarily unavailable
當前cgroup和子cgroup之間的關系
當前cgroup中的pids.current和pids.max代表了當前cgroup及所有子孫cgroup的所有進程,所以子孫cgroup中的pids.max大小不能超過父cgroup中的大小,如果子cgroup中的pids.max設置的大於父cgroup里的大小,會怎么樣?請看下面的演示
#繼續使用上面的兩個窗口
#--------------------------第二個shell窗口----------------------
#將pids.max設置成2
dev@dev:/sys/fs/cgroup/pids/test$ echo 2 > pids.max
#在test下面創建一個子cgroup
dev@dev:/sys/fs/cgroup/pids/test$ mkdir subtest
dev@dev:/sys/fs/cgroup/pids/test$ cd subtest/
#將subtest的pids.max設置為5
dev@dev:/sys/fs/cgroup/pids/test/subtest$ echo 5 > pids.max
#將當前bash進程加入到subtest中
dev@dev:/sys/fs/cgroup/pids/test/subtest$ echo $$ > cgroup.procs
#--------------------------第三個shell窗口----------------------
#重新打開一個bash窗口,看一下test和subtest里面的數據
#test里面的數據如下:
dev@dev:~$ cd /sys/fs/cgroup/pids/test
dev@dev:/sys/fs/cgroup/pids/test$ cat pids.max
2
#這里為2表示目前test和subtest里面總的進程數為2
dev@dev:/sys/fs/cgroup/pids/test$ cat pids.current
2
dev@dev:/sys/fs/cgroup/pids/test$ cat cgroup.procs
3083
#subtest里面的數據如下:
dev@dev:/sys/fs/cgroup/pids/test$ cat subtest/pids.max
5
dev@dev:/sys/fs/cgroup/pids/test$ cat subtest/pids.current
1
dev@dev:/sys/fs/cgroup/pids/test$ cat subtest/cgroup.procs
3185
#--------------------------第一個shell窗口----------------------
#回到第一個窗口,隨便運行一個命令,由於test里面的pids.current已經等於pids.max了,
#所以創建新進程失敗,於是命令運行失敗,說明限制生效
dev@dev:/sys/fs/cgroup/pids/test$ ls
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: Resource temporarily unavailable
#--------------------------第二個shell窗口----------------------
#回到第二個窗口,隨便運行一個命令,雖然subtest里面的pids.max還大於pids.current,
#但由於其父cgroup “test”里面的pids.current已經等於pids.max了,
#所以創建新進程失敗,於是命令運行失敗,說明子cgroup中的進程數不僅受自己的pids.max的限制,
#還受祖先cgroup的限制
dev@dev:/sys/fs/cgroup/pids/test/subtest$ ls
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: Resource temporarily unavailable
pids.current > pids.max的情況
並不是所有情況下都是pids.max >= pids.current,在下面兩種情況下,會出現pids.max < pids.current 的情況:
-
設置pids.max時,將其值設置的比pids.current小
-
#繼續使用上面的三個窗口 #--------------------------第三個shell窗口---------------------- #將test的pids.max設置為1 dev@dev:/sys/fs/cgroup/pids/test$ echo 1 > pids.max dev@dev:/sys/fs/cgroup/pids/test$ cat pids.max 1 #這個時候就會出現pids.current > pids.max的情況 dev@dev:/sys/fs/cgroup/pids/test$ cat pids.current 2 #--------------------------第一個shell窗口---------------------- #回到第一個shell #還是運行失敗,說明雖然pids.current > pids.max,但限制創建新進程的功能還是會生效 dev@dev:/sys/fs/cgroup/pids/test$ ls -bash: fork: retry: No child processes -bash: fork: retry: No child processes -bash: fork: retry: No child processes -bash: fork: retry: No child processes -bash: fork: Resource temporarily unavailable
pids.max只會在當前cgroup中的進程fork、clone的時候生效,將其他進程加入到當前cgroup時,不會檢測pids.max,所以將其他進程加入到當前cgroup有可能會導致pids.current > pids.max
-
#繼續使用上面的三個窗口 #--------------------------第三個shell窗口---------------------- #將subtest中的進程移動到根cgroup下,然后刪除subtest dev@dev:/sys/fs/cgroup/pids/test$ sudo sh -c 'echo 3185 > /sys/fs/cgroup/pids/cgroup.procs' #里面沒有進程了,說明移動成功 dev@dev:/sys/fs/cgroup/pids/test$ cat subtest/cgroup.procs #移除成功 dev@dev:/sys/fs/cgroup/pids/test$ rmdir subtest/ #這時候test下的pids.max等於pids.current了 dev@dev:/sys/fs/cgroup/pids/test$ cat pids.max 1 dev@dev:/sys/fs/cgroup/pids/test$ cat pids.current 1 #--------------------------第二個shell窗口---------------------- #將當前bash加入到test中 dev@dev:/sys/fs/cgroup/pids/test/subtest$ cd .. dev@dev:/sys/fs/cgroup/pids/test$ echo $$ > cgroup.procs #--------------------------第三個shell窗口---------------------- #回到第三個窗口,查看相關信息 #第一個和第二個窗口的bash都屬於test dev@dev:/sys/fs/cgroup/pids/test$ cat cgroup.procs 3083 3185 dev@dev:/sys/fs/cgroup/pids/test$ cat pids.max 1 #出現了pids.current > pids.max的情況,這是因為我們將第二個窗口的shell加入了test dev@dev:/sys/fs/cgroup/pids/test$ cat pids.current 2 #--------------------------第二個shell窗口---------------------- #對fork調用的限制仍然生效 dev@dev:/sys/fs/cgroup/pids/test$ ls -bash: fork: retry: No child processes -bash: fork: retry: No child processes -bash: fork: retry: No child processes -bash: fork: retry: No child processes -bash: fork: Resource temporarily unavailable
清理
-
#--------------------------第三個shell窗口---------------------- dev@dev:/sys/fs/cgroup/pids/test$ sudo sh -c 'echo 3185 > /sys/fs/cgroup/pids/cgroup.procs' dev@dev:/sys/fs/cgroup/pids/test$ sudo sh -c 'echo 3083 > /sys/fs/cgroup/pids/cgroup.procs' dev@dev:/sys/fs/cgroup/pids/test$ cd .. dev@dev:/sys/fs/cgroup/pids$ sudo rmdir test/
結束語
本文介紹了如何利用pids這個subsystem來限制cgroup中的進程數,以及一些要注意的地方,總的來說pids比較簡單。下一篇將介紹稍微復雜點的內存控制。
為什么需要內存控制?
代碼總會有bug,有時會有內存泄漏,或者有意想不到的內存分配情況,或者這是個惡意程序,運行起來就是為了榨干系統內存,讓其它進程無法分配到足夠的內存而出現異常,如果系統配置了交換分區,會導致系統大量使用交換分區,從而系統運行很慢。
-
站在一個普通Linux開發者的角度,如果能控制一個或者一組進程所能使用的內存數,那么就算代碼有bug,內存泄漏也不會對系統造成影響,因為可以設置內存使用量的上限,當到達這個值之后可以將進程重啟。
-
站在一個系統管理者的角度,如果能限制每組進程所能使用的內存量,那么不管程序的質量如何,都能將它們對系統的影響降到最低,從而保證整個系統的穩定性。
內存控制能控制些什么?
-
限制cgroup中所有進程所能使用的物理內存總量
-
限制cgroup中所有進程所能使用的物理內存+交換空間總量(CONFIG_MEMCG_SWAP): 一般在server上,不太會用到swap空間,所以不在這里介紹這部分內容。
-
限制cgroup中所有進程所能使用的內核內存總量及其它一些內核資源(CONFIG_MEMCG_KMEM): 限制內核內存有什么用呢?其實限制內核內存就是限制當前cgroup所能使用的內核資源,比如進程的內核棧空間,socket所占用的內存空間等,通過限制內核內存,當內存吃緊時,可以阻止當前cgroup繼續創建進程以及向內核申請分配更多的內核資源。由於這塊功能被使用的較少,本篇中也不對它做介紹。
內核相關的配置
-
由於memory subsystem比較耗資源,所以內核專門添加了一個參數cgroup_disable=memory來禁用整個memory subsystem,這個參數可以通過GRUB在啟動系統的時候傳給內核,加了這個參數后內核將不再進行memory subsystem相關的計算工作,在系統中也不能掛載memory subsystem。
-
上面提到的CONFIG_MEMCG_SWAP和CONFIG_MEMCG_KMEM都是擴展功能,在使用前請確認當前內核是否支持,下面看看ubuntu 16.04的內核:
-
#這里CONFIG_MEMCG_SWAP和CONFIG_MEMCG_KMEM等於y表示內核已經編譯了該模塊,即支持相關功能 dev@dev:~$ cat /boot/config-`uname -r`|grep CONFIG_MEMCG CONFIG_MEMCG=y CONFIG_MEMCG_SWAP=y # CONFIG_MEMCG_SWAP_ENABLED is not set CONFIG_MEMCG_KMEM=y
-
CONFIG_MEMCG_SWAP控制內核是否支持Swap Extension,而CONFIG_MEMCG_SWAP_ENABLED(3.6以后的內核新加的參數)控制默認情況下是否使用Swap Extension,由於Swap Extension比較耗資源,所以很多發行版(比如ubuntu)默認情況下會禁用該功能(這也是上面那行被注釋掉的原因),當然用戶也可以根據實際情況,通過設置內核參數swapaccount=0或者1來手動禁用和啟用Swap Extension。
怎么控制?
在ubuntu 16.04里面,systemd已經幫我們將memory綁定到了/sys/fs/cgroup/memory
-
-
#如果這里發現有多行結果,說明這顆cgroup數被綁定到了多個地方, #不過不要擔心,由於它們都是指向同一顆cgroup樹,所以它們里面的內容是一模一樣的 dev@dev:~$ mount|grep memory cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
創建子cgroup
在/sys/fs/cgroup/memory下創建一個子目錄即創建了一個子cgroup
-
#--------------------------第一個shell窗口---------------------- dev@dev:~$ cd /sys/fs/cgroup/memory dev@dev:/sys/fs/cgroup/memory$ sudo mkdir test dev@dev:/sys/fs/cgroup/memory$ ls test cgroup.clone_children memory.kmem.failcnt memory.kmem.tcp.limit_in_bytes memory.max_usage_in_bytes memory.soft_limit_in_bytes notify_on_release cgroup.event_control memory.kmem.limit_in_bytes memory.kmem.tcp.max_usage_in_bytes memory.move_charge_at_immigrate memory.stat tasks cgroup.procs memory.kmem.max_usage_in_bytes memory.kmem.tcp.usage_in_bytes memory.numa_stat memory.swappiness memory.failcnt memory.kmem.slabinfo memory.kmem.usage_in_bytes memory.oom_control memory.usage_in_bytes memory.force_empty memory.kmem.tcp.failcnt memory.limit_in_bytes memory.pressure_level memory.use_hierarchy
從上面ls的輸出可以看出,除了每個cgroup都有的那幾個文件外,和memory相關的文件還不少(由於ubuntu默認禁用了CONFIG_MEMCG_SWAP,所以這里看不到swap相關的文件),這里先做個大概介紹(kernel相關的文件除外),后面會詳細介紹每個文件的作用
-
cgroup.event_control #用於eventfd的接口 memory.usage_in_bytes #顯示當前已用的內存 memory.limit_in_bytes #設置/顯示當前限制的內存額度 memory.failcnt #顯示內存使用量達到限制值的次數 memory.max_usage_in_bytes #歷史內存最大使用量 memory.soft_limit_in_bytes #設置/顯示當前限制的內存軟額度 memory.stat #顯示當前cgroup的內存使用情況 memory.use_hierarchy #設置/顯示是否將子cgroup的內存使用情況統計到當前cgroup里面 memory.force_empty #觸發系統立即盡可能的回收當前cgroup中可以回收的內存 memory.pressure_level #設置內存壓力的通知事件,配合cgroup.event_control一起使用 memory.swappiness #設置和顯示當前的swappiness memory.move_charge_at_immigrate #設置當進程移動到其他cgroup中時,它所占用的內存是否也隨着移動過去 memory.oom_control #設置/顯示oom controls相關的配置 memory.numa_stat #顯示numa相關的內存
添加進程
和“創建並管理cgroup”中介紹的一樣,往cgroup中添加進程只要將進程號寫入cgroup.procs就可以了
注意:本篇將以進程為單位進行操作,不考慮以線程為單位進行管理(原因見“創建並管理cgroup”中cgroup.pro與tasks的區別),也即只寫cgroup.procs文件,不會寫tasks文件
#--------------------------第二個shell窗口----------------------
#重新打開一個shell窗口,避免相互影響
dev@dev:~$ cd /sys/fs/cgroup/memory/test/
dev@dev:/sys/fs/cgroup/memory/test$ echo $$
4589
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo $$ >> cgroup.procs"
#運行top命令,這樣這個cgroup消耗的內存會多點,便於觀察
dev@dev:/sys/fs/cgroup/memory/test$ top
#后續操作不再在這個窗口進行,避免在這個bash中運行進程影響cgropu里面的進程數及相關統計
設置限額
設置限額很簡單,寫文件memory.limit_in_bytes就可以了,請仔細看示例
#--------------------------第一個shell窗口----------------------
#回到第一個shell窗口
dev@dev:/sys/fs/cgroup/memory$ cd test
#這里兩個進程id分別時第二個窗口的bash和top進程
dev@dev:/sys/fs/cgroup/memory/test$ cat cgroup.procs
4589
4664
#開始設置之前,看看當前使用的內存數量,這里的單位是字節
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.usage_in_bytes
835584
#設置1M的限額
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 1M > memory.limit_in_bytes"
#設置完之后記得要查看一下這個文件,因為內核要考慮頁對齊, 所以生效的數量不一定完全等於設置的數量
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.limit_in_bytes
1048576
#如果不再需要限制這個cgroup,寫-1到文件memory.limit_in_bytes即可
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo -1 > memory.limit_in_bytes"
#這時可以看到limit被設置成了一個很大的數字
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.limit_in_bytes
9223372036854771712
如果設置的限額比當前已經使用的內存少呢?如上面顯示當前bash用了800多k,如果我設置limit為400K會怎么樣?
#--------------------------第一個shell窗口----------------------
#先用free看下當前swap被用了多少
dev@dev:/sys/fs/cgroup/memory/test$ free
total used free shared buff/cache available
Mem: 500192 45000 34200 2644 420992 424020
Swap: 524284 16 524268
#設置內存限額為400K
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 400K > memory.limit_in_bytes"
#再看當前cgroup的內存使用情況
#發現內存占用少了很多,剛好在400K以內,原來用的那些內存都去哪了呢?
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.usage_in_bytes
401408
#再看swap空間的占用情況,和剛開始比,多了500-16=384K,說明內存中的數據被移到了swap上
dev@dev:/sys/fs/cgroup/memory/test$ free
total used free shared buff/cache available
Mem: 500192 43324 35132 2644 421736 425688
Swap: 524284 500 523784
#這個時候再來看failcnt,發現有453次之多(隔幾秒再看這個文件,發現次數在增長)
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.failcnt
453
#再看看memory.stat(這里只顯示部分內容),發現物理內存用了400K,
#但有很多pgmajfault以及pgpgin和pgpgout,說明發生了很多的swap in和swap out
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.stat
rss 409600
total_pgpgin 4166
total_pgpgout 4066
total_pgfault 7558
total_pgmajfault 419
#從上面的結果可以看出,當物理內存不夠時,就會觸發memory.failcnt里面的數量加1,
#但進程不會被kill掉,那是因為內核會嘗試將物理內存中的數據移動到swap空間中,從而讓內存分配成功
如果設置的限額過小,就算swap out部分內存后還是不夠會怎么樣?
#--------------------------第一個shell窗口----------------------
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 1K > memory.limit_in_bytes"
#進程已經不在了(第二個窗口已經掛掉了)
dev@dev:/sys/fs/cgroup/memory/test$ cat cgroup.procs
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.usage_in_bytes
0
#從這里的結果可以看出,第二個窗口的bash和top都被kill掉了
從上面的這些測試可以看出,一旦設置了內存限制,將立即生效,並且當物理內存使用量達到limit的時候,memory.failcnt的內容會加1,但這時進程不一定就會被kill掉,內核會盡量將物理內存中的數據移到swap空間上去,如果實在是沒辦法移動了(設置的limit過小,或者swap空間不足),默認情況下,就會kill掉cgroup里面繼續申請內存的進程。
觸發控制
當物理內存達到上限后,系統的默認行為是kill掉cgroup中繼續申請內存的進程,那么怎么控制這樣的行為呢?答案是配置memory.oom_control
這個文件里面包含了一個控制是否為當前cgroup啟動OOM-killer的標識。如果寫0到這個文件,將啟動OOM-killer,當內核無法給進程分配足夠的內存時,將會直接kill掉該進程;如果寫1到這個文件,表示不啟動OOM-killer,當內核無法給進程分配足夠的內存時,將會暫停該進程直到有空余的內存之后再繼續運行;同時,memory.oom_control還包含一個只讀的under_oom字段,用來表示當前是否已經進入oom狀態,也即是否有進程被暫停了。
注意:root cgroup的oom killer是不能被禁用的
為了演示OOM-killer的功能,創建了下面這樣一個程序,用來向系統申請內存,它會每秒消耗1M的內存。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define MB (1024 * 1024) int main(int argc, char *argv[]) { char *p; int i = 0; while(1) { p = (char *)malloc(MB); memset(p, 0, MB); printf("%dM memory allocated\n", ++i); sleep(1); } return 0; }
保存上面的程序到文件~/mem-allocate.c,然后編譯並測試
#--------------------------第一個shell窗口----------------------
#編譯上面的文件
dev@dev:/sys/fs/cgroup/memory/test$ gcc ~/mem-allocate.c -o ~/mem-allocate
#設置內存限額為5M
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 5M > memory.limit_in_bytes"
#將當前bash加入到test中,這樣這個bash創建的所有進程都會自動加入到test中
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo $$ >> cgroup.procs"
#默認情況下,memory.oom_control的值為0,即默認啟用oom killer
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.oom_control
oom_kill_disable 0
under_oom 0
#為了避免受swap空間的影響,設置swappiness為0來禁止當前cgroup使用swap
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 0 > memory.swappiness"
#當分配第5M內存時,由於總內存量超過了5M,所以進程被kill了
dev@dev:/sys/fs/cgroup/memory/test$ ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
Killed
#設置oom_control為1,這樣內存達到限額的時候會暫停
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 1 >> memory.oom_control"
#跟預期的一樣,程序被暫停了
dev@dev:/sys/fs/cgroup/memory/test$ ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
#--------------------------第二個shell窗口----------------------
#再打開一個窗口
dev@dev:~$ cd /sys/fs/cgroup/memory/test/
#這時候可以看到memory.oom_control里面under_oom的值為1,表示當前已經oom了
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.oom_control
oom_kill_disable 1
under_oom 1
#修改test的額度為7M
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 7M > memory.limit_in_bytes"
#--------------------------第一個shell窗口----------------------
#再回到第一個窗口,會發現進程mem-allocate繼續執行了兩步,然后暫停在6M那里了
dev@dev:/sys/fs/cgroup/memory/test$ ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
5M memory allocated
6M memory allocated
該文件還可以配合cgroup.event_control實現OOM的通知,當OOM發生時,可以收到相關的事件,下面是用於測試的程序,流程大概如下:
-
利用函數eventfd()創建一個efd;
-
打開文件memory.oom_control,得到ofd;
-
往cgroup.event_control中寫入這么一串:
<efd> <ofd>
-
通過讀efd得到通知,然后打印一句話到終端
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/eventfd.h> #include <errno.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> static inline void die(const char *msg) { fprintf(stderr, "error: %s: %s(%d)\n", msg, strerror(errno), errno); exit(EXIT_FAILURE); } #define BUFSIZE 256 int main(int argc, char *argv[]) { char buf[BUFSIZE]; int efd, cfd, ofd; uint64_t u; if ((efd = eventfd(0, 0)) == -1) die("eventfd"); snprintf(buf, BUFSIZE, "%s/%s", argv[1], "cgroup.event_control"); if ((cfd = open(buf, O_WRONLY)) == -1) die("cgroup.event_control"); snprintf(buf, BUFSIZE, "%s/%s", argv[1], "memory.oom_control"); if ((ofd = open(buf, O_RDONLY)) == -1) die("memory.oom_control"); snprintf(buf, BUFSIZE, "%d %d", efd, ofd); if (write(cfd, buf, strlen(buf)) == -1) die("write cgroup.event_control"); if (close(cfd) == -1) die("close cgroup.event_control"); for (;;) { if (read(efd, &u, sizeof(uint64_t)) != sizeof(uint64_t)) die("read eventfd"); printf("mem_cgroup oom event received\n"); } return 0; }
將上面的文件保存為~/oom_listen.c,然后測試如下
#--------------------------第二個shell窗口----------------------
#編譯程序
dev@dev:/sys/fs/cgroup/memory/test$ gcc ~/oom_listen.c -o ~/oom_listen
#啟用oom killer
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 0 >> memory.oom_control"
#設置限額為2M,縮短測試周期
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 2M > memory.limit_in_bytes"
#啟動監聽程序
dev@dev:/sys/fs/cgroup/memory/test$ ~/oom_listen /sys/fs/cgroup/memory/test
#--------------------------第一個shell窗口----------------------
#連續運行兩次mem-allocate,使它觸發oom killer
dev@dev:/sys/fs/cgroup/memory/test$ ~/mem-allocate
1M memory allocated
Killed
dev@dev:/sys/fs/cgroup/memory/test$ ~/mem-allocate
1M memory allocated
Killed
#--------------------------第二個shell窗口----------------------
#回到第二個窗口可以看到,收到了兩次oom事件
dev@dev:/sys/fs/cgroup/memory/test$ ~/oom_listen /sys/fs/cgroup/memory/test
mem_cgroup oom event received
mem_cgroup oom event received
其他
進程遷移(migration)
當一個進程從一個cgroup移動到另一個cgroup時,默認情況下,該進程已經占用的內存還是統計在原來的cgroup里面,不會占用新cgroup的配額,但新分配的內存會統計到新的cgroup中(包括swap out到交換空間后再swap in到物理內存中的部分)。
我們可以通過設置memory.move_charge_at_immigrate讓進程所占用的內存隨着進程的遷移一起遷移到新的cgroup中。
enable: echo 1 > memory.move_charge_at_immigrate
disable:echo 0 > memory.move_charge_at_immigrate
注意: 就算設置為1,但如果不是thread group的leader,這個task占用的內存也不能被遷移過去。換句話說,如果以線程為單位進行遷移,必須是進程的第一個線程,如果以進程為單位進行遷移,就沒有這個問題。
當memory.move_charge_at_immigrate被設置成1之后,進程占用的內存將會被統計到目的cgroup中,如果目的cgroup沒有足夠的內存,系統將嘗試回收目的cgroup的部分內存(和系統內存緊張時的機制一樣,刪除不常用的file backed的內存或者swap out到交換空間上,請參考Linux內存管理),如果回收不成功,那么進程遷移將失敗。
注意:遷移內存占用數據是比較耗時的操作。
移除cgroup
當memory.move_charge_at_immigrate為0時,就算當前cgroup中里面的進程都已經移動到其它cgropu中去了,由於進程已經占用的內存沒有被統計過去,當前cgroup有可能還占用很多內存,當移除該cgroup時,占用的內存需要統計到誰頭上呢?答案是依賴memory.use_hierarchy的值,如果該值為0,將會統計到root cgroup里;如果值為1,將統計到它的父cgroup里面。
force_empty
當向memory.force_empty文件寫入0時(echo 0 > memory.force_empty),將會立即觸發系統盡可能的回收該cgroup占用的內存。該功能主要使用場景是移除cgroup前(cgroup中沒有進程),先執行該命令,可以盡可能的回收該cgropu占用的內存,這樣遷移內存的占用數據到父cgroup或者root cgroup時會快些。
memory.swappiness
該文件的值默認和全局的swappiness(/proc/sys/vm/swappiness)一樣,修改該文件只對當前cgroup生效,其功能和全局的swappiness一樣,請參考Linux交換空間中關於swappiness的介紹。
注意:有一點和全局的swappiness不同,那就是如果這個文件被設置成0,就算系統配置的有交換空間,當前cgroup也不會使用交換空間。
memory.use_hierarchy
該文件內容為0時,表示不使用繼承,即父子cgroup之間沒有關系;當該文件內容為1時,子cgroup所占用的內存會統計到所有祖先cgroup中。
如果該文件內容為1,當一個cgroup內存吃緊時,會觸發系統回收它以及它所有子孫cgroup的內存。
注意: 當該cgroup下面有子cgroup或者父cgroup已經將該文件設置成了1,那么當前cgroup中的該文件就不能被修改。
#當前cgroup和父cgroup里都是1
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.use_hierarchy
1
dev@dev:/sys/fs/cgroup/memory/test$ cat ../memory.use_hierarchy
1
#由於父cgroup里面的值為1,所以修改當前cgroup的值失敗
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 0 > ./memory.use_hierarchy"
sh: echo: I/O error
#由於父cgroup里面有子cgroup(至少有當前cgroup這么一個子cgroup),
#修改父cgroup里面的值也失敗
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 0 > ../memory.use_hierarchy"
sh: echo: I/O error
memory.soft_limit_in_bytes
有了hard limit(memory.limit_in_bytes),為什么還要soft limit呢?hard limit是一個硬性標准,絕對不能超過這個值,而soft limit可以被超越,既然能被超越,要這個配置還有啥用?先看看它的特點
-
當系統內存充裕時,soft limit不起任何作用
-
當系統內存吃緊時,系統會盡量的將cgroup的內存限制在soft limit值之下(內核會盡量,但不100%保證)
從它的特點可以看出,它的作用主要發生在系統內存吃緊時,如果沒有soft limit,那么所有的cgroup一起競爭內存資源,占用內存多的cgroup不會讓着內存占用少的cgroup,這樣就會出現某些cgroup內存飢餓的情況。如果配置了soft limit,那么當系統內存吃緊時,系統會讓超過soft limit的cgroup釋放出超過soft limit的那部分內存(有可能更多),這樣其它cgroup就有了更多的機會分配到內存。
從上面的分析看出,這其實是系統內存不足時的一種妥協機制,給次等重要的進程設置soft limit,當系統內存吃緊時,把機會讓給其它重要的進程。
注意: 當系統內存吃緊且cgroup達到soft limit時,系統為了把當前cgroup的內存使用量控制在soft limit下,在收到當前cgroup新的內存分配請求時,就會觸發回收內存操作,所以一旦到達這個狀態,就會頻繁的觸發對當前cgroup的內存回收操作,會嚴重影響當前cgroup的性能。
memory.pressure_level
這個文件主要用來監控當前cgroup的內存壓力,當內存壓力大時(即已使用內存快達到設置的限額),在分配內存之前需要先回收部分內存,從而影響內存分配速度,影響性能,而通過監控當前cgroup的內存壓力,可以在有壓力的時候采取一定的行動來改善當前cgroup的性能,比如關閉當前cgroup中不重要的服務等。目前有三種壓力水平:
low
意味着系統在開始為當前cgroup分配內存之前,需要先回收內存中的數據了,這時候回收的是在磁盤上有對應文件的內存數據。
medium
意味着系統已經開始頻繁為當前cgroup使用交換空間了。
critical
快撐不住了,系統隨時有可能kill掉cgroup中的進程。
如何配置相關的監聽事件呢?和memory.oom_control類似,大概步驟如下:
-
利用函數eventfd(2)創建一個event_fd
-
打開文件memory.pressure_level,得到pressure_level_fd
-
往cgroup.event_control中寫入這么一串:
<event_fd> <pressure_level_fd> <level>
-
然后通過讀event_fd得到通知
注意: 多個level可能要創建多個event_fd,好像沒有辦法共用一個(本人沒有測試過)
Memory thresholds
我們可以通過cgroup的事件通知機制來實現對內存的監控,當內存使用量穿過(變得高於或者低於)我們設置的值時,就會收到通知。使用方法和memory.oom_control類似,大概步驟如下:
-
利用函數eventfd(2)創建一個event_fd
-
打開文件memory.usage_in_bytes,得到usage_in_bytes_fd
-
往cgroup.event_control中寫入這么一串:
<event_fd> <usage_in_bytes_fd> <threshold>
-
然后通過讀event_fd得到通知
stat file
這個文件包含的統計項比較細,需要一些內核的內存管理知識才能看懂,這里就不介紹了(怕說錯)。詳細信息可以參考Memory Resource Controller中的“5.2 stat file”。這里有幾個需要注意的地方:
-
里面total開頭的統計項包含了子cgroup的數據(前提條件是memory.use_hierarchy等於1)。
-
里面的'rss + file_mapped"才約等於是我們常說的RSS(ps aux命令看到的RSS)
-
文件(動態庫和可執行文件)及共享內存可以在多個進程之間共享,不過它們只會統計到他們的owner cgroup中的file_mapped去。(不確定是怎么定義owner的,但如果看到當前cgroup的file_mapped值很小,說明共享的數據沒有算到它頭上,而是其它的cgroup)
結束語
本篇沒有介紹swap和kernel相關的內容,不過在實際使用過程中一定要留意swap空間,如果系統使用了交換空間,那么設置限額時一定要注意一點,那就是當cgroup的物理空間不夠時,內核會將不常用的內存swap out到交換空間上,從而導致一直不觸發oom killer,而是不停的swap out/in,導致cgroup中的進程運行速度很慢。如果一定要用交換空間,最好的辦法是限制swap+物理內存的額度,雖然我們在這篇中沒有介紹這部分內容,但其使用方法和限制物理內存是一樣的,只是換做寫文件memory.memsw.limit_in_bytes罷了。
在cgroup里面,跟CPU相關的子系統有cpusets、cpuacct和cpu。
其中cpuset主要用於設置CPU的親和性,可以限制cgroup中的進程只能在指定的CPU上運行,或者不能在指定的CPU上運行,同時cpuset還能設置內存的親和性。設置親和性一般只在比較特殊的情況才用得着,所以這里不做介紹。
cpuacct包含當前cgroup所使用的CPU的統計信息,信息量較少,有興趣可以去看看它的文檔,這里不做介紹。
本篇只介紹cpu子系統,包括怎么限制cgroup的CPU使用上限及相對於其它cgroup的相對值。
本篇所有例子都在ubuntu-server-x86_64 16.04下執行通過
創建子cgroup
在ubuntu下,systemd已經幫我們mount好了cpu子系統,我們只需要在相應的目錄下創建子目錄就可以了
#從這里的輸出可以看到,cpuset被掛載在了/sys/fs/cgroup/cpuset,
#而cpu和cpuacct一起掛載到了/sys/fs/cgroup/cpu,cpuacct下面
dev@ubuntu:~$ mount|grep cpu
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
#進入/sys/fs/cgroup/cpu,cpuacct並創建子cgroup
dev@ubuntu:~$ cd /sys/fs/cgroup/cpu,cpuacct
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct$ sudo mkdir test
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct$ cd test
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct/test$ ls
cgroup.clone_children cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.stat tasks
cgroup.procs cpuacct.usage cpu.cfs_period_us cpu.shares notify_on_release
除了cgroup里面通用的cgroup.clone_children、tasks、cgroup.procs、notify_on_release這幾個文件外,以cpuacct.開頭的文件跟cpuacct子系統有關,我們這里只需要關注cpu.開頭的文件。
cpu.cfs_period_us & cpu.cfs_quota_us
cfs_period_us用來配置時間周期長度,cfs_quota_us用來配置當前cgroup在設置的周期長度內所能使用的CPU時間數,兩個文件配合起來設置CPU的使用上限。兩個文件的單位都是微秒(us),cfs_period_us的取值范圍為1毫秒(ms)到1秒(s),cfs_quota_us的取值大於1ms即可,如果cfs_quota_us的值為-1(默認值),表示不受cpu時間的限制。下面是幾個例子:
1.限制只能使用1個CPU(每250ms能使用250ms的CPU時間)
# echo 250000 > cpu.cfs_quota_us /* quota = 250ms */
# echo 250000 > cpu.cfs_period_us /* period = 250ms */
2.限制使用2個CPU(內核)(每500ms能使用1000ms的CPU時間,即使用兩個內核)
# echo 1000000 > cpu.cfs_quota_us /* quota = 1000ms */
# echo 500000 > cpu.cfs_period_us /* period = 500ms */
3.限制使用1個CPU的20%(每50ms能使用10ms的CPU時間,即使用一個CPU核心的20%)
# echo 10000 > cpu.cfs_quota_us /* quota = 10ms */
# echo 50000 > cpu.cfs_period_us /* period = 50ms */
cpu.shares
shares用來設置CPU的相對值,並且是針對所有的CPU(內核),默認值是1024,假如系統中有兩個cgroup,分別是A和B,A的shares值是1024,B的shares值是512,那么A將獲得1024/(1204+512)=66%的CPU資源,而B將獲得33%的CPU資源。shares有兩個特點:
-
如果A不忙,沒有使用到66%的CPU時間,那么剩余的CPU時間將會被系統分配給B,即B的CPU使用率可以超過33%
-
如果添加了一個新的cgroup C,且它的shares值是1024,那么A的限額變成了1024/(1204+512+1024)=40%,B的變成了20%
從上面兩個特點可以看出:
-
在閑的時候,shares基本上不起作用,只有在CPU忙的時候起作用,這是一個優點。
-
由於shares是一個絕對值,需要和其它cgroup的值進行比較才能得到自己的相對限額,而在一個部署很多容器的機器上,cgroup的數量是變化的,所以這個限額也是變化的,自己設置了一個高的值,但別人可能設置了一個更高的值,所以這個功能沒法精確的控制CPU使用率。
cpu.stat
包含了下面三項統計結果
-
nr_periods: 表示過去了多少個cpu.cfs_period_us里面配置的時間周期
-
nr_throttled: 在上面的這些周期中,有多少次是受到了限制(即cgroup中的進程在指定的時間周期中用光了它的配額)
-
throttled_time: cgroup中的進程被限制使用CPU持續了多長時間(納秒)
示例
這里以cfs_period_us & cfs_quota_us為例,演示一下如何控制CPU的使用率。
#繼續使用上面創建的子cgroup: test
#設置只能使用1個cpu的20%的時間
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct/test$ sudo sh -c "echo 50000 > cpu.cfs_period_us"
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct/test$ sudo sh -c "echo 10000 > cpu.cfs_quota_us"
#將當前bash加入到該cgroup
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct/test$ echo $$
5456
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct/test$ sudo sh -c "echo 5456 > cgroup.procs"
#在bash中啟動一個死循環來消耗cpu,正常情況下應該使用100%的cpu(即消耗一個內核)
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct/test$ while :; do echo test > /dev/null; done
#--------------------------重新打開一個shell窗口----------------------
#通過top命令可以看到5456的CPU使用率為20%左右,說明被限制住了
#不過這時系統的%us+%sy在10%左右,那是因為我測試的機器上cpu是雙核的,
#所以系統整體的cpu使用率為10%左右
dev@ubuntu:~$ top
Tasks: 139 total, 2 running, 137 sleeping, 0 stopped, 0 zombie
%Cpu(s): 5.6 us, 6.2 sy, 0.0 ni, 88.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 499984 total, 15472 free, 81488 used, 403024 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 383332 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
5456 dev 20 0 22640 5472 3524 R 20.3 1.1 0:04.62 bash
#這時可以看到被限制的統計結果
dev@ubuntu:~$ cat /sys/fs/cgroup/cpu,cpuacct/test/cpu.stat
nr_periods 1436
nr_throttled 1304
throttled_time 51542291833
結束語
使用cgroup限制CPU的使用率比較糾結,用cfs_period_us & cfs_quota_us吧,限制死了,沒法充分利用空閑的CPU,用shares吧,又沒法配置百分比,極其難控制。總之,使用cgroup的cpu子系統需謹慎。
資料摘自:https://segmentfault.com/a/1190000009732550