iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 30
1
自我挑戰組

跨界的追尋:trace 30個基本Linux系統呼叫系列 第 30

trace 30個基本Linux系統呼叫第三十日:從今以後繼續努力:reboot

前情提要

我們一路走來看過檔案的介面、程序管理、訊號處理、記憶體管理、網路、以及一些其他的系統呼叫,也很遺憾無法來得及研究檔案管理、時間處理等功能的系統呼叫。我們今天要來看的是非常系統層級的呼叫,牽涉到機器本身運行的狀態,那就是: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搭配一些指令能夠將SIGHUPSIGINT訊號傳給該容器或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的主流關心領域有點格格不入,但看來還是有超乎我預期的人數點閱,令人相當感動。我想,就讓我們明年的鐵人賽再見吧!


上一篇
trace 30個基本Linux系統呼叫第二十九日:對話:recvfrom與sendto
系列文
跨界的追尋:trace 30個基本Linux系統呼叫30

尚未有邦友留言

立即登入留言