Control Groups

はじめに

これは、linux insidesの新しい章の最初のパートです。 パートの名前からあなたが予想したとおり、このパートではLinuxカーネル内での control groupscgroupsの仕組みを扱います。

Cgroups はLinuxカーネルが提供する特別な仕組みであり、プロセスやプロセスの集合に対して、Processor Time、グループあたりのプロセス数、コントロールグループあたりのメモリ量、またはそのようなリソースの組み合わせなどに対して リソース の種類を割り当てることができます。 Cgroupsは階層的に構成されています。仕組みは通常のプロセスと似ていて、階層的であるため、子のcgroupsは親から特定のパラメータの集合を継承します。 しかし、実際には同じではありません。cgroups と通常のプロセスの主な違いは、通常のプロセスツリーは常に単一であるがコントロールグループでは多くの異なる階層が同時に存在する可能性があるということです。 コントロールグループの階層がそれぞれ サブシステム のコントロールグループに関連付けられているため、単純ではありません。

ある control group subsystem はProcessor Time、または pids の数や、言い換えるなら、control group のプロセス数を表します。 Linux カーネルは以下の12の control group subsystems のサポートを提供しています:

  • cpuset - 個々のプロセッサ(たち)とメモリノードをグループ内のタスク(たち)に割り当てます。
  • cpu - プロセッサの資源へcgroupタスクのアクセスを提供するためにスケジューラを使用します。
  • cpuacct - グループによるプロセッサ使用状況に関するレポートを生成します。
  • io - block devicesからの読み取り/への書き込みの制限を設定します。
  • memory - グループからのタスク(たち)によるメモリ使用制限を設定します。
  • devices - グループからのタスク(たち)によってデバイスへのアクセスを許可します。
  • freezer - グループからのタスク(たち)の中断/再開を許可します。
  • net_cls - グループからのタスク(たち)からのネットワークパケットをマークすることを許可します。
  • net_prio - グループのネットワークインタフェース毎のネットワークトラフィックの優先度を動的に設定する方法を提供します。
  • perf_event - グループにperf events)へのアクセスを提供します。
  • hugetlb - グループのhuge pagesをサポートを有効にします。
  • pid - グループ内のプロセス数に制限を設定します。

コントロールグループのサブシステムは関連する設定オプションに依存します。 例えば、 cpuset サブシステムはカーネル設定オプションのCONFIG_CPUSETSioサブシステムはカーネル設定オプションの CONFIG_BLK_CGROUP を介して有効にする必要があります。 すべてのカーネルの設定のオプションはGeneral setup → Control Group supportで見つかるでしょう:

menuconfig

あなたのコンピュータで有効なコントロールグループをprocファイルシステムで見ることができるでしょう:

$ cat /proc/cgroups 
#subsys_name    hierarchy    num_cgroups    enabled
cpuset    8    1    1
cpu    7    66    1
cpuacct    7    66    1
blkio    11    66    1
memory    9    94    1
devices    6    66    1
freezer    2    1    1
net_cls    4    1    1
perf_event    3    1    1
net_prio    4    1    1
hugetlb    10    1    1
pids    5    69    1

または、sysfsで見ることができるでしょう:

$ ls -l /sys/fs/cgroup/
total 0
dr-xr-xr-x 5 root root  0 Dec  2 22:37 blkio
lrwxrwxrwx 1 root root 11 Dec  2 22:37 cpu -> cpu,cpuacct
lrwxrwxrwx 1 root root 11 Dec  2 22:37 cpuacct -> cpu,cpuacct
dr-xr-xr-x 5 root root  0 Dec  2 22:37 cpu,cpuacct
dr-xr-xr-x 2 root root  0 Dec  2 22:37 cpuset
dr-xr-xr-x 5 root root  0 Dec  2 22:37 devices
dr-xr-xr-x 2 root root  0 Dec  2 22:37 freezer
dr-xr-xr-x 2 root root  0 Dec  2 22:37 hugetlb
dr-xr-xr-x 5 root root  0 Dec  2 22:37 memory
lrwxrwxrwx 1 root root 16 Dec  2 22:37 net_cls -> net_cls,net_prio
dr-xr-xr-x 2 root root  0 Dec  2 22:37 net_cls,net_prio
lrwxrwxrwx 1 root root 16 Dec  2 22:37 net_prio -> net_cls,net_prio
dr-xr-xr-x 2 root root  0 Dec  2 22:37 perf_event
dr-xr-xr-x 5 root root  0 Dec  2 22:37 pids
dr-xr-xr-x 5 root root  0 Dec  2 22:37 systemd

あなたはすでに control groups のメカニズムは、Linuxカーネルのニーズに直接的に開発された仕組みでなく、 主にユーザー空間のニーズに対して開発されたものだと勘づいているかもしれません。 control groupsを使うには、まず最初に control groups を作成する必要があります。私たちは二つの方法で cgroupを作成するでしょう。

1つ目の方法は、sys/fs/cgroup からサブディレクトリを作成し、その作成直後に自動的に作成される tasksファイルにタスクのpidを追加することです。

2つ目の方法は libcgroupライブラリ(Fedoraのlibcgroup-tools)のutilを使って cgroupsを作成/破壊/管理する方法です。

シンプルな例を考えてみましょう。 以下のbashスクリプトは、現在のプロセスの制御端末を表す /dev/ttyデバイスに出力します:

#!/bin/bash

while :
do
    echo "print line" > /dev/tty
    sleep 5
done

このスクリプトを走らせ、結果を見ましょう:

$ sudo chmod +x cgroup_test_script.sh
~$ ./cgroup_test_script.sh 
print line
print line
print line
...
...
...

cgroupfsがマウントされる場所を見てみましょう。 見たとおり、これは /sys/fs/cgroup ディレクトリですが、あなたがマウントしたい場所にマウント出来ます。

$ cd /sys/fs/cgroup

cgroupのタスクによってデバイスへのアクセスを許可/拒否するリソースの種類を表す devicesサブディレクトリに行きましょう。:

# cd /devices

cgroup_test_groupディレクトリを作ります:

# mkdir cgroup_test_group

cgroup_test_groupディレクトリの作成直後に、以下のファイルが生成されるでしょう:

/sys/fs/cgroup/devices/cgroup_test_group$ ls -l
total 0
-rw-r--r-- 1 root root 0 Dec  3 22:55 cgroup.clone_children
-rw-r--r-- 1 root root 0 Dec  3 22:55 cgroup.procs
--w------- 1 root root 0 Dec  3 22:55 devices.allow
--w------- 1 root root 0 Dec  3 22:55 devices.deny
-r--r--r-- 1 root root 0 Dec  3 22:55 devices.list
-rw-r--r-- 1 root root 0 Dec  3 22:55 notify_on_release
-rw-r--r-- 1 root root 0 Dec  3 22:55 tasks

この瞬間、tasksdevices.denyファイルに興味が湧きます。 最初の tasksファイルには、cgroup_test_groupに付加されるプロセスのpid(s)が含まれていなければなりません。 2番目の devices.denyファイルには、拒否されたデバイスのリストが含まれています。 デフォルトでは、新しく作成されたグループにはデバイスアクセスの制限がありません。 デバイスアクセスを禁止するには(この場合は /dev/tty)、以下の行にdevices.denyを書きこみます:

# echo "c 5:0 w" > devices.deny

この行を1行1行見ていきましょう。最初のcはデバイスの種類を表します。 このケースでは、/dev/ttychar deviceです。lsコマンドの出力からこれを確認できます:

~$ ls -l /dev/tty
crw-rw-rw- 1 root tty 5, 0 Dec  3 22:48 /dev/tty

パーミッションリストの最初のcを見てください。 2番目の部分は5:0で、これはデバイスのマイナー番号とメジャー番号です。 これらの数字はlsの出力でも見ることができます。 最後のwは指定されたデバイスに書き込む作業を禁じます。 それでは、cgroup_test_script.shスクリプトを開始しましょう:

~$ ./cgroup_test_script.sh 
print line
print line
print line
...
...

そしてこのプロセスのpidを私たちのグループのdevices/tasksファイルに付け足します:

# echo $(pidof -x cgroup_test_script.sh) > /sys/fs/cgroup/devices/cgroup_test_group/tasks

結果は予想どおりになります:

~$ ./cgroup_test_script.sh 
print line
print line
print line
print line
print line
print line
./cgroup_test_script.sh: line 5: /dev/tty: Operation not permitted

同様の状況としてdocker)コンテナを実行するときを例に挙げられます:

~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
fa2d2085cd1c        mariadb:10          "docker-entrypoint..."   12 days ago         Up 4 minutes        0.0.0.0:3306->3306/tcp   mysql-work

~$ cat /sys/fs/cgroup/devices/docker/fa2d2085cd1c8d797002c77387d2061f56fefb470892f140d0dc511bd4d9bb61/tasks | head -3
5501
5584
5585
...
...
...

docker コンテナの起動時に、dockerはコンテナ内のプロセスのためにcgroupを作成します:

$ docker exec -it mysql-work /bin/bash
$ top
 PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                   1 mysql     20   0  963996 101268  15744 S   0.0  0.6   0:00.46 mysqld                                                                                  71 root      20   0   20248   3028   2732 S   0.0  0.0   0:00.01 bash                                                                                    77 root      20   0   21948   2424   2056 R   0.0  0.0   0:00.00 top

ホストマシンのcgroupを見られるかもしれません:

$ systemd-cgls

Control group /:
-.slice
├─docker
│ └─fa2d2085cd1c8d797002c77387d2061f56fefb470892f140d0dc511bd4d9bb61
│   ├─5501 mysqld
│   └─6404 /bin/bash

これで、われわれは、control groupの仕組み、手動での使用方法、この仕組みの目的について少し分かりました。 Linuxカーネルのソースコードを見て、この仕組みの実装を深く見ていきます。

コントロールグループの早期の初期化

control groupsのLinuxカーネルの仕組みに関する少しの理論を見ただけで、Linuxカーネルのソースコードを知ることができます。 いつものように、私たちは control groupsの初期化から始めます。 cgroupsの初期化は、Linuxカーネルの「早期」と「末期」の2つの部分に分かれています。 この部分では、「早期」と「末期」の部分のみが次の章で考慮されると考えます。

cgroupsの早期の初期化は次の関数を呼ぶことから始まります:

cgroup_init_early();

Linuxカーネルの早期の初期化の間にinit/main.cの中の関数を実行してください。 この関数は、kernel/cgroup.cのソース内で定義され、次の2つのローカル変数の定義から始まります:

int __init cgroup_init_early(void)
{
    static struct cgroup_sb_opts __initdata opts;
    struct cgroup_subsys *ss;
    ...
    ...
    ...
}

cgroup_sb_opts構造体は同じソース内で定義されていて、以下のように見えます:

struct cgroup_sb_opts {
    u16 subsys_mask;
    unsigned int flags;
    char *release_agent;
    bool cpuset_clone_children;
    char *name;
    bool none;
};

これは cgroupfs のマウントオプションを表します。 例えば、以下のコマンドはname =オプションを持ち、サブシステムを持たない、名付けられた cgroupの階層(名前は my_cgrp)を作成します:

$ mount -t cgroup -oname=my_cgrp,none /mnt/cgroups

2番目の変数 ssは、include/linux/cgroup-defs.hで定義されているタイプの cgroup_subsys構造体を持っています。 ヘッダファイルを参照し、タイプの名前から推測できるように、 cgroupサブシステムを表します。 この構造体には、次のようなさまざまなフィールドとコールバック関数が含まれています。:

struct cgroup_subsys {
    int (*css_online)(struct cgroup_subsys_state *css);
    void (*css_offline)(struct cgroup_subsys_state *css);
    ...
    ...
    ...
    bool early_init:1;
    int id;
    const char *name;
    struct cgroup_root *root;
    ...
    ...
    ...
}

ccs_onlineccs_offlineコールバックが呼び出されると、cgroupが正常に終了した後、すべての割り当てが完了し、cgroupは解放されます。 early_initフラグは、早期に初期化されるかもしれない/初期化されるべきサブシステムを示す。 idフィールドとnameフィールドは、それぞれcgroupの登録されたサブシステムの配列内のユニークな識別子とサブシステムのnameを表します。 最後の - rootフィールドは、cgroupの階層のルートへのポインタを表します。

もちろん cgroup_subsys構造体は大きく、他のフィールドもありますが、今は十分です。 cgroupsメカニズムに関連する重要な構造を知れたので、cgroup_init_early関数に戻りましょう。 この関数の主な目的は、いくつかの早期にサブシステムの初期化を行うことです。 あなたがすでに想像しているように、これらの初期のサブシステムは cgroup_subsys -> early_init = 1を持つべきです。 早期に初期化されるサブシステムを見てみましょう。

2つのローカル変数を定義した後に、次のコード行を見れます:

init_cgroup_root(&cgrp_dfl_root, &opts);
cgrp_dfl_root.cgrp.self.flags |= CSS_NO_REF;

ここでは、デフォルトの統一された階層の初期化を実行する init_cgroup_root関数の呼び出しを見ることができます。 この後、このデフォルトのcgroupの状態で CSS_NO_REFフラグをセットしてこのcssの参照カウントを無効にします。 cgrp_dfl_rootは同じソースコードファイルで定義されています:

struct cgroup_root cgrp_dfl_root;

その cgrpフィールドは cgroup構造体によってを表されます。 これはinclude/linux/cgroup-defs.h内で定義されます。 われわれは、Linuxカーネルの task_struct で表されるプロセスをすでに知っています。 task_struct はこのタスクがついている cgroup への直接リンクを含んでいません。 しかし、task_structccs_setフィールドを通してそれを知ることができます。 この ccs_set構造体は、サブシステム状態の配列へのポインタを保持します:

struct css_set {
    ...
    ...
    ....
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
    ...
    ...
    ...
}

そして、cgroup_subsys_state を通して、プロセスはアタッチする cgroup を取得できます:

struct cgroup_subsys_state {
    ...
    ...
    ...
    struct cgroup *cgroup;
    ...
    ...
    ...
}

以下はcgroupsに関連する構造体の全体像です。:

+-------------+         +---------------------+    +------------->+---------------------+          +----------------+
| task_struct |         |       css_set       |    |              | cgroup_subsys_state |          |     cgroup     |
+-------------+         |                     |    |              +---------------------+          +----------------+
|             |         |                     |    |              |                     |          |     flags      |
|             |         |                     |    |              +---------------------+          |  cgroup.procs  |
|             |         |                     |    |              |        cgroup       |--------->|       id       |
|             |         |                     |    |              +---------------------+          |      ....      | 
|-------------+         |---------------------+----+                                               +----------------+
|   cgroups   | ------> | cgroup_subsys_state | array of cgroup_subsys_state
|-------------+         +---------------------+------------------>+---------------------+          +----------------+
|             |         |                     |                   | cgroup_subsys_state |          |      cgroup    |
+-------------+         +---------------------+                   +---------------------+          +----------------+
                                                                  |                     |          |      flags     |
                                                                  +---------------------+          |   cgroup.procs |
                                                                  |        cgroup       |--------->|        id      |
                                                                  +---------------------+          |       ....     |
                                                                  |    cgroup_subsys    |          +----------------+
                                                                  +---------------------+
                                                                             |
                                                                             |
                                                                             ↓
                                                                  +---------------------+
                                                                  |    cgroup_subsys    |
                                                                  +---------------------+
                                                                  |         id          |
                                                                  |        name         |
                                                                  |      css_online     |
                                                                  |      css_ofline     |
                                                                  |        attach       |
                                                                  |         ....        |
                                                                  +---------------------+

init_cgroup_rootcgrp_dfl_root をデフォルト値で埋めます。 次に、システムの最初のプロセスを表す init_task に最初の ccs_set を割り当てます。:

RCU_INIT_POINTER(init_task.cgroups, &init_css_set);

そして、cgroup_init_early関数の最後の大きなことは early cgroups の初期化です。 ここでは、すべての登録されたサブシステムを調べ、一意の識別番号、サブシステムの名前を割り当て、早期にマークされたサブシステムのために cgroup_init_subsys関数を呼び出します。:

for_each_subsys(ss, i) {
        ss->id = i;
        ss->name = cgroup_subsys_name[i];

        if (ss->early_init)
            cgroup_init_subsys(ss, true);
}

ここの for_each_subsysマクロは、kernel/cgroup.cで定義されているものです。cgroup_subsys配列に対するforループです。 この配列の定義は、同じファイルにありますが、それは少し奇妙です:

#define SUBSYS(_x) [_x ## _cgrp_id] = &_x ## _cgrp_subsys,
    static struct cgroup_subsys *cgroup_subsys[] = {
        #include <linux/cgroup_subsys.h>
};
#undef SUBSYS

1つの引数(サブシステムの名前)をとり、cgroupサブシステムの cgroup_subsys配列を定義するSUBSYSマクロとして定義されています。 それに加えて、配列が linux/cgroup_subsys.hヘッダーファイルの内容で初期化されていることがわかります。 このヘッダーファイルを見てみると、指定されたサブシステムの名前を持つSUBSYSマクロのセットが再び表示されます:

#if IS_ENABLED(CONFIG_CPUSETS)
SUBSYS(cpuset)
#endif

#if IS_ENABLED(CONFIG_CGROUP_SCHED)
SUBSYS(cpu)
#endif
...
...
...

これは、SUBSYSマクロを最初に定義した後の#undef文のために働きます。 &_x ## _cgrp_subsys式を見てください。 ##演算子は、Cマクロの右と左の式を連結します。 cpusetcpuなどを SUBSYSマクロに渡すと、cpuset_cgrp_subsyscp_cgrp_subsysのどこかを定義する必要があります。 それは本当です。kernel/cpuset.cのソースコードファイルを見ると、次のように定義されています。:

struct cgroup_subsys cpuset_cgrp_subsys = {
    ...
    ...
    ...
    .early_init    = true,
};

したがって、cgroup_init_early関数の最後のステップでは、cgroup_init_subsys関数を呼び出すことによって、初期のサブシステムを初期化します。 以下の初期のサブシステムは初期化されます:

  • cpuset;
  • cpu;
  • cpuacct.

cgroup_init_subsys関数は与えられたサブシステムをデフォルト値で初期化します。 階層のルートを設定し、css_allocコールバック関数の呼び出しで与えられたサブシステムのためのスペースを割り当て、もし親プロセスが存在する場合はサブシステムを親プロセスにリンクし、割り当てられたサブシステムなどを初期プロセスに追加します。

これで全てです。この瞬間から、初期のサブシステムが初期化されます。

まとめ

これでLinuxカーネルの Control groups の仕組みについての最初のパートは終わりです。 control groupsの仕組みに関連する初期化の最初のステップと理論を見てきました。 次のパートでは、control groupsの実践的な側面を見ていこうと思います。

もし質問や提案があれば twitterで連絡してください。

英語が私の母国語ではないことに留意してください、ご迷惑をおかけして申し訳ありません。 もし、何か間違いが見つかった場合は、linux-insidesにPRを送ってください

リンク

results matching ""

    No results matching ""