docker cgroup 技術之memory(首篇)


  測試環境centos7 ,內核版本4.20

內核使用cgroup對進程進行分組,並限制進程資源和對進程進行跟蹤。內核通過名為cgroupfs類型的虛擬文件系統來提供cgroup功能接口。cgroup有如下2個概念:

  • subsystem:用於控制cgroup中的進程行為的內核組件,可以在/proc/cgroups查看所有支持的subsystem,subsystem也別稱為resource controller;第二列為croup id;第三列為cgroup中進程數目。
# cat /proc/cgroups
#subsys_name    hierarchy   num_cgroups enabled
    cpuset          8           6           1
    cpu             7           105         1
    cpuacct         7           105         1
    blkio           5           105         1
    memory          3           327         1
    devices         6           106         1
    freezer         4           6           1
    net_cls         2           6           1
    perf_event      11          6           1
    net_prio        2           6           1
    hugetlb         9           6           1
    pids            12          106         1
    rdma            10          1           1
  • hierarchy:由cgroup組成的層級樹,每個hierarchy都對應一個cgroup虛擬文件系統,每個hierarchy都有系統上的所有task,此外低level的hierarchy不能超過高level設定的資源上限

  系統默認會掛載cgroup,路徑為/sys/fs/cgroup,查看當前系統掛載的cgroup,可以看到在默認路徑下掛載了所有的子系統。后續可以直接使用下述hierarchy作為父hierarchy。進程的cgroup可以在/proc/$pid/cgroup文件中查看。

# mount|grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,seclabel,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,rdma)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpu,cpuacct)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_cls,net_prio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)

 cgroup有如下4個規則:

    • 一個hierarchy可以有一個或多個subsystem,這個從/sys/fs/cgroup中可以看出來cpu和cpuacct可以同屬於一個hierarchy,而memory則僅屬於一個hierarchy;
    • 一個subsystem不能掛載到一個已經掛載了不同subsystem的hierarchy上(下面講);
    • 一個task不能同時存在於同一個hierarchy下的兩個cgoup中,但可以存在於不同類型的hierarchy中;如下例中,在hierarchy memory中創建2個cgroup mem1和mem2,可以看到將當前bash進程寫入到mem2/tasks之后,mem1/tasks中的內容就會被清空。注:刪除cgroup之前需要退出所有attach到該cgroup的進程,如下面的進程為bash,exit退出即可
[root@ memory]# echo $$
9439
[root@ memory]# echo $$ > mem1/tasks 
[root@ memory]# cat mem1/tasks 
9439
9680
[root@ memory]# echo $$ > mem2/tasks 
[root@ memory]# cat mem2/tasks 
9439
9693
[root@ memory]# cat mem1/tasks 
[root@ memory]# 

相同類型subsystem的hierarchy為同一個hierarchy,如下例中創建一個包含memory subsystem的hierarchy,它與/sys/fs/cgroup下面的memory是一致的,在cgrp1中創建一個名為mem1的cgroup。在/sys/fs/cgroup/memory下可以看到新創建的mem1

[root@ cgroup]# mount -t cgroup -o memory mem cgrp1/
[root@ cgroup]# cd cgrp1/
[root@ cgrp1]# mkdir mem1
    • 子進程會繼承父進程的hierarchy,但可以將子進程調整到其他cgroup。下例中可以看到子進程同樣受到父進程hierarchy的限制
[root@ mem1]# echo $$
10928
[root@ mem1]# echo $$>tasks
[root@ mem1]# cat /proc/10928/cgroup 
11:devices:/user.slice
10:perf_event:/
9:pids:/user.slice
8:freezer:/
7:cpuacct,cpu:/
6:hugetlb:/
5:memory:/mem1 4:cpuset:/
3:net_prio,net_cls:/
2:blkio:/
1:name=systemd:/user.slice/user-1000.slice/session-1.scope

[root@ mem1]# bash #創建一個子進程
[root@ mem1]# echo $$
11402
[root@ mem1]# cat /proc/11402/cgroup 
11:devices:/user.slice
10:perf_event:/
9:pids:/user.slice
8:freezer:/
7:cpuacct,cpu:/
6:hugetlb:/
5:memory:/mem1 4:cpuset:/
3:net_prio,net_cls:/
2:blkio:/
1:name=systemd:/user.slice/user-1000.slice/session-1.scope

  從上面可以看到,subsystem相同的hierarchy是被重復使用的;當創建一個新的hierarchy時,如果使用的subsystem被其他hierarchy使用,則會返回EBUSY錯誤。如/sys/fs/cgroup中已經在cpuset和memory中單獨使用了名為cpuset和memory的subsystem,則重新創建一個包含了它們的hierarchy會返回錯誤

[root@ cgroup]# mount -t cgroup -o cpuset,memory mem1 cgrp1/
mount: mem1 is already mounted or /cgroup/cgrp1 busy

 可以創建沒有subsystem的hierarchy,默認包含如下文件:

  • tasks:包含了attach到該cgoup的pid。如上述例子中所示,將進程pid寫入到該文件會將進程轉移到該cgroup。(cgroupv2中移除了該文件,使用cgroup.procs)
  • cgroup.procs:包含了線程的group id。將一個線程的group id寫入該文件,會將該group下的所有線程轉移到該cgroup。(cgroupv2中該文件的定義與cgroupv1中tasks文件的意義類似)
  • notify_on_release:flag文件,用來判斷是否執行release_agent
  • release_agent:如果cgroup中使能notify_on_release,cgroup中的最后一個進程被移除,最后一個子cgroup也被刪除時,cgroup會主動通知kernel。接收到消息的kernel會執行release_agent文件中指定的程序
[root@ cgroup]# mount -t cgroup -onone,name=cgrp1 mycgroup  cgrp1/
[root@ cgroup]# cd cgrp1/
[root@ cgrp1]# ll
total 0
-rw-r--r--. 1 root root 0 Jan  2 23:54 cgroup.clone_children
--w--w--w-. 1 root root 0 Jan  2 23:54 cgroup.event_control
-rw-r--r--. 1 root root 0 Jan  2 23:54 cgroup.procs
-r--r--r--. 1 root root 0 Jan  2 23:54 cgroup.sane_behavior
-rw-r--r--. 1 root root 0 Jan  2 23:54 notify_on_release
-rw-r--r--. 1 root root 0 Jan  2 23:54 release_agent
-rw-r--r--. 1 root root 0 Jan  2 23:54 tasks

 

上面為cgroup使用的一般規則,下面講解memory cgroup

linux memory基礎知識

  以32位系統為例講解下linux內存分布。linux內存分為用戶空間和內核空間,用戶空間占用0~3G的內存,內核空間占用3~4G的內存。從下圖中可以看到用戶空間的進程地址均為動態映射(即虛擬地址和物理地址的映射,如使用malloc申請到的內存為虛擬內存,只有對該內存進行訪問時才會進行虛擬內存到物理內存的映射查找),而內核空間主要分為了2種內存區域,物理頁面映射區和內核地址空間,前者可以直接訪問物理地址且不會觸發缺頁異常(物理頁面直接映射),而后者與用戶空間用法一樣,為動態映射。

  當用戶空間使用malloc等系統調用申請內存時,內核會檢查線性地址對應的物理地址,如果沒有找到會觸發一個缺頁異常,進而調用brk或do_map申請物理內存(brk申請的內存通常小於128k)。而對於內核空間來說,它有2種申請內存的方式,slab(也有slob和slub)和vmalloc:

  • slab用於管理內存塊比較小的數據,可以在/proc/slabinfo下查看當前slab的使用情況,該文件下的slab主要分為3種:模塊特定的slab,如UDPv6;為kmalloc使用的slab,如kmalloc-32(32代表32b);申請ZONE-DMA區域的slab,如dma-kmalloc-32。kmalloc和dma-kmalloc都屬於普通的slab。可以看到kmalloc申請的內存為物理內存,且是連續內存,一般用於處理小內存(一般小於128 K)申請的場景;
  • vmalloc操作的內存空間為VMALLOC_START~4GB,它與kmalloc操作的內存空間不存在沖突。vlmalloc申請的內存在物理上可能是不連續的,主要用於解決內存碎片化的問題,因為可能存在缺頁異常且內存分布比較散,因此適用於申請內存比較大且效率要求不高的場景。可以在/proc/vmallocinfo中查看vmalloc的內存分布情況。

用戶空間和內核空間申請內存的方式如下:

linux使用"伙伴關系"算法來管理空閑的內存資源,可以在/proc/buddyinfo中查看當前空閑的內存分布情況,注意到slab申請物理內存時並沒有缺頁異常。(Linux使用分頁機制管理物理內存,將物理內存划分為4k大小的頁面(使用getconf PAGESIZE查看當前系統頁大小),當用戶使用malloc申請內存時可以以kb為單位指定內存大小,但內核在申請內存時是以頁為單位申請實際的物理內存。linux系統的針對大內存分配通過“伙伴關系”算法進行維護,可以在/proc/buddyinfo文件中查看當前空閑內存的划分。buddyinfo使用list保存了連續的物理內存,申請內存時會遍歷該list,找到合適的內存並將其從該list上移除,將其注冊到內存的tables上。

 

linux 內存回收

  linux使用LRU(least recently used)來回收內存頁面,LRU維護2個list,active和inactive,每個list上維護了2種類型的內存映射:文件映射(file)和匿名映射(anon),所以LRU上的內存也就分為了Active(anon)、Inactive(anon)、Active(file)和Inactive(file)4種類型,對應memory.stat中的inactive_anon,active_anon,inactive_file,active_file。匿名映射包含使用malloc或mmap(MAP_ANONYMOUS方式)申請的內存以及swap cache和shmem(見下),其內存不對應具體的文件,故稱為匿名映射;文件映射對應的內存又稱為file-backed pages,包含進程的代碼、映射的文件,在運行一個新的程序時該內存會增加。

  當系統出現內存不足時,首先會想到使用free命令查看當前系統的內存情況,如下例中,系統free內存為65M,available為85M,多出的20M為buff/cache中可以釋放的部分。swap用於在內存不足時將數據從內存swap到硬盤上。

# free -m
              total        used        free      shared  buff/cache   available
Mem:            972         660          65          28         246          85
Swap:          2047          51        1996

  使用free命令可以看到內存的大致情況,如果需要更詳細的信息,就需要結合/proc/meminfo文件。/proc/meminfo文件中一般只要關注與LRU相關的內存即可,即Active(anon)、Inactive(anon)、Active(file)和Inactive(file)。需要注意的是,內核在統計內存時,只統計產生了缺頁異常的內存(即實際的物理內存),如果只是申請了內存,而沒有對內存進行訪問,則不會加入統計。

  其中Active(file)+Inactive(file)+Shmem=Cached+Buffers(如果內存沒有指定mlock),Buffers主要用於塊設備的存儲緩存,該值通常比較小,所以Active(file)+Inactive(file)+Shmem通常也可以認為是Cached,Cached表示了當前的文件緩存。Shmem表示share memory和tmpfs+devtmpfs占用內存空間的總和(由於share memory就是tmpfs實現的,實際上shemem就是各種tmpfs實現的內存的總和。可以使用ipcs查看共享內存大小,使用df -k查看掛載的tmpfs文件系統),該值與free命令的shared相同。所有tmpfs類型的文件系統占用的空間都計入共享內存。注:ipc中共享內存,消息隊列和信號量的底層實現就是基於tmpfs的。

下面創建一個200M的tmpfs的文件系統,可以看到在創建前后內存並沒有變化,原因是此時並沒有訪問內存,/tmp/tmpfs的空間利用率為0%

# mkdir /tmp/tmpfs 
# free -m
              total        used        free      shared  buff/cache   available
Mem:            972         673          63          29         235          70
Swap:          2047          58        1989
# mount -t tmpfs -o size=200M none /tmp/tmpfs/
# free -m
              total        used        free      shared  buff/cache   available
Mem:            972         674          62          29         235          70
Swap:          2047          58        1989
# df -k
Filesystem              1K-blocks    Used Available Use% Mounted on
...
none                       204800       0    204800   0% /tmp/tmpfs

在上述文件系統下創建一個100M的文件,再次查看內存,可以看到shared增加了100M。查看/proc/meminfo,可以看到Shmem和Cached都增加了100M

# echo 3 > /proc/sys/vm/drop_caches
# free -m
              total        used        free      shared  buff/cache   available
Mem:            972         574         253          28         144         212
Swap:          2047         157        1890
[root@ tmpfs]# dd if=/dev/zero of=/tmp/tmpfs/testfile bs=100M count=1
[root@ tmpfs]# free -m
              total        used        free      shared  buff/cache   available
Mem:            972         568         162         128         241         119
Swap:          2047         162        1885

在/proc/meminfo中還有一個Mapped值,用於統計映射的文件的大小。下面使用mmap映射上面生成的100M大小的testfile,注意mmap的選項為MAP_ANONYMOUS|MAP_SHARED,它會創建一個匿名映射,實際上也是tmpfs的實現。下面需要有一個memset操作,否則這塊內存不會被統計進去

#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
    void *ptr;
    int fd;

    fd = open("testfile", O_RDWR);
    if (fd < 0) {
        perror("open()");
        exit(1);
    }
    ptr = mmap(NULL, 1024*1024*100, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, fd, 0);
    if (fd < 0) {
        perror("open()");
        exit(1);
    }
    memset(ptr,0,1024*1024*100);
    getchar();
    return 0;
}

執行前查看當前的內存

# free -m
              total        used        free      shared  buff/cache   available
Mem:            972         538         174         128         260         140
Swap:          2047         192        1855
# ./mmap

在另外一個shell界面查看內存,可以看到shared增加了100M,對比/proc/meminfo前后差距,可以看到Mapped增加了100M,Shmem增加了100M,Inactive(anon)也增加了100M。結束進程后,申請的內存會被釋放。

# free -m
              total        used        free      shared  buff/cache   available
Mem:            972         536          65         228         370          37
Swap:          2047         194        1853

但Mapped並不是Shmem的子集,上述代碼的mmap僅使用MAP_SHARED時不會增加Shmem大小,僅表示映射的文件大小。

AnonPages表示不包含Shmem的匿名映射,AnonPages=Active(anon)+Inactive(anon)-Shmem

“Mlocked”統計的是被mlock()系統調用鎖定的內存大小。被鎖定的內存因為不能pageout/swapout,會從Active/Inactive LRU list移到Unevictable LRU list上。也就是說,當”Mlocked”增時,”Unevictable”也同步增加,而”Active”或”Inactive”同時減小;當”Mlocked”減小的時候,”Unevictable”也同步減小,而”Active”或”Inactive”同時增加。由於swap會影響進程處理內存的效率,對內存進行鎖定可以避免這段進程被交換到硬盤,增加數據處理效率

 

linux進程內存空間

32位系統下,linux中所有進程使用的內存布局如下:

使用pmap可以查看進程內存段的簡要信息,借此可以初步判定是否存在內存泄漏或內存占用過大的地方。pmap命令主要從/proc/$pid/smaps中獲取數值。如下代碼創建並使用100M內存

#include<stdlib.h>
#include<stdio.h>
int main()
{
    int i=0;
    char *p = malloc(1024*1024*100);
    memset(p,1,1024);
    getchar();
    return 0;
}

編譯並執行上述代碼,使用pmap查看該進程的內存分布如下,可以看到有一個100M(102404K)大小的匿名內存占用,即malloc申請的內存,此外也可以看到動態庫libc占用的內存,以及該進程的棧大小(棧默認8M,可以使用ulimit -a查看)。更多參見Linux進程內存布局

# pmap -x 52218
52218:   ./test
Address           Kbytes     RSS   Dirty Mode  Mapping
0000000000400000       4       4       0 r-x-- test
0000000000600000       4       4       4 r---- test
0000000000601000       4       4       4 rw--- test
00007f4b47564000 102404       4       4 rw--- [ anon ]
00007f4b4d965000    1800     256       0 r-x-- libc-2.17.so
00007f4b4db27000    2048       0       0 ----- libc-2.17.so
00007f4b4dd27000      16      16      16 r---- libc-2.17.so
00007f4b4dd2b000       8       8       8 rw--- libc-2.17.so
00007f4b4dd2d000      20      12      12 rw---   [ anon ]
00007f4b4dd32000     136     112       0 r-x-- ld-2.17.so
00007f4b4df39000      12      12      12 rw---   [ anon ]
00007f4b4df51000       8       4       4 rw---   [ anon ]
00007f4b4df53000       4       4       4 r---- ld-2.17.so
00007f4b4df54000       4       4       4 rw--- ld-2.17.so
00007f4b4df55000       4       4       4 rw---   [ anon ]
00007ffc73bf9000 132 16 16 rw--- [ stack ]
00007ffc73cd5000       8       4       0 r-x--   [ anon ]
ffffffffff600000       4       0       0 r-x--   [ anon ]
---------------- ------- ------- -------
total kB          106620     468      92

 

 linux可以使用如下3種方式回收LRU上的內存:

  • 使用kswapd 進行周期性檢查,由上面圖可以看到,linux的內存被分為不同的zone,每個zone中都有3個字段:page_min,page_low,page_high(參見/proc/zoneinfo),kswapd依據這3個值進行內存回收(參見min_free_kbytes),需要注意的是,/proc/zoneinfo的值類型為pages,而/proc/sys/vm/min_free_kbytes的類型為kb,如下例中,計算公式為:

(8+1485+15402)*4=16895,基本等於67584

# cat min_free_kbytes
67584
#  cat /proc/zoneinfo |grep -E "zone|min"
Node 0, zone      DMA
        min      8
Node 0, zone    DMA32
        min      1485
Node 0, zone   Normal
        min      15402

linux 32位和64位定義的zone是不同的,可以在/proc/zoneinfo中查看zone的具體信息;注:numa場景下每個內存被划分到不同的node,每個node含獨立的zone,關系如下:

如下可以查看Node0 包含的zone(本機器只有一個node)

# cat zoneinfo |grep Node
Node 0, zone      DMA
Node 0, zone    DMA32
  • 內存嚴重不足時觸發OOM-killer,當kswapd回收后仍然不滿足需求時才會觸發該機制;
  • 使用/proc/sys/vm/drop_caches手動釋放內存。

用戶進程的內存頁分為兩種:file-backed pages(與文件對應的內存頁),和anonymous pages(匿名頁),比如進程的代碼、映射的文件都是file-backed,而進程的堆、棧都是不與文件相對應的、就屬於匿名頁。file-backed pages在內存不足的時候可以直接寫回對應的硬盤文件里,稱為page-out,不需要用到交換區(swap)。/proc/meminfo中有一個dirty字段(所有的drity=Dirty+NFS_Unstable+Writeback),為了維護數據的一致性,內核在清空內存前會對內存中的數據進行寫回操作,此時內存中的數據也被稱為臟數據,如果需要清空的內存比較大,可能會消耗大量系統io資源;而anonymous pages在內存不足時就只能寫到硬盤上的交換區(swap)里,稱為swap-out,匿名頁即將被swap-out時會先被放進swap cache,在數據寫入硬盤后,swap cache才會被free。下面為swap-out和swap-in的流程.。注:tmpfs也可以被swap-out;cached不包含swap cache。swap 和file-backed可以參見該文檔

[swap-out]
 Make a page as swapcache → unmap → write out → free
[swap-in]
 Alloc page → make it as swapcache → read from disk → map it.

 更多linux內存的信息可以參見這里

 

memory cgroup 對內存的限制

內核擴展

  cgroup內存的回收與上述linux系統的回收機制類似,每個cgroup都有對應的LRU,當內存cgroup的內存達到限定值時會觸發LRU上的內存回收。需要注意的是cgroup無法控制全局LRU的內存回收,因此在系統內存匱乏的時候,會觸發全局LRU上內存的swap操作,此時cgroup無法限制這種行為(如cgroup限制了swap的大小為1G,但此時可能會超過1G)。下圖可以看出cgoup的LRU控制的內存也在全局LRU所控制的范圍內。

memory cgroup的主要作用如下:

    1. 限制memory(含匿名和文件映射,swap cache)
    2. 限制swap+memory
    3. 顯示cgroup的內存信息
    4. 為每個cgroup設置softlimit

  memory cgroup中以 memory.kmem.開頭的文件用於設置cgroup的內核參數,這些功能被稱為內核內存擴展(CONFIG_MEMCG_KMEM),用於限制cgroup中進程占用的內核內存資源,一般用的比較少。內核內存不會使用swap。系統默認會開啟這些功能,可以使用如下命令查看是否打開:

# cat /boot/config-`uname -r`|grep CONFIG_MEMCG
CONFIG_MEMCG=y
CONFIG_MEMCG_SWAP=y
CONFIG_MEMCG_SWAP_ENABLED=y
CONFIG_MEMCG_KMEM=y

cgroup中內核內存和用戶內存有如下限制關系(U表示用戶內存,K表示內核內存):

  1. U != 0,K > ulimited:這是典型的memory cgroup方式,僅對用戶內存進行限制
  2. U !=0,K<U: 當內核內存低於內存時,當前實現下不會觸發內存回收機制,一般不會采用這種使用方式
  3. U !=0,K>=U: 這種情況下會觸發內存回收,主要用於系統管理員對內核內存的限制和跟蹤

cgroup設置用戶內存

  cgoup對用戶內存的限制主要是memory.limit_in_bytes和memory.memsw.limit_in_bytes。后者用於限制swap+memory的大小,前者則不限制swap。后者的值要不小於前者,因此在設置的時候優先設置memory.limit_in_bytes的值,當memory.limit_in_bytes==memory.memsw.limit_in_bytes時表示cgroup不使用swap

當新創建一個cgroup的時候,memory.limit_in_bytes和memory.memsw.limit_in_bytes默認不會對內存進行限制,使用所有的系統內存。

  • 首先設置memory.limit_in_bytes和memory.memsw.limit_in_bytes的限定值為4M,此時不會使用swap
# echo 50M > memory.limit_in_bytes
# cat memory.limit_in_bytes
52428800

# echo 50M > memory.memsw.limit_in_bytes
# cat memory.memsw.limit_in_bytes
52428800
  • 將當前bash設置到tasks中,並嘗試使用dd命令創建一個100M的文件,此時會觸發OOM-killer機制,查看memory.max_usage_in_bytes和memory.max_usage_in_bytes,均為52428800(即50M)
# echo $$ > tasks
# dd if=/dev/zero of=/home/testfile bs=100M count=1
Killed
  • 將memory.memsw.limit_in_bytes設置為系統默認值(可以在root cgroup的memory.memsw.limit_in_bytes中查看),此時可以看到創建成功。memory.max_usage_in_bytes中顯示的內存使用最大值為50M,而memory.memsw.max_usage_in_bytes中的內存使用最大值大於100M
# echo 9223372036854771712 > memory.memsw.limit_in_bytes
# dd if=/dev/zero of=/home/testfile bs=100M count=1
1+0 records in
1+0 records out
104857600 bytes (105 MB) copied, 0.740223 s, 142 MB/s
# cat memory.max_usage_in_bytes
52428800
# cat memory.memsw.max_usage_in_bytes
113299456

 memory.force_empty主要用於在執行rmdir刪除cgroup時盡量清空cgroup占用的內存。類似echo 3 > /proc/sys/vm/drop_caches

# cat memory.usage_in_bytes
86016
# echo 1 > memory.force_empty
# cat memory.usage_in_bytes
0

memory.stat中的字段解析如下:

# per-memory cgroup local status
cache           - # of bytes of page cache memory.
rss             - # of bytes of anonymous and swap cache memory (includes transparent hugepages). #非正真的進程rss
rss_huge        - # of bytes of anonymous transparent hugepages.
mapped_file     - # of bytes of mapped file (includes tmpfs/shmem)
pgpgin          - # of charging events to the memory cgroup. The charging event happens each time a page is accounted as either mapped anon page(RSS) or cache page(Page Cache) to the cgroup.
pgpgout         - # of uncharging events to the memory cgroup. The uncharging event happens each time a page is unaccounted from the cgroup.
swap            - # of bytes of swap usage
dirty           - # of bytes that are waiting to get written back to the disk.
writeback       - # of bytes of file/anon cache that are queued for syncing to disk.
inactive_anon    - # of bytes of anonymous and swap cache memory on inactive LRU list.
active_anon     - # of bytes of anonymous and swap cache memory on active LRU list.
inactive_file    - # of bytes of file-backed memory on inactive LRU list.
active_file     - # of bytes of file-backed memory on active LRU list.
unevictable     - # of bytes of memory that cannot be reclaimed (mlocked etc).

# status considering hierarchy (see memory.use_hierarchy settings)
hierarchical_memory_limit - # of bytes of memory limit with regard to hierarchy under which the memory cgroup is
hierarchical_memsw_limit - # of bytes of memory+swap limit with regard to hierarchy under which memory cgroup is.

 memory.swappiness用於設置發生swap時內存的比例,設置值為[0,100],100表示積極使用swap,0表示優先使用內存。cgroup中的swappiness作用與全局swappiness大體類似,用於限制本group中的swap使用。但cgroup中的swappiness在設置為0時完全禁止swap,而全局在內存不足時依然會使用swap,因此當cgroup中swappiness設置為0時更容易發生OOM-kill。root cgroup中的swappiness設置對應全局swappiness。注:swappiness用於設置發生swap的內存比例,如設置為60,表示內存在%(100-60)時開始發生swap。swappiness的設置建議如下

vm.swappiness = 0 :僅在內存不足的情況下--當剩余空閑內存低於vm.min_free_kbytes limit時,使用交換空間。
vm.swappiness = 1 :進行最少量的交換,而不禁用交換。
vm.swappiness = 10:當系統存在足夠內存時,推薦設置為該值以提高性能。
vm.swappiness = 60:系統默認值。這樣回收內存時,對file-backed的文件cache內存的清空比例會更大,內核將會更傾向於進行緩存清空而不是交換
vm.swappiness = 100:內核將積極的使用交換空間。

memory.failcnt 和memory.memsw.failcnt用於內存達到上限的次數,分別對應memory.limit_in_bytes和memory.memsw.limit_in_bytes。可以使用echo 0>memory.failcnt重置。

memory.use_hierarchy用於設置cgroup內存的繼承管理,如下圖在設置了memory.use_hierarchy=1后,e group的內存會累計到它的祖先c和root。如果某個祖先的內存使用達到上限,則會在該祖先和它的子group中發生內存回收

       root
     /  |   \
    /   |    \
   a    b     c|       \
        d        e

如下圖在root cgroup下面創建一個子cgroup test1,在test1下面創建test2 和test3

      root
        |
        |
      test1
       / \
      /   \
   test2  test3

在test1中設置不適用swap,且內存上限為100M,查看當前內存使用為0

# echo 100M > memory.limit_in_bytes
# echo 100M > memory.memsw.limit_in_bytes
# cat memory.usage_in_bytes
0

使用如下命令創建一個可執行文件,用於申請30M的內存,執行該程序,並將進程添加到test2 cgroup(注:進程只有在添加到cgroup之后的內存申請才受croup的限制,因此在t2進程添加到test 2 cgroup之后,回到t2進程執行界面,執行回車以執行malloc操作),可以看到test2 cgroup的內存使用為30M

#include<stdlib.h>
#include<stdio.h>
int main(int argc,char **argv)
{
    getchar();
    void *mem=malloc(30*1024*1024);
    memset(mem,0,30*1024*1024);
    getchar();
    return 0;
}
# echo 15570 > tasks
# cat tasks
15570
# cat memory.usage_in_bytes
31588352

在test1 cgroup中查看內存使用,約30M

# cat memory.usage_in_bytes
31465472

創建t3進程,用於申請50M內存,操作同t2進程,在test1 cgroup下查看內存消耗,約80M

# cat memory.usage_in_bytes
83902464

修改t3進程申請內存的大小=100M,此時test1 cgroup的子cgrou總內存消耗超過了它的上限100M,在t3加入到test3的cgroup之后,在申請內存時會被oom-kill掉

# ./t3

Killed

memory.use_hierarchy可以限制一個cgroup的總內存大小。當有子cgroup或父cgroup的use_hierarchy enabled時,無法修改該值

memory.soft_limit_in_bytes用於調節內存的使用,該值不能大於memory.limit_in_bytes。當系統發現內存不足時,系統會盡量將cgroup中的內存回退到memory.soft_limit_in_bytes設定的內存值以下。

memory.move_charge_at_immigrate用於控制線程在不同cgroup間移動時對內存charge的動作。當設置為1時,當線程移動到另一個cgroup時,其申請的內存頁也會移動到另一個cgroup。默認不會。需要注意的是,轉移的線程必須是該線程組的主線程,且目標內存充足時才能遷移成功,否則會失敗,同時需要在目的cgroup中設置memory.move_charge_at_immigrate。設置該值會影響效率,如果內存過大,可能會消耗過長時間。

 使用上面的代碼進行測試。創建一個新的cgroup test4且設置其memory.move_charge_at_immigrate=1。test1,test2和test3啟用use_hierarchy功能,限定內存上限為100M

# cat memory.use_hierarchy
1
# cat memory.limit_in_bytes
104857600
# cat memory.memsw.limit_in_bytes
104857600

新的cgroup組織如下:

      root
      / \
     /   \
  test1  test4
   / \
  /   \
test2  test3

使用程序t2申請30M內存,將其加入test2 tasks中,此時在test1 cgroup中可以看到其使用的內存約30M;使用t3申請80M內存,當然此時t3無法加入test3的tasks中。將t2遷移到test4 cgroup中,查看test1 cgroup的內存使用,其變為了0

# cat memory.usage_in_bytes
0

此時將t3加入test3 cgroup,加入成功,查看test1 cgroup的內存使用,使用約80M,而test4 cgroup的內存使用約30M

# cat memory.usage_in_bytes
83894272

memory.oom_control用於控制oom-kill的行為,默認啟動oom-kill,當內存不足時,oom-kill可能會進行內存回收。設置memory.oom_control=1時disable oom-kill,此時當進程監測到內存不足時會進入掛起或睡眠狀態

仍然使用上述代碼創建t1,申請100M內存,同時設置memory.oom_control=1,將其加入test1 cgroup,在t1申請內存之前,可以看到其進程狀態為S+

# ps -aux|grep test1
...
root 21382 0.0 0.0 4212 356 pts/11 S+ 23:24 0:00 ./test1

t1申請內存,可以看到其進程狀態變為了D+。可以通過釋放test1 cgroup的內存(殺死或轉移其他進程)或擴大內存上限(如echo 200M > memory.memsw.limit_in_bytes)來重新激活t1進程

# ps -aux|grep test1
...
root      21382  0.1 10.2 116856 102520 pts/11  D+   23:24   0:00 ./test1

cgroup.event_control用來與memory.oom_control配合,在觸發oom-kill的時候給出事件通知。事件級別有low,medium和critical三種,事件傳遞有default,hierarchy,local三種示例代碼可以參考memory_example-usage,該代碼運行時,當cgroup的內存觸發oom-kill的時候會給出"mem_cgroup oom event received"的提示。

 

 TIPs:

  • 可以使用ps -aux,在RSS一列中查看進程占用的物理內存空間,RSS定義可以查看內存耗用:VSS/RSS/PSS/USS 的介紹。ps -aux顯示的進程的RSS與/proc/$pid/status中的RSS值相同,等於/proc/$pid/smap的所有RSS的和,RSS包含進程memory-mapped(使用lsof查看)文件,不包含打開的文件(/proc/$pid/fd)cache。
  • 使用top命令可以查看進程正在使用的swap的大小,執行top命令,按F,選擇SWAP回車即可
  • 在手動drop cache前可以執行sync,讓臟數據直接寫回到文件中,這樣可以釋放更多內存
  •  由於無法設置root cgroup的限制,因此內存回收機制在root cgroup中是不起作用的。
  •  當一個task從一個cgroup遷移到另一個cgroup的時候,它的charge(匿名頁,文件cache和swap cache)默認是不會隨之遷移的。因此當一個cgroup中沒有任何task的時候,不代表其不占用任何內存(memory.move_charge_at_immigrate)。
  • 將一個一般的 pid 寫入到 tasks 中,只有這個 pid 對應的線程,以及由它產生的其他進程、線程會屬於這個控制組。而把 pid 寫入 cgroups.procs,操作系統則會把找到其所屬進程的所有線程,把它們統統加入到當前控制組。

 

參考:

https://segmentfault.com/a/1190000006917884

http://man7.org/linux/man-pages/man7/cgroups.7.html

http://www.haifux.org/lectures/299/netLec7.pdf

https://files-cdn.cnblogs.com/files/lisperl/cgroups%E4%BB%8B%E7%BB%8D.pdf

https://www.cnblogs.com/AlwaysOnLines/p/5639713.html

https://www.cnblogs.com/wuchanming/p/4465155.html

https://github.com/digoal/blog/blob/master/201701/20170111_02.md

http://linuxperf.com/?cat=7


免責聲明!

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



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