Help us understand the problem. What is going on with this article?

Linuxカーネルのsysctl処理を追いかけてみた

More than 5 years have passed since last update.

Linuxカーネルのsysctl処理を追いかけてみた

今日のLinux Advent Calendarでは、Linuxカーネル内部のsysctl処理を追いかけてみます。ちょうど同じ日が空いていたので、NetBSDのsysctl処理も同様に追いかけてみています。よかったらそちらもご覧ください。

sysctl

sysctlはカーネルの内部状態を設定、参照するための機能です。例えば、IPパケットのフォワーディング等の設定も
sysctlで有効・無効にすることができます。Linuxでは、以下のコマンドにより、IPパケットのフォワーディング設定を
確認できます。

[yuuna@localhost ~]$ sysctl net.ipv4.conf.all.forwarding
net.ipv4.conf.all.forwarding = 0

環境について

今回はFedora21の環境を利用して調べてみます。

[yuuna@localhost ~]$ cat /etc/redhat-release 
Fedora release 21 (Twenty One)
[yuuna@localhost ~]$ uname -r
3.17.6-300.fc21.x86_64

Linuxカーネルのバージョンは3.17.6ですね。Fedoraにインストールされているのがvanillaカーネルだとは思えないけど、
以下のカーネスソースコードでsysctl処理を追いかけてみます。

カーネルソースコードを追いかけてみる

まずは調べるための足がかりが欲しいので、"forwarding"と"ipv4"でソースコードをgrepしてみます。
それっぽいファイルが引っかかりますね。

[yuuna@localhost linux-3.17.6]$ find . -type f | grep sysctl | xargs grep -w forwarding | grep -i ipv4
./kernel/sysctl_binary.c:       { CTL_INT,      NET_IPV4_CONF_FORWARDING,               "forwarding" },

上記はstruct bin_table bin_net_ipv4_conf_vars_table[]の一部ですね。struct bin_tableは入れ子の配列になっているようなので、
どんどん手繰ってみます。

linux-3.17.6/kernel/sysctl_binary.c:
 230 static const struct bin_table bin_net_ipv4_conf_vars_table[] = {
 231         { CTL_INT,      NET_IPV4_CONF_FORWARDING,               "forwarding" },
 ...
 258 static const struct bin_table bin_net_ipv4_conf_table[] = {
 259         { CTL_DIR,      NET_PROTO_CONF_ALL,     "all",          bin_net_ipv4_conf_vars_table },
 ...
 330 static const struct bin_table bin_net_ipv4_table[] = {
 331         {CTL_INT,       NET_IPV4_FORWARD,                       "ip_forward" },
 332
 333         { CTL_DIR,      NET_IPV4_CONF,          "conf",         bin_net_ipv4_conf_table },
 ...
 723 static const struct bin_table bin_net_table[] = {
 ...
 728         { CTL_DIR,      NET_IPV4,               "ipv4",         bin_net_ipv4_table },
 903 static const struct bin_table bin_root_table[] = {
 ...
 906         { CTL_DIR,      CTL_NET,        "net",          bin_net_table },
 ...
1223 static const struct bin_table *get_sysctl(const int *name, int nlen, char *path)
1224 {
1225         const struct bin_table *table = &bin_root_table[0];

最終的にstruct bin_table bin_root_table[]という配列に行き着きます。簡単に図示すると、以下のような階層になっています。

+------------------+
| bin_root_table[] |
+-+----------------+
  |  +-----------------+
  +--| bin_net_table[] |
     +-+---------------+
       |  +----------------------+
       +--| bin_net_ipv4_table[] |
          +-+--------------------+
            |  +--------------------------------+
            +--| bin_net_ipv4_conf_vars_table[] |
               +--------------------------------+

上記の階層は、ちょうど以下のようなsysctlにおける、ドットで区切った名前に対応しているようですね。

$ sysctl -a | grep forwarding | grep ipv4
net.ipv4.conf.all.forwarding = 0
net.ipv4.conf.all.mc_forwarding = 0
net.ipv4.conf.default.forwarding = 0
net.ipv4.conf.default.mc_forwarding = 0
net.ipv4.conf.enp0s3.forwarding = 0
net.ipv4.conf.enp0s3.mc_forwarding = 0
net.ipv4.conf.lo.forwarding = 0
net.ipv4.conf.lo.mc_forwarding = 0

struct bin_table bin_root_table[]を参照しているget_sysctl()関数を呼び出し元に向かって追いかけてみます。

linux-3.17.6/kernel/sysctl_binary.c:
1223 static const struct bin_table *get_sysctl(const int *name, int nlen, char *path)
1224 {
1225         const struct bin_table *table = &bin_root_table[0];
...

get_sysctl()を呼んでいるのはsysctl_getname()ですね。struct bin_table *をtablepに入れて返しています。

linux-3.17.6/kernel/sysctl_binary.c:
1278 static char *sysctl_getname(const int *name, int nlen, const struct bin_table **tablep)
1279 {
1280         char *tmp, *result;
1281
1282         result = ERR_PTR(-ENOMEM);
1283         tmp = __getname();
1284         if (tmp) {
1285                 const struct bin_table *table = get_sysctl(name, nlen, tmp);
1286                 result = tmp;
1287                 *tablep = table;
1288                 if (IS_ERR(table)) {
1289                         __putname(tmp);
1290                         result = ERR_CAST(table);
1291                 }
1292         }
1293         return result;
1294 }

sysctl_getname()はbinary_sysctl()から呼ばれており、取得した&tableに対し、convert()を呼び出しています。
引数の内容からすると、この中で値を設定しているようです。

linux-3.17.6/kernel/sysctl_binary.c:
1296 static ssize_t binary_sysctl(const int *name, int nlen,
1297         void __user *oldval, size_t oldlen, void __user *newval, size_t newlen)
1298 {
1299         const struct bin_table *table = NULL;
...
1306         pathname = sysctl_getname(name, nlen, &table);
...
1329         result = table->convert(file, oldval, oldlen, newval, newlen);

struct bin_tableも一連の関数が定義されているsysctl_binary.cで定義されています。sysctlまわりの構造体や関数はsysctl_binary.cに
まとめられているようです。

linux-3.17.6/kernel/sysctl_binary.c:
  41 struct bin_table {
  42         bin_convert_t           *convert;
  43         int                     ctl_name;
  44         const char              *procname;
  45         const struct bin_table  *child;
  46 };

struct bin_tableのメンバ変数を見ると、最初のメンバがconver()への関数ポインタのように見えます。
bin_net_ipv4_conf_vars_table[]を見ると、CTL_INTが指定されています。

linux-3.17.6/kernel/sysctl_binary.c:
 230 static const struct bin_table bin_net_ipv4_conf_vars_table[] = {
 231         { CTL_INT,      NET_IPV4_CONF_FORWARDING,               "forwarding" },

CTL_INTはマクロ定数なのでは...?と思って確認すると、実は関数になっていました。
実体はbin_intvec()で、引数として渡されたnewvalを設定している...はず...(この関数の中までは追いかけきれませんでした...)

bin_intvec()については、日を改めて追いかけてみます。

linux-3.17.6/kernel/sysctl_binary.c:
  34 #define CTL_INT   bin_intvec
  ...
 971 static ssize_t bin_intvec(struct file *file,
 972         void __user *oldval, size_t oldlen, void __user *newval, size_t newlen)
 973 {
 ...
1013         if (newval && newlen) {
1014                 unsigned __user *vec = newval;
1015                 size_t length = newlen / sizeof(*vec);
1016                 char *str, *end;
1017                 int i;
1018
1019                 str = buffer;
1020                 end = str + BUFSZ;
1021                 for (i = 0; i < length; i++) {
1022                         unsigned long value;
1023
1024                         result = -EFAULT;
1025                         if (get_user(value, vec + i))
1026                                 goto out_kfree;
1027
1028                         str += scnprintf(str, end - str, "%lu\t", value);
1029                 }
1030
1031                 result = kernel_write(file, buffer, str - buffer, 0);

まとめ

LinuxとNetBSDでsysctlの実装を少しだけ追いかけてみました。両者の実装を比較しながら解説してみたかったのですが、
それはもう少しソースコードを調べる必要がありそうです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away