歡迎光臨
每天分享高質量文章

Linux cgroups 命令簡介

來源:sparkdev

www.cnblogs.com/sparkdev/p/8296063.html

cgroups(Control Groups) 是 linux 核心提供的一種機制,這種機制可以根據需求把一系列系統任務及其子任務整合(或分隔)到按資源劃分等級的不同組內,從而為系統資源管理提供一個統一的框架。簡單說,cgroups 可以限制、記錄任務組所使用的物理資源。本質上來說,cgroups 是核心附加在程式上的一系列鉤子(hook),透過程式執行時對資源的排程觸發相應的鉤子以達到資源追蹤和限制的目的。


本文以 Ubuntu 16.04 系統為例介紹 cgroups,所有的 demo 均在該系統中演示。


為什麼要瞭解 cgroups


在以容器技術為代表的虛擬化技術大行其道的時代瞭解 cgroups 技術是非常必要的!比如我們可以很方便的限制某個容器可以使用的 CPU、記憶體等資源,這究竟是如何實現的呢?透過瞭解 cgroups 技術,我們可以窺探到 linux 系統中整個資源限制系統的脈絡。從而幫助我們更好的理解和使用 linux 系統。


cgroups 的主要作用


實現 cgroups 的主要目的是為不同使用者層面的資源管理提供一個統一化的介面。從單個任務的資源控制到作業系統層面的虛擬化,cgroups 提供了四大功能:


  • 資源限制:cgroups 可以對任務是要的資源總額進行限制。比如設定任務執行時使用的記憶體上限,一旦超出就發 OOM。

  • 優先順序分配:透過分配的 CPU 時間片數量和磁碟 IO 頻寬,實際上就等同於控制了任務執行的優先順序。

  • 資源統計:cgoups 可以統計系統的資源使用量,比如 CPU 使用時長、記憶體用量等。這個功能非常適合當前雲端產品按使用量計費的方式。

  • 任務控制:cgroups 可以對任務執行掛起、恢復等操作。


相關概念


Task(任務) 


在 linux 系統中,核心本身的排程和管理並不對行程和執行緒進行區分,只是根據 clone 時傳入的引數的不同來從概念上區分行程和執行緒。這裡使用 task 來表示系統的一個行程或執行緒。


Cgroup(控制組) 


cgroups 中的資源控制以 cgroup 為單位實現。Cgroup 表示按某種資源控制標準劃分而成的任務組,包含一個或多個子系統。一個任務可以加入某個 cgroup,也可以從某個 cgroup 遷移到另一個 cgroup。


Subsystem(子系統) 


cgroups 中的子系統就是一個資源排程控制器(又叫 controllers)。比如 CPU 子系統可以控制 CPU 的時間分配,記憶體子系統可以限制記憶體的使用量。以筆者使用的 Ubuntu 16.04.3 為例,其核心版本為 4.10.0,支援的 subsystem 如下( cat /proc/cgroups):

Hierarchy(層級)


層級有一系列 cgroup 以一個樹狀結構排列而成,每個層級透過系結對應的子系統進行資源控制。層級中的 cgroup 節點可以包含零個或多個子節點,子節點繼承父節點掛載的子系統。一個作業系統中可以有多個層級。

cgroups 的檔案系統介面


cgroups 以檔案的方式提供應用介面,我們可以透過 mount 命令來檢視 cgroups 預設的掛載點:

$ mount | grep cgroup


第一行的 tmpfs 說明 /sys/fs/cgroup 目錄下的檔案都是存在於記憶體中的臨時檔案。


第二行的掛載點 /sys/fs/cgroup/systemd 用於 systemd 系統對 cgroups 的支援,相關內容筆者今後會做專門的介紹。


其餘的掛載點則是核心支援的各個子系統的根級層級結構。


需要註意的是,在使用 systemd 系統的作業系統中,/sys/fs/cgroup 目錄都是由 systemd 在系統啟動的過程中掛載的,並且掛載為只讀的型別。換句話說,系統是不建議我們在 /sys/fs/cgroup 目錄下建立新的目錄並掛載其它子系統的。這一點與之前的作業系統不太一樣。


下麵讓我們來探索一下 /sys/fs/cgroup 目錄及其子目錄下都是些什麼:



/sys/fs/cgroup 目錄下是各個子系統的根目錄。我們以 memory 子系統為例,看看 memory 目錄下都有什麼?



這些檔案就是 cgroups 的 memory 子系統中的根級設定。比如 memory.limit_in_bytes 中的數字用來限制行程的最大可用記憶體,memory.swappiness 中儲存著使用 swap 的權重等等。


既然 cgroups 是以這些檔案作為 API 的,那麼我就可以透過建立或者是修改這些檔案的內容來應用 cgroups。具體該怎麼做呢?比如我們怎麼才能限制某個行程可以使用的資源呢?接下來我們就透過簡單的 demo 來演示如何使用 cgroups 限制行程可以使用的資源。


檢視行程所屬的 cgroups


可以透過 /proc/[pid]/cgroup 來檢視指定行程屬於哪些 cgroup:



每一行包含用冒號隔開的三列,他們的含義分別是:


  • cgroup 樹的 ID, 和 /proc/cgroups 檔案中的 ID 一一對應。

  • 和 cgroup 樹系結的所有 subsystem,多個 subsystem 之間用逗號隔開。這裡 name=systemd 表示沒有和任何 subsystem 系結,只是給他起了個名字叫 systemd。

  • 行程在 cgroup 樹中的路徑,即行程所屬的 cgroup,這個路徑是相對於掛載點的相對路徑。


既然 cgroups 是以這些檔案作為 API 的,那麼我就可以透過建立或者是修改這些檔案的內容來應用 cgroups。具體該怎麼做呢?比如我們怎麼才能限制某個行程可以使用的資源呢?接下來我們就透過簡單的 demo 來演示如何使用 cgroups 限制行程可以使用的資源。


cgroups 工具


在介紹透過 systemd 應用 cgroups 之前,我們先使用 cgroup-bin 工具包中的 cgexec 來演示 demo。Ubuntu 預設沒有安裝 cgroup-bin 工具包,請透過下麵的命令安裝:

$ sudo apt install cgroup-bin


demo:限制行程可用的 CPU


在我們使用 cgroups 時,最好不要直接在各個子系統的根目錄下直接修改其配置檔案。推薦的方式是為不同的需求在子系統樹中定義不同的節點。比如我們可以在 /sys/fs/cgroup/cpu 目錄下新建一個名稱為 nick_cpu 的目錄:

$ cd  /sys/fs/cgroup/cpu

$ sudo mkdir nick_cpu

然後檢視新建的目錄下的內容:



是不是有點吃驚,cgroups 的檔案系統會在建立檔案目錄的時候自動建立這些配置檔案!


讓我們透過下麵的設定把 CPU 週期限製為總量的十分之一:

$ sudo su

$ echo 100000 > nick_cpu/cpu.cfs_period_us

$ echo 10000 > nick_cpu/cpu.cfs_quota_us

然後建立一個 CPU 密集型的程式:

void main()

{

    unsigned int i, end;

 

    end = 1024 * 1024 * 1024;

    for(i = 0; i < end; )

    {

        i ++;

    }

}

儲存為檔案 cputime.c 編譯並透過不同的方式執行:

$ gcc cputime.co cputime

$ sudo su

$ time ./cputime

$ time cgexecg cpu:nick_cpu ./cputime


time 命令可以為我們報告程式執行消耗的時間,其中的 real 就是我們真實感受到的時間。使用 cgexec 能夠把我們新增的 cgroup 配置 nick_cpu 應用到執行 cputime 程式的行程上。 上圖顯示,預設的執行只需要 2s 左右。透過 cgroups 限制 CPU 資源後需要執行 23s。


demo:限制行程可用的記憶體


這次我們來限制行程可用的最大記憶體,在 /sys/fs/cgroup/memory 下建立目錄nick_memory:

$ cd  /sys/fs/cgroup/memory

$ sudo mkdir nick_memory

下麵的設定把行程的可用記憶體限制在最大 300M,並且不使用 swap:

# 物理記憶體 + SWAP <= 300 MB;1024*1024*300 = 314572800

$ sudo su

$ echo 314572800 > nick_memory/memory.limit_in_bytes

$ echo 0 > nick_memory/memory.swappiness

然後建立一個不斷分配記憶體的程式,它分五次分配記憶體,每次申請 100M:

#include

#include

#include

 

#define CHUNK_SIZE 1024 * 1024 * 100

 

void main()

{

    char *p;

    int i;

 

    for(i = 0; i < 5; i ++)

    {

        p = malloc(sizeof(char) * CHUNK_SIZE);

        if(p == NULL)

        {

            printf(“fail to malloc!”);

            return ;

        }

        // memset() 函式用來將指定記憶體的前 n 個位元組設定為特定的值

        memset(p, 0, CHUNK_SIZE);

        printf(“malloc memory %d MB
, (i + 1) * 100);

    }

}

把上面的程式碼儲存為 mem.c 檔案,然後編譯:

$ gcc mem.c -o mem


執行生成的 mem 程式:

$ ./mem


此時一切順利,然後加上剛才的約束試試:

$ cgexec -g memory:nick_memory ./mem


由於記憶體不足且禁止使用 swap,所以被限制資源的行程在申請記憶體時被強制殺死了。


下麵再使用 stress 程式測試一個類似的場景(透過 stress 程式申請 500M 的記憶體):

$ sudo cgexec -g memory:nick_memory stress –vm 1 –vm-bytes 500000000 –vm-keep –verbo


stress 程式能夠提供比較詳細的資訊,行程被殺掉的方式是收到了 SIGKILL(signal 9) 訊號。


實際應用中往往要同時限制多種的資源,比如既限制 CPU 資源又限制記憶體資源。使用 cgexec 實現這樣的用例其實很簡單,直接指定多個 -g 選項就可以了:

$ cgexec -g cpu:nick_cpu -g memory:nick_memory ./cpumem


總結


cgroups 是 linux 核心提供的功能,由於牽涉的概念比較多,所以不太容易理解。本文試圖在介紹概念性內容的同時,用最簡單的 demo 演示 cgroups 的用法。希望直觀的 demo 能夠幫助大家理解 cgroups。


●編號583,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

 

運維

更多推薦18個技術類微信公眾號

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂