我們一路走來看過檔案的介面、程序管理、訊號處理、記憶體管理、網路、以及一些其他的系統呼叫,也很遺憾無法來得及研究檔案管理、時間處理等功能的系統呼叫。我們今天要來看的是非常系統層級的呼叫,牽涉到機器本身運行的狀態,那就是:reboot
。
手冊reboot(2)
中這麼寫著:
NAME
reboot - reboot or enable/disable Ctrl-Alt-Del
SYNOPSIS
/* For libc4 and libc5 the library call and the system call
are identical, and since kernel version 2.1.30 there are
symbolic names LINUX_REBOOT_* for the constants and a
fourth argument to the call: */
#include <unistd.h>
#include <linux/reboot.h>
int reboot(int magic, int magic2, int cmd, void *arg);
/* Under glibc and most alternative libc's (including uclibc, dietlibc,
musl and a few others), some of the constants involved have gotten
symbolic names RB_*, and the library call is a 1-argument
wrapper around the 3-argument system call: */
#include <unistd.h>
#include <sys/reboot.h>
int reboot(int cmd);
也就是說Linux環境中支援兩種wrapper,其中第一個需要付四個參數,是和系統呼叫一對一的版本;另一個比較傳統的只吃一個參數,會在函式庫中擴充。這個系統呼叫不只是重新開機的字面上意義,也能夠用來控制Ctrl-Alt-Delete組合鍵的意義。
有趣的是定義的magic值,有點類似通關密語。第一個的magic
只允許0xfee1dead
,第二個則允許四組值,乍看之下的10進位都不明所以,手冊中還提示轉成16進位就會有意義。據筆者所知,應該是Linus Torvalds的家人們的生日。
在kernel/reboot.c
272 /*
273 * Reboot system call: for obvious reasons only root may call it,
274 * and even root needs to set up some magic numbers in the registers
275 * so that some mistake won't make this reboot the whole machine.
276 * You can also set the meaning of the ctrl-alt-del-key here.
277 *
278 * reboot doesn't sync: do that yourself before calling this.
279 */
280 SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
281 void __user *, arg)
282 {
283 struct pid_namespace *pid_ns = task_active_pid_ns(current);
284 char buffer[256];
285 int ret = 0;
286
287 /* We only trust the superuser with rebooting the system. */
288 if (!ns_capable(pid_ns->user_ns, CAP_SYS_BOOT))
289 return -EPERM;
290
291 /* For safety, we require "magic" arguments. */
292 if (magic1 != LINUX_REBOOT_MAGIC1 ||
293 (magic2 != LINUX_REBOOT_MAGIC2 &&
294 magic2 != LINUX_REBOOT_MAGIC2A &&
295 magic2 != LINUX_REBOOT_MAGIC2B &&
296 magic2 != LINUX_REBOOT_MAGIC2C))
297 return -EINVAL;
298
這個系統呼叫有一些針對namespace的擴張功能,也就是在容器或相似的環境中,reboot
搭配一些指令能夠將SIGHUP
或SIGINT
訊號傳給該容器或PIDNS的init程序。291行之後則是檢驗傳入的參數。
299 /*
300 * If pid namespaces are enabled and the current task is in a child
301 * pid_namespace, the command is handled by reboot_pid_ns() which will
302 * call do_exit().
303 */
304 ret = reboot_pid_ns(pid_ns, cmd);
305 if (ret)
306 return ret;
reboot_pid_ns
即是關於當前程序處在有別於初始PIDNS的其他NS的狀態的特別處理,位在kernel/pid_namespace.c
,
308 int reboot_pid_ns(struct pid_namespace *pid_ns, int cmd)
309 {
310 if (pid_ns == &init_pid_ns)
311 return 0;
312
313 switch (cmd) {
314 case LINUX_REBOOT_CMD_RESTART2:
315 case LINUX_REBOOT_CMD_RESTART:
316 pid_ns->reboot = SIGHUP;
317 break;
318
319 case LINUX_REBOOT_CMD_POWER_OFF:
320 case LINUX_REBOOT_CMD_HALT:
321 pid_ns->reboot = SIGINT;
322 break;
323 default:
324 return -EINVAL;
325 }
326
327 read_lock(&tasklist_lock);
328 force_sig(SIGKILL, pid_ns->child_reaper);
329 read_unlock(&tasklist_lock);
330
331 do_exit(0);
332
333 /* Not reached */
334 return 0;
335 }
若是初始NS,則回傳0。case判斷的部份也很容易理解。最後在328行強制給出一個SIGKILL給這個NS的負責程序。
311 if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)
312 cmd = LINUX_REBOOT_CMD_HALT;
313
314 mutex_lock(&reboot_mutex);
315 switch (cmd) {
316 case LINUX_REBOOT_CMD_RESTART:
317 kernel_restart(NULL);
318 break;
319
320 case LINUX_REBOOT_CMD_CAD_ON:
321 C_A_D = 1;
322 break;
323
324 case LINUX_REBOOT_CMD_CAD_OFF:
325 C_A_D = 0;
326 break;
pm_power_off
是一個函數指標,依照處理器以及平台的不同會有不同的函式(分別對應到arch/x86/platform
底下的不同平台)來處理。然後就開始依照cmd
的差異判斷應該做的事情。如320~326行的C_A_D
設置。若是要重新啟動,則kernel_restart
:
214 void kernel_restart(char *cmd)
215 {
216 kernel_restart_prepare(cmd);
217 migrate_to_reboot_cpu();
218 syscore_shutdown();
219 if (!cmd)
220 pr_emerg("Restarting system\n");
221 else
222 pr_emerg("Restarting system with command '%s'\n", cmd);
223 kmsg_dump(KMSG_DUMP_RESTART);
224 machine_restart(cmd);
225 }
226 EXPORT_SYMBOL_GPL(kernel_restart);
原來要重新啟動機器還要讓控制權轉移到編號0的CPU?
繼續看其他的指令,
328 case LINUX_REBOOT_CMD_HALT:
329 kernel_halt();
330 do_exit(0);
331 panic("cannot halt");
332
333 case LINUX_REBOOT_CMD_POWER_OFF:
334 kernel_power_off();
335 do_exit(0);
336 break;
337
338 case LINUX_REBOOT_CMD_RESTART2:
339 ret = strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1);
340 if (ret < 0) {
341 ret = -EFAULT;
342 break;
343 }
344 buffer[sizeof(buffer) - 1] = '\0';
345
346 kernel_restart(buffer);
347 break;
前兩者和展示過的kernel_restart
都有類似的結構,透過kmsg_dump
印不同的訊息之後,呼叫與之對應的machine_*
呼叫,控制與架構相關的事項吧。LINUX_REBOOT_CMD_RESTART2
則是帶重啟參數的狀況,就還需要這些從使用者空間搬資料的動作。
349 #ifdef CONFIG_KEXEC_CORE
350 case LINUX_REBOOT_CMD_KEXEC:
351 ret = kernel_kexec();
352 break;
353 #endif
354
355 #ifdef CONFIG_HIBERNATION
356 case LINUX_REBOOT_CMD_SW_SUSPEND:
357 ret = hibernate();
358 break;
359 #endif
360
361 default:
362 ret = -EINVAL;
363 break;
364 }
365 mutex_unlock(&reboot_mutex);
366 return ret;
367 }
kexec可以說是個使用當前系統作為bootloader的機制,讓下一次開機可以在指定kernel image的情況下快速載入。hibernate
則是休眠,在kernel/power/hibernate.c
之中,要做的事情很多,比方說sync檔案系統、凍結所有正在執行的程序、儲存整個系統的映像等。最後如果是會回傳的cmd
,則會解開reboot_mutex
,然後回傳。
本文瀏覽了reboot
這個目前為止看到與機器最有直接關聯的系統呼叫,雖然名為reboot,實際上的業務內容卻包含重啟(整個作業系統或是kexec)、關電源、停機、休眠等會出現在系統選單中的選項。
這三十天的旅程結束了,筆者覺得在過程中學習到的東西非常珍貴,也許分享得不夠高技術水準、也或者表達能力太差,但是這連續發文的挑戰,終究是作到了。非常開心能夠有這樣的機會參加這麼饒富意義的活動。雖然是鐵人賽的結束,但是筆者也深深理解到還有許多要進步的地方,因此最後一個系統呼叫,特別選擇reboot
,提醒自己這不是一個halt
,而是一個重新激發自己的機會。
每一篇文章都堅持一定要打上的最後一句話「我們明日再會」,在這最後一篇似乎有點格格不入。但我想那不是不適合,而是不夠貼切。在這個系列鐵人賽中的確有這樣的關係存在,但自今天之後,諸位邦友與筆者仍然會同在這個資訊領域的技術之道上共同前進;也許不會直接接觸、問答,也可能不會有機會看到彼此的技術文章,但是總是同在一個世界之中共同為了技術增長而打拼。
最後的最後,感謝各位讀者用眼睛監督筆者,是你們的關心以及期待讓這系列成為可能。雖然題目似乎太偏作業系統,而與ITHOME的主流關心領域有點格格不入,但看來還是有超乎我預期的人數點閱,令人相當感動。我想,就讓我們明年的鐵人賽再見吧!