linux systemにおけるinitの最期
2018/12/18
元々の予定はボツになった!?ので ちょっと古い話になります。
7日目のsystemd理解のヒント - systemdの概念と歴史と相反するような記事です。
古い情報も必要としている組込もんもいるので、その助けとなりましたら幸いです。
initとは
PID=1として、カーネルの初期化が終わって初めて生成されるプロセスです。ユーザランドで必要なファイルシステムのマウント・システム設定・サービス(デーモン)やプロセス起動を行います。
kernel parameterに init=でinitコマンドを指定することができます。省略すると /sbin/initなどが参照されます。
init=/etc/preinitここでは 組込装置を前提として、busyboxのinitを想定します
設定ファイル
initが起動すると、以下のファイルを参照します。run levelに応じた処理を記述するようになっています。シリアルコンソールの設定なども、ここに記述することができます。
/etc/inittab■/etc/inittab
FILE: 06.build/patches/rootfs/etc/inittab
# /etc/inittab # # Copyright (C) 2001 Erik Andersen <andersen@codepoet.org> # # Note: BusyBox init doesn't support runlevels. The runlevels field is # completely ignored by BusyBox init. If you want runlevels, use # sysvinit. # # Format for each entry: <id>:<runlevels>:<action>:<process> # # id == tty to run on, or empty for /dev/console # runlevels == ignored # action == one of sysinit, respawn, askfirst, wait, and once # process == program to run # Startup the system ::sysinit:/bin/mount -t proc proc /proc ::sysinit:/bin/mount -o remount,rw / ::sysinit:/bin/mkdir -p /dev/pts ::sysinit:/bin/mkdir -p /dev/shm ::sysinit:/bin/mount -a ::sysinit:/bin/hostname -F /etc/hostname # now run any rc scripts ::sysinit:/etc/init.d/rcS # Put a getty on the serial port #ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 # GENERIC_SERIAL # Stuff to do for the 3-finger salute #::ctrlaltdel:/sbin/reboot # Stuff to do before rebooting ::shutdown:/etc/init.d/rcK ::shutdown:/sbin/swapoff -a ::shutdown:/bin/umount -a -r
システムの終了
reboot、halt、shutdownといったコマンドを実行すれば、システムを再起動・停止・終了することができますね。このコマンドを実行したとき、何が行われているのでしょうか。
rebootに対象を絞って、かつ、busybox版で確認していきましょう。
v1.19.4
FILE: init/halt.c
//applet:IF_HALT(APPLET(halt, BB_DIR_SBIN, BB_SUID_DROP)) //applet:IF_HALT(APPLET_ODDNAME(poweroff, halt, BB_DIR_SBIN, BB_SUID_DROP, poweroff)) //applet:IF_HALT(APPLET_ODDNAME(reboot, halt, BB_DIR_SBIN, BB_SUID_DROP, reboot))rebootは haltのaliasになるようですね。
int halt_main(int argc UNUSED_PARAM, char **argv) | ... sleep(0); write_wtmp(); // last.logを記録する sync(); // デフォルトはsync(2)を実行する. kill(1,SIGTERM) // initに signalを飛ばす. -f付きなら reboot(2)で 0x01234567 を渡す..コメントに書いたようにsignalを飛ばすだけでした。
initに処理が移ります:
FILE: init/init.c
FUNC: int init_main(int argc UNUSED_PARAM, char **argv)
bb_signals(0 + (1 << SIGUSR1) /* halt */ + (1 << SIGTERM) /* reboot */ + (1 << SIGUSR2) /* poweroff */ , halt_reboot_pwoff);signal handlerを登録します。bb_*は、Busyboxのライブラリ関数です。
第一引数で指定されたsignalを許可し、第二引数の関数をハンドラとして登録します。
ハンドラを見ていきましょう:
static void halt_reboot_pwoff(int sig) { const char *m; unsigned rb; /* We may call run() and it unmasks signals, * including the one masked inside this signal handler. * Testcase which would start multiple reboot scripts: * while true; do reboot; done * Preventing it: */ reset_sighandlers_and_unblock_sigs(); // ハンドラをデフォルトに戻して, 有効化 run_shutdown_and_kill_processes(); // 後述. run_actrions(),全プロセスにSIGTERM, sync(2),sleep(1sec),SIGKILL,sync(2) m = "halt"; rb = RB_HALT_SYSTEM; if (sig == SIGTERM) { m = "reboot"; rb = RB_AUTOBOOT; } else if (sig == SIGUSR2) { m = "poweroff"; rb = RB_POWER_OFF; } message(L_CONSOLE, "Requesting system %s", m); pause_and_low_level_reboot(rb); // 後述. sleep(1sec), vfork(2)した子で reboot(magic:0x1234567), 親(pid=1)はsleep forever. /* not reached */ }いくつか関数を読んでいるので、引用します。コメントに書いたような処理を行っています。
static void run_shutdown_and_kill_processes(void) { /* Run everything to be run at "shutdown". This is done _prior_ * to killing everything, in case people wish to use scripts to * shut things down gracefully... */ run_actions(SHUTDOWN); message(L_CONSOLE | L_LOG, "The system is going down NOW!"); /* Send signals to every process _except_ pid 1 */ kill(-1, SIGTERM); message(L_CONSOLE | L_LOG, "Sent SIG%s to all processes", "TERM"); sync(); sleep(1); kill(-1, SIGKILL); message(L_CONSOLE, "Sent SIG%s to all processes", "KILL"); sync(); /*sleep(1); - callers take care about making a pause */ }run_actions(SHUTDOWN);では、 inittabで記述した "::shutdown:"のコマンドを実行していきます。
swapoff, unmount -a -rが特徴的ですね。とくに後者は ファイルシステムをアンマウントすることで、
終了処理中に書き込みを行わせないようにしています(アンマウントできないときは
read onlyでリマウントするオプションが設定されています)。
static void pause_and_low_level_reboot(unsigned magic) { pid_t pid; /* Allow time for last message to reach serial console, etc */ sleep(1); /* We have to fork here, since the kernel calls do_exit(EXIT_SUCCESS) * in linux/kernel/sys.c, which can cause the machine to panic when * the init process exits... */ pid = vfork(); if (pid == 0) { /* child */ reboot(magic); _exit(EXIT_SUCCESS); } while (1) sleep(1); }reboot(2)を実行しているようなので、libcを見ていきましょう。
libc
reboot(2)は以下のようにシステムコールを実行するだけでした。/* Call kernel with additional two arguments the syscall requires. */ int reboot (int howto) { return INLINE_SYSCALL (reboot, 3, (int) 0xfee1dead, 672274793, howto); }magic1: 0xfee1dead
magic2: 0x28121969
マジックナンバーが入っているけれども、これはカーネルソースで定義されている値ですね。
FILE: include/linux/reboot.h
/* * Magic values required to use _reboot() system call. */ #define LINUX_REBOOT_MAGIC1 0xfee1dead #define LINUX_REBOOT_MAGIC2 672274793 #define LINUX_REBOOT_MAGIC2A 85072278 #define LINUX_REBOOT_MAGIC2B 369367448 #define LINUX_REBOOT_MAGIC2C 537993216 /* * Commands accepted by the _reboot() system call. * * RESTART Restart system using default command and mode. * HALT Stop OS and give system control to ROM monitor, if any. * CAD_ON Ctrl-Alt-Del sequence causes RESTART command. * CAD_OFF Ctrl-Alt-Del sequence sends SIGINT to init task. * POWER_OFF Stop OS and remove all power from system, if possible. * RESTART2 Restart system using given command string. * SW_SUSPEND Suspend system using software suspend if compiled in. * KEXEC Restart system using a previously loaded Linux kernel */ #define LINUX_REBOOT_CMD_RESTART 0x01234567 #define LINUX_REBOOT_CMD_HALT 0xCDEF0123 #define LINUX_REBOOT_CMD_CAD_ON 0x89ABCDEF #define LINUX_REBOOT_CMD_CAD_OFF 0x00000000 #define LINUX_REBOOT_CMD_POWER_OFF 0x4321FEDC #define LINUX_REBOOT_CMD_RESTART2 0xA1B2C3D4 #define LINUX_REBOOT_CMD_SW_SUSPEND 0xD000FCE2 #define LINUX_REBOOT_CMD_KEXEC 0x45584543
kernel実装
(v4.4.120を参照しています)システムコールから呼ばれる関数は以下ですね:
FILE: kernel/reboot.c
/* * Reboot system call: for obvious reasons only root may call it, * and even root needs to set up some magic numbers in the registers * so that some mistake won't make this reboot the whole machine. * You can also set the meaning of the ctrl-alt-del-key here. * * reboot doesn't sync: do that yourself before calling this. */ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, void __user *, arg)
mutex_lock(&reboot_mutex); switch (cmd) { case LINUX_REBOOT_CMD_RESTART: kernel_restart(NULL); break; .. mutex_unlock(&reboot_mutex); return ret; }
/** * kernel_restart - reboot the system * @cmd: pointer to buffer containing command to execute for restart * or %NULL * * Shutdown everything and perform a clean reboot. * This is not safe to call in interrupt context. */ void kernel_restart(char *cmd) { kernel_restart_prepare(cmd); migrate_to_reboot_cpu(); syscore_shutdown(); // syscore_ops_listを走査する if (!cmd) printk(KERN_EMERG "Restarting system.\n"); else printk(KERN_EMERG "Restarting system with command '%s'.\n", cmd); kmsg_dump(KMSG_DUMP_RESTART); machine_restart(cmd); } EXPORT_SYMBOL_GPL(kernel_restart);
void kernel_restart_prepare(char *cmd) { blocking_notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd); system_state = SYSTEM_RESTART; usermodehelper_disable(); // timeout: 5sec device_shutdown(); // timeoutなし, devices_kset->list を走査する. }device_shutdown()と syscore_shutdown()の詳細は追い切れていませんが、
登録されているコールバック関数を呼び出して後処理をしているようです。
loadable moduleは removeされるような挙動を見かけたので、ここで解放されていると思われます(要確認)。
アーキテクチャ依存部: machine_restart()
armの場合は以下のとおり。さらにプラットフォーム依存・ボード依存部で定義された関数呼び出しへと続きます。
FILE: arch/arm/kernel/reboot.c
void machine_restart(char *cmd) { local_irq_disable(); smp_send_stop(); if (arm_pm_restart) arm_pm_restart(reboot_mode, cmd); else do_kernel_restart(cmd); /* Give a grace period for failure to restart of 1s */ mdelay(1000); /* Whoops - the platform was unable to reboot. Tell the user! */ printk("Reboot failed -- System halted\n"); local_irq_disable(); while (1); }このあたりは使用されているターゲットのコードを追いかけましょう。
参考まで、arm64の場合は以下のとおり。firmwareがUEFI対応であれば、それを使うようです。
FILE: arm64/kernel/process.c
/* * Restart requires that the secondary CPUs stop performing any activity * while the primary CPU resets the system. Systems with multiple CPUs must * provide a HW restart implementation, to ensure that all CPUs reset at once. * This is required so that any code running after reset on the primary CPU * doesn't have to co-ordinate with other CPUs to ensure they aren't still * executing pre-reset code, and using RAM that the primary CPU's code wishes * to use. Implementing such co-ordination would be essentially impossible. */ void machine_restart(char *cmd) { /* Disable interrupts first */ local_irq_disable(); smp_send_stop(); /* * UpdateCapsule() depends on the system being reset via * ResetSystem(). */ if (efi_enabled(EFI_RUNTIME_SERVICES)) efi_reboot(reboot_mode, NULL); /* Now call the architecture specific reboot code. */ if (arm_pm_restart) arm_pm_restart(reboot_mode, cmd); else do_kernel_restart(cmd); /* * Whoops - the architecture was unable to reboot. */ printk("Reboot failed -- System halted\n"); while (1); }
FILE: kernel/reboot.c
/** * do_kernel_restart - Execute kernel restart handler call chain * * Calls functions registered with register_restart_handler. * * Expected to be called from machine_restart as last step of the restart * sequence. * * Restarts the system immediately if a restart handler function has been * registered. Otherwise does nothing. */ void do_kernel_restart(char *cmd) { atomic_notifier_call_chain(&restart_handler_list, reboot_mode, cmd); }
奥が深いですね...(ぇ