Linux の挙動を変更する 4 つの方法

Linux カーネルの挙動を変更する次の 4 つの方法について整理する。

名称 概要 設定変更を反映するタイミング
カーネルコンフィグ コンパイル時の設定 再ビルド
ブートパラメータ カーネル起動時の設定 再起動
モジュールパラメータ モジュールロード時の設定 モジュールの再ロード
カーネルパラメータ カーネル起動時の設定 リアルタイム

検証環境

CentOS 8 で検証する。

]# cat /etc/redhat-release
CentOS Linux release 8.1.1911 (Core)

]# uname -a
Linux localhost.localdomain 4.18.0-80.el8.x86_64 #1 SMP Tue Jun 4 09:19:46 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

カーネルコンフィグ

異なるバイナリを作成するための設定。Linux は様々なハードウェアをサポートしており、また、機能もわんさかある。これらの機能をすべて実行バイナリに含めると、使ってない機能のせいでセキュリティリスクが無駄に高くなったりバイナリサイズがでかくなったり分岐処理が挟まって性能が落ちたりして都合が悪い。そこで、ソースコードレベルで機能の有効/無効を切り替えて専用のカーネルを作成する。

設定変更するにはカーネルの再ビルドが必要になるため、普通に Linux を使ってるだけならあまり気にすることはない。yum 等のパッケージマネージャでダウンロードするカーネルは、各ディストリビューションごとにカーネルコンフィグを設定してビルドしてくれたもの。むしろ RHEL なんかはカスタムカーネルはサポート対象外なので(RedHat Customer Portal『Recompile and repackage rhel 7 kernel』、パラメータを変えて再ビルドしようものならサポート対象外になる可能性すらある。

現在の設定を確認する

yum でインストールしたカーネルを利用している場合、次のファイルで確認できる。

]# cat /boot/config-4.18.0-80.el8.x86_64
...
CONFIG_64BIT=y
CONFIG_X86_64=y
CONFIG_X86=y
CONFIG_INSTRUCTION_DECODER=y
CONFIG_OUTPUT_FORMAT="elf64-x86-64"
CONFIG_ARCH_DEFCONFIG="arch/x86/configs/x86_64_defconfig"
CONFIG_LOCKDEP_SUPPORT=y
CONFIG_STACKTRACE_SUPPORT=y
...

設定可能なパラメータ

ソースコード中の Kconfig というファイルに記載されている。

arch/x86/Kconfig

config RANDOMIZE_BASE
        bool "Randomize the address of the kernel image (KASLR)"
        depends on RELOCATABLE
        default y
        ---help---
          In support of Kernel Address Space Layout Randomization (KASLR),
          this randomizes the physical address at which the kernel image
          is decompressed and the virtual address where the kernel
          image is mapped, as a security feature that deters exploit
          attempts relying on knowledge of the location of kernel
          code internals.

          On 64-bit, the kernel physical and virtual addresses are
          randomized separately. The physical address will be anywhere
          between 16MB and the top of physical memory (up to 64TB). The
          virtual address will be randomized from 16MB up to 1GB (9 bits
          of entropy). Note that this also reduces the memory space
          available to kernel modules from 1.5GB to 1GB.

単純な形式は KEY=VALUE だが、項目間の依存関係も表現できる。Kconfig の記述方法の詳細は kconfig-language.txt に記載されている。

設定を変更する

例としてスワップのサポートを外してみる(ジョークなので絶対やらないでください。Anonymous なメモリの追い出し先がなくなるので、無用な Anonymous なメモリが幅を利かせて、有用なページキャッシュなどがメモリから追い出されやすくなります。また他にも様々な弊害があります。詳細はスワップの弁護:よくある誤解を解くが詳しいです。)。

まず .config ファイルを作成する。方法はいろいろあるが、今回は make menuconfig で作成する。

]# make menuconfig

f:id:kimulla:20200521064147p:plain

f:id:kimulla:20200521063958p:plain

スワップの無効化(CONFIG_SWAP=n)はこのバージョンでは壊れてたので(まあ誰もやりませんからね)、次のパッチを当てる。
参考 x86/init: fix build with CONFIG_SWAP=n

diff --git a/arch/x86/mm/init.c b/arch/x86/mm/init.c
index 156ed8154af8..acfab322fbe0 100644
--- a/arch/x86/mm/init.c
+++ b/arch/x86/mm/init.c
@@ -914,6 +914,7 @@  void update_cache_mode_entry(unsigned entry, enum page_cache_mode cache)
    __pte2cachemode_tbl[entry] = cache;
 }
 
+#ifdef CONFIG_SWAP
 unsigned long max_swapfile_size(void)
 {
    unsigned long pages;
@@ -934,3 +935,4 @@  unsigned long max_swapfile_size(void)
    }
    return pages;
 }
+#endif

カーネルをビルドし、インストールする。

]# make
]# make modules_install install

インストールしたカーネルで再起動すると、スワップ用のファイルを作成しても swapon が失敗する。

]# dd if=/dev/zero of=/swapfile bs=1M count=10
10+0 レコード入力
10+0 レコード出力
10485760 bytes (10 MB, 10 MiB) copied, 0.0319983 s, 328 MB/s

]# mkswap /swapfile
mkswap: /swapfile: 警告: 古い swap 署名を消去しています。
スワップ空間バージョン 1 を設定します。サイズ = 10 MiB (10481664 バイト)
ラベルはありません, UUID=9f34c288-f43a-45a8-9451-6b39738d9b4a

]# swapon /swapfile
swapon: /swapfile: swapon が失敗しました: 関数は実装されていません

ソースコード上の扱い

マクロの分岐処理のフラグとして使われることが多い。

static unsigned long canonicalize_ip(unsigned long ip)
{
#ifdef CONFIG_RANDOMIZE_BASE
        ip -= kaslr_offset();
#endif
        return ip;
}

Kconfig の実装

make menuconfig(ほかのターゲットも) は最終的に .config ファイルを生成する。.config ファイルは次のようなテキストファイルなので、カーネルソースコードでそのまま扱えない。

#
# Automatically generated file; DO NOT EDIT.
# Linux/x86 4.18.0-80.11.2.el8.x86_64+debug Kernel Configuration
#

#
# Compiler: gcc (GCC) 8.3.1 20190507 (Red Hat 8.3.1-4)
#
CONFIG_64BIT=y
CONFIG_X86_64=y
CONFIG_X86=y
CONFIG_INSTRUCTION_DECODER=y
CONFIG_OUTPUT_FORMAT="elf64-x86-64"
CONFIG_ARCH_DEFCONFIG="arch/x86/configs/x86_64_defconfig"
CONFIG_LOCKDEP_SUPPORT=y
CONFIG_STACKTRACE_SUPPORT=y
CONFIG_MMU=y
CONFIG_ARCH_MMAP_RND_BITS_MIN=28
CONFIG_ARCH_MMAP_RND_BITS_MAX=32
CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=8

そこで、ビルド中に scripts/kconfig/conf が .config を include/generated/autoconf.h に変換する。

]# make
scripts/kconfig/conf  --syncconfig Kconfig
...
<Ctrl+C>

]# cat include/generated/autoconf.h
/*
 *
 * Automatically generated file; DO NOT EDIT.
 * Linux/x86 4.18.0-80.11.2.el8.x86_64+debug Kernel Configuration
 *
 */
#define CONFIG_IP6_NF_MATCH_AH_MODULE 1
#define CONFIG_NLS_CODEPAGE_861_MODULE 1
#define CONFIG_RING_BUFFER 1
#define CONFIG_HARDENED_USERCOPY_FALLBACK 1

そして生成した include/generated/autoconf.hinclude/linux/kconfig.h がインクルードしている。しかし include/linux/kconfig.h はどのソースコードからもインクルードされていない。ではどうしているかというと、コンパイル時に gcc オプションで -include ./include/linux/kconfig.hを指定している(これは #include "./include/linux/kconfig.h" 相当を書いたことになるオプション)。

]# make V=1
...
 gcc -Wp,-MD,kernel/.panic.o.d  -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/8/include -I./arch/x86/include -I./arch/x86/include/generated  -I./include -I./arch/x86/include/uapi -I./arch/x86/include/generated/uapi -I./include/uapi -I./include/generated/uapi -include ./include/linux/kconfig.h -include ./include/linux/compiler_types.h -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -fshort-wchar -Werror-implicit-function-declaration -Wno-format-security -std=gnu89 -fno-PIE -DCC_HAVE_ASM_GOTO -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -m64 -falign-jumps=1 -falign-loops=1 -mno-80387 -mno-fp-ret-in-387 -mpreferred-stack-boundary=3 -mskip-rax-setup -mtune=generic -mno-red-zone -mcmodel=kernel -funit-at-a-time -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -DCONFIG_AS_FXSAVEQ=1 -DCONFIG_AS_SSSE3=1 -DCONFIG_AS_CRC32=1 -DCONFIG_AS_AVX=1 -DCONFIG_AS_AVX2=1 -DCONFIG_AS_AVX512=1 -DCONFIG_AS_SHA1_NI=1 -DCONFIG_AS_SHA256_NI=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mindirect-branch=thunk-extern -mindirect-branch-register -fno-delete-null-pointer-checks -Wno-frame-address -Wno-format-truncation -Wno-format-overflow -Wno-int-in-bool-context -O2 -Werror --param=allow-store-data-races=0 -Wframe-larger-than=2048 -fstack-protector-strong -Wno-unused-but-set-variable -Wno-unused-const-variable -fno-var-tracking-assignments -g -gdwarf-4 -pg -mfentry -DCC_USING_FENTRY -fno-inline-functions-called-once -Wdeclaration-after-statement -Wno-pointer-sign -Wno-stringop-truncation -fno-strict-overflow -fno-merge-all-constants -fmerge-constants -fno-stack-check -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -Werror=date-time -Werror=incompatible-pointer-types -Werror=designated-init -fmacro-prefix-map=./= -Wno-packed-not-aligned -mrecord-mcount    -DKBUILD_BASENAME='"panic"' -DKBUILD_MODNAME='"panic"' -c -o kernel/.tmp_panic.o kernel/panic.c
   ./tools/objtool/objtool orc generate  --no-fp  --retpoline "kernel/.tmp_pan
...

これでようやく、Kconfig の内容がソースコード中で参照できるようになる。

ブートパラメータ

Linux カーネルの起動オプションに関する設定。

現在の設定を確認する

現在のカーネルの起動オプションは/proc/cmdline で確認できる。

]# cat /proc/cmdline
BOOT_IMAGE=(hd0,gpt2)/vmlinuz-4.18.0-147.3.1.el8_1.x86_64 root=/dev/mapper/cl-root ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet

設定可能なパラメータ

The kernel’s command-line parameters に記載されている。

設定を変更する

一時的に変更する

起動時の選択画面で e を押すと、一時的に変更できる。

f:id:kimulla:20200521070924p:plain

今回はレスキューモードで起動するために、4 行目の linux... から始まる行の末尾に rescue を足して起動してみる。

f:id:kimulla:20200521071214p:plain

するとレスキューモードで起動できたことがわかる。

f:id:kimulla:20200521070956p:plain

永続的に変更する

/etc/default/grub の GRUB_CMDLINE_LINUX で設定する。

# cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true

設定後は、 grub2-mkconfig で設定ファイルを作り直す必要がある点に注意。詳細な手順は BIOS/UEFI によって多少異なる。

たとえば selinux の無効化を UEFI で設定すると、次のようになる。

]# cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
# selinux=0 を設定する
GRUB_CMDLINE_LINUX="crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet selinux=0"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true

]# grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
Generating grub configuration file ...
Adding boot menu entry for EFI firmware configuration
done
]# reboot

selinux=0 が設定されると、/sys/fs/selinux への selinuxfs のマウントがされなくなる。この状態になると、 getenforce は selinux の設定ファイルとは無関係に Disabled を返し、また setenforce コマンドも SELinux is disabled を返す。

]# cat /etc/sysconfig/selinux

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=enforcing
# SELINUXTYPE= can take one of these three values:
#     targeted - Targeted processes are protected,
#     minimum - Modification of targeted policy. Only selected processes are protected.
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted

]# getenforce
Disabled

]# setenforce 1
setenforce: SELinux is disabled

設定の優先度

selinux の例では、ブートパラメータによって selinux の設定ファイルが無効になった。つまり、ブートパラメータの設定が優先される。しかしそれとは逆に、ブートパラメータの設定を起動後に変更できるパラメータも存在する(たとえば loglevel など)。設定の優先度は各パラメータの実装に任せられているので、画一的な指針はないように思う(どうしてこうなった)。基本的には Linux カーネルのブートがスタートしてからユーザ空間のプロセスが起動するまでの初期化処理の時点で利用したいパラメータがブートパラメータになるが、ブート後はただのメモリ上に配置された変数でしかないので、技術的には変更できない理由はない。再起動しないと設定反映されないというのもけっこうハードル高いわけで…まあそういうものなんだろう。

ソースコード上の扱い

__setup、core_param、module_param マクロによってブートパラメータが定義される。

__setup の使い方

__setup は、ブートパラメータによって複数の初期化処理を実行するときや引数なしパラメータで初期値を設定するときに使う。

kernel/trace/trace.c

static int __init set_cmdline_ftrace(char *str)
{
        strlcpy(bootup_tracer_buf, str, MAX_TRACER_SIZE);
        default_bootup_tracer = bootup_tracer_buf;
        /* We are using ftrace early, expand it */
        ring_buffer_expanded = true;
        return 1;
}
__setup("ftrace=", set_cmdline_ftrace);

__setup の実装(蛇足)

Linuxの備忘録とか・・・__setupマクロ とほぼ変わってなかったので、こちらを参照する。

core_param の使い方

core_param は、ブートパラメータを変数に代入するときに使う。

kernel/panic.c

static int pause_on_oops;
...

#  pause_on_oops 変数に pause_on_oops のブートパラメータが渡される。
core_param(pause_on_oops, pause_on_oops, int, 0644);

...
static void do_oops_enter_exit(void)
{
        unsigned long flags;
        static int spin_counter;

        if (!pause_on_oops)
                return;

        spin_lock_irqsave(&pause_on_oops_lock, flags);
        if (pause_on_oops_flag == 0) {
                /* This CPU may now print the oops message */
                pause_on_oops_flag = 1;
        } else {
                /* We need to stall this CPU */
                if (!spin_counter) {
                        /* This CPU gets to do the counting */
                        spin_counter = pause_on_oops;
                        do {
...
}

ブートパラメータが変数に渡されたあとは、分岐処理などをひたすら頑張る。

core_param の実装(蛇足)

core_param マクロを展開して整形すると、次のようになる。__section__ ("__param")から、 __param セクションに kernel_param 構造体が配置される。

static inline __attribute__((unused)) __attribute__((no_instrument_function)) int __attribute__((unused)) *__check_pause_on_oops(void) {
        return(&(pause_on_oops));
};
static const char __param_str_pause_on_oops[] = "" "pause_on_oops";
static struct kernel_param const __param_pause_on_oops __attribute__((__used__)) __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) = {
        __param_str_pause_on_oops, ((struct module *)0), &param_ops_int, (
        (sizeof(struct { int:(-!!((0644) < 0));}))+
                (sizeof(struct { int:(-!!((0644) > 0777));})) +
                (sizeof(struct { int:(-!!((((0644) >> 6) & 4) < (((0644) >> 3) & 4)));})) +
                (sizeof(struct { int:(-!!((((0644) >> 3) & 4) < ((0644) & 4)));})) +
                (sizeof(struct { int:(-!!((((0644) >> 6) & 2) < (((0644) >> 3) & 2)));})) +
                (sizeof(struct { int:(-!!((0644) & 2));})) +
                (0644)
    ),-1, 0, { &pause_on_oops }
};

この情報は次のリンカで利用されていて、バシバシ増える引数の全体サイズを把握するために使われるっぽい。

include/asm-generic/vmlinux.lds.h

        /* Built-in module parameters. */                               \
        __param : AT(ADDR(__param) - LOAD_OFFSET) {                     \
                __start___param = .;                                    \
                KEEP(*(__param))                                        \
                __stop___param = .;                                     \
        }

この情報を利用して init/main.c で parse_args を呼び出して、

init/main.c

        after_dashes = parse_args("Booting kernel",
                                  static_command_line, __start___param,
                                  __stop___param - __start___param,
                                  -1, -1, NULL, &unknown_bootoption);

パースされたパラメータ名に一致する kernel_param 構造体の ops を実行する。

kernel/params.c

/* Args looks like "foo=bar,bar2 baz=fuz wiz". */
char *parse_args(const char *doing,
                 char *args,
                 const struct kernel_param *params,
                 unsigned num,
                 s16 min_level,
                 s16 max_level,
                 void *arg,
                 int (*unknown)(char *param, char *val,
                                const char *doing, void *arg))
{
        char *param, *val, *err = NULL;
...
        while (*args) {
                int ret;
                int irq_was_disabled;

                args = next_arg(args, &param, &val);
                /* Stop at -- */
                if (!val && strcmp(param, "--") == 0)
                        return err ?: args;
                irq_was_disabled = irqs_disabled();
                ret = parse_one(param, val, doing, params, num,
                                min_level, max_level, arg, unknown);
...
}
...
static int parse_one(char *param,
                     char *val,
                     const char *doing,
                     const struct kernel_param *params,
                     unsigned num_params,
                     s16 min_level,
                     s16 max_level,
                     void *arg,
                     int (*handle_unknown)(char *param, char *val,
                                     const char *doing, void *arg))
{
        unsigned int i;
        int err;

        /* Find parameter */
        for (i = 0; i < num_params; i++) {
                if (parameq(param, params[i].name)) {
                        if (params[i].level < min_level
                            || params[i].level > max_level)
                                return 0;
                        /* No one handled NULL, so do it here. */
                        if (!val &&
                            !(params[i].ops->flags & KERNEL_PARAM_OPS_FL_NOARG))
                                return -EINVAL;
                        pr_debug("handling %s with %p\n", param,
                                params[i].ops->set);
                        kernel_param_lock(params[i].mod);
                        if (param_check_unsafe(&params[i], doing))
                                err = params[i].ops->set(val, &params[i]);

kernel_param 構造体の ops は、展開されたコードで見た通り param_ops_intで、それは次のように定義されている。

kernel/params.c

/* Lazy bastard, eh? */
#define STANDARD_PARAM_DEF(name, type, format, strtolfn)                \
        int param_set_##name(const char *val, const struct kernel_param *kp) \
        {                                                               \
                return strtolfn(val, 0, (type *)kp->arg);               \
        }                                                               \
        int param_get_##name(char *buffer, const struct kernel_param *kp) \
        {                                                               \
                return scnprintf(buffer, PAGE_SIZE, format "\n",        \
                                *((type *)kp->arg));                    \
        }                                                               \
        const struct kernel_param_ops param_ops_##name = {                      \
                .set = param_set_##name,                                \
                .get = param_get_##name,                                \
        };                                                              \
        EXPORT_SYMBOL(param_set_##name);                                \
        EXPORT_SYMBOL(param_get_##name);                                \
        EXPORT_SYMBOL(param_ops_##name)

...
STANDARD_PARAM_DEF(int,         int,                    "%i",   kstrtoint);
...

上記から kernel_param の arg に値が設定されることがわかる。今回の arg は &pause_on_oops のため、ブートパラメータの値が変数 pause_on_oops に設定される。

モジュールパラメータ

カーネルモジュールに対する設定。

現在の設定を確認する

/sys/module/<module_name>/parameters で確認できる。

]# grep -H '' /sys/module/nfs/parameters/*
/sys/module/nfs/parameters/callback_nr_threads:0
/sys/module/nfs/parameters/callback_tcpport:0
/sys/module/nfs/parameters/enable_ino64:Y
/sys/module/nfs/parameters/max_session_cb_slots:1
/sys/module/nfs/parameters/max_session_slots:64
/sys/module/nfs/parameters/nfs4_disable_idmapping:Y
/sys/module/nfs/parameters/nfs4_unique_id:
/sys/module/nfs/parameters/nfs_access_max_cachesize:18446744073709551615
/sys/module/nfs/parameters/nfs_idmap_cache_timeout:600
/sys/module/nfs/parameters/recover_lost_locks:N
/sys/module/nfs/parameters/send_implementation_id:1

設定可能なパラメータ

modinfo -pで確認できる。

]# modinfo -p nfs
callback_tcpport: (portnr)
callback_nr_threads:Number of threads that will be assigned to the NFSv4 callback channels. (ushort)
nfs_idmap_cache_timeout: (int)
nfs4_disable_idmapping:Turn off NFSv4 idmapping when using 'sec=sys' (bool)
max_session_slots:Maximum number of outstanding NFSv4.1 requests the client will negotiate (ushort)
max_session_cb_slots:Maximum number of parallel NFSv4.1 callbacks the client will process for a given server (ushort)
send_implementation_id:Send implementation ID with NFSv4.1 exchange_id (ushort)
nfs4_unique_id:nfs_client_id4 uniquifier string (string)
recover_lost_locks:If the server reports that a lock might be lost, try to recover it risking data corruption. (bool)
enable_ino64: (bool)
nfs_access_max_cachesize:NFS access maximum total cache length (ulong)

設定を変更する

modprobe

modprobe の引数にパラメータを設定する。

]# modprobe nfs callback_nr_threads=3
]# cat /sys/module/nfs/parameters/callback_nr_threads
3

永続化する

/etc/modprobe.d 配下に、規約に沿ってファイルを作成すると、 modprobe 実行時に読み込まれる。

]# cat /etc/modprobe.d/nfs.conf
options nfs callback_nr_threads=4

]# modprobe nfs
]# cat /sys/module/nfs/parameters/callback_nr_threads
4

ブートパラメータ

ブートパラメータに設定した値も modprobe 実行時に読み込まれる。ビルトインカーネルモジュール(/lib/modules/$(uname -r)/modules.builtin)に対する設定はこれを使う。
参考 The kernel’s command-line parameters

]# cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet nfs.callback_nr_threads=5"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true

]# grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
Generating grub configuration file ...
Adding boot menu entry for EFI firmware configuration
done    

]# cat /sys/module/nfs/parameters/callback_nr_threads
5

ソースコード上の扱い

module_param (や module_param_named。module_param から module_param_named を呼ぶので実質同じ) マクロによってブートパラメータが定義される。

module_param の使い方

module_param (や module_param_named) は、モジュールにパラメータを渡すために使う。

kernel/trace/trace.c

module_param_named(callback_nr_threads, nfs_callback_nr_threads, ushort, 0644);
MODULE_PARM_DESC(callback_nr_threads, "Number of threads that will be "
                "assigned to the NFSv4 callback channels.");

ブートパラメータが変数に渡されたあとは、分岐処理などをひたすら頑張る。

module_param の実装(蛇足)

core_param と大体同じなので省略。少し違うところは、__param セクションなどはカーネル本体とは独立してカーネルモジュール(koファイル)に埋め込まれる点(ローダブルモジュールはカーネル本体とはバイナリが分離されてるので当然なんですが…)。またパラメータが設定されるのは INIT_MODULE システムコール実行時(kernel/modules.c)。このシステムコールでカーネルモジュールを読み込むとき(kernel/module.c の load_module)に parse_args が実行されて、パラメータと一致する kernel_param に値を設定する。

static int load_module(struct load_info *info, const char __user *uargs,
                       int flags)
{
    ...
        /* Module is ready to execute: parsing args may do that. */
        after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,
                                  -32768, 32767, mod,
    ...

カーネルパラメータ

カーネルに対する設定。カーネル実行時に変更できる。

現在の設定を確認する

sysctl で確認する。

# 全件表示
]# sysctl -a
abi.vsyscall32 = 1
crypto.fips_enabled = 0
debug.exception-trace = 1
debug.kprobes-optimization = 1
...

# 特定のプレフィックスのみ表示
]# sysctl fs
...
fs.file-max = 784541
fs.file-nr = 1600       0       784541
fs.inode-nr = 93731     30892
...

# 指定したパラメータのみ表示
]# sysctl fs.file-nr
fs.file-nr = 1600       0       784541

/proc/sys/ を直接参照してもいいが、 sysctl のほうが使い勝手が良い気がする。

]# cat /proc/sys/fs/file-nr
1568    0       784541

設定可能なパラメータ

Documentation/sysctl 配下に設定可能なパラメータと設定の意味がまとまっている。読む気にならない分量なので、やりたいことを WEB で検索したほうがよさげ。

設定を変更する

一時的に変更する

sysctl か /proc/sys を利用する。

]# sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

]# echo 1 >  /proc/sys/net/ipv4/ip_forward

永続的に変更する

/etc/sysctl.conf を直接利用するか /etc/sysctl.d/ 配下に適当なファイル名でファイルを作成する。

]# cat /etc/sysctl.d/network.conf
net.ipv4.ip_forward = 0

# すぐに設定ファイルを有効にする場合は -p を利用する
]# sysctl -p

ソースコード上の扱い

sysctl を使おうが最終的には /proc/sys インタフェースを利用する。

procfs の概要については以前まとめた。

www.kimullaa.com

まあ当たり前っちゃ当たり前だが、結局はファイルに読み書きがあったタイミングでカーネルが用意したハンドラを実行する。以前の記事と異なる点としては、sysctl に対応するファイル用に struct ctl_table が用意されていること。また fs/proc/proc_sysctl.c に共通的な処理がまとめられている。読めばわかりそうなのであまり深くは追わない。

net/ipv4/devinet.c

static struct ctl_table ctl_forward_entry[] = {
        {
                .procname       = "ip_forward",
                .data           = &ipv4_devconf.data[
                                        IPV4_DEVCONF_FORWARDING - 1],
                .maxlen         = sizeof(int),
                .mode           = 0644,
        // 書き込み時に呼ばれる関数
                .proc_handler   = devinet_sysctl_forward,
                .extra1         = &ipv4_devconf,
                .extra2         = &init_net,
        },
        { },
};
...
    // fs/proc/proc_sysctl.c の __register_sysctl_table に登録するとテーブル構造でよしなに仮想ファイルを作成してくれる
        forw_hdr = register_net_sysctl(net, "net/ipv4", tbl);

まとめ

Linux カーネルの挙動を変更する 4 つの方法について整理した。これらを知ったところで WEB でググったパラメータを WEB でググったように設定する未来が見えるが、深みのあるコピペができるようになるのではないか。深みのあるコピペ…。