Debian 11 在 ARM CPU 的開機流程
本文中,開機流程分為三大部分:
一開始先執行 UBOOT,接著 UBoot 將控制權交給 Kernel,而 Kernel 執行到最後,會將控制權交給作業系統。
UBOOT => Kernel => Operating System。
UBOOT
上電後,CPU 就會開始執行 UBoot,UBoot 最後會呼叫 start_kernel() 函式,嘗試將控制權交給 kernel,程式碼流程如下。
在 u-boot/arch/arm/lib/bootm.c 的 do_bootm_linux() 函式,會先呼叫 boot_prep_linux() 函式來準備 kernel command line,接著再呼叫 boot_jump_linux() 函式。
在 boot_jump_linux() 會呼叫 announce_and_cleanup(),而在 announce_and_cleanup() 函式中,就可以看到啟動 kernel 的關鍵函式名稱 start_kernel。
/* Main Entry point for arm bootm implementation * * Modeled after the powerpc implementation * DIFFERENCE: Instead of calling prep and go at the end * they are called if subcommand is equal 0. */ int do_bootm_linux(int flag, int argc, char * const argv[], bootm_headers_t *images) { ... boot_prep_linux(images); boot_jump_linux(images, flag); return 0; } /* Subcommand: GO */ static void boot_jump_linux(bootm_headers_t *images, int flag) { #ifdef CONFIG_ARM64 void (*kernel_entry)(void *fdt_addr, void *res0, void *res1, void *res2); int fake = (flag & BOOTM_STATE_OS_FAKE_GO); int es_flag = 0; #if defined(CONFIG_AMP) es_flag = arm64_switch_amp_pe(images); #elif defined(CONFIG_ARM64_SWITCH_TO_AARCH32) es_flag = arm64_switch_aarch32(images); #endif kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1, void *res2))images->ep; debug("## Transferring control to Linux (at address %lx)...\n", (ulong) kernel_entry); bootstage_mark(BOOTSTAGE_ID_RUN_OS); announce_and_cleanup(images, fake); if (!fake) { #ifdef CONFIG_ARMV8_PSCI armv8_setup_psci(); #endif do_nonsec_virt_switch(); update_os_arch_secondary_cores(images->os.arch); #ifdef CONFIG_ARMV8_SWITCH_TO_EL1 armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0, (u64)switch_to_el1, ES_TO_AARCH64); #else if ((IH_ARCH_DEFAULT == IH_ARCH_ARM64) && (images->os.arch == IH_ARCH_ARM)) armv8_switch_to_el2(0, (u64)gd->bd->bi_arch_number, (u64)images->ft_addr, es_flag, (u64)images->ep, ES_TO_AARCH32); else armv8_switch_to_el2((u64)images->ft_addr, 0, 0, es_flag, images->ep, ES_TO_AARCH64); #endif } #else ... } /** * announce_and_cleanup() - Print message and prepare for kernel boot * * @fake: non-zero to do everything except actually boot */ static void announce_and_cleanup(bootm_headers_t *images, int fake) { ulong us; bootstage_mark_name(BOOTSTAGE_ID_BOOTM_HANDOFF, "start_kernel"); #ifdef CONFIG_BOOTSTAGE_FDT bootstage_fdt_add_report(); #endif #ifdef CONFIG_BOOTSTAGE_REPORT bootstage_report(); #endif #ifdef CONFIG_USB_DEVICE udc_disconnect(); #endif board_quiesce_devices(images); /* Flush all console data */ flushc(); /* * Call remove function of all devices with a removal flag set. * This may be useful for last-stage operations, like cancelling * of DMA operation or releasing device internal buffers. */ dm_remove_devices_flags(DM_REMOVE_ACTIVE_ALL); cleanup_before_linux(); us = (get_ticks() - gd->sys_start_tick) / (COUNTER_FREQUENCY / 1000000); printf("Total: %ld.%ld ms\n", us / 1000, us % 1000); printf("\nStarting kernel ...%s\n\n", fake ? "(fake run for tracing)" : ""); }
Kernel
kernel 的 start_kernel() 函式位於 kernel/init/main.c 中,如下:
asmlinkage __visible void __init __no_sanitize_address start_kernel(void) { char *command_line; char *after_dashes; set_task_stack_end_magic(&init_task); smp_setup_processor_id(); debug_objects_early_init(); cgroup_init_early(); local_irq_disable(); early_boot_irqs_disabled = true; /* * Interrupts are still disabled. Do necessary setups, then * enable them. */ boot_cpu_init(); page_address_init(); pr_notice("%s", linux_banner); early_security_init(); setup_arch(&command_line); setup_boot_config(command_line); setup_command_line(command_line); setup_nr_cpu_ids(); setup_per_cpu_areas(); smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */ boot_cpu_hotplug_init(); build_all_zonelists(NULL); page_alloc_init(); #ifdef CONFIG_ARCH_ROCKCHIP { const char *s = saved_command_line; const char *e = &saved_command_line[strlen(saved_command_line)]; int n = pr_notice("Kernel command line: %s\n", saved_command_line); n -= strlen("Kernel command line: "); s += n; /* command line maybe too long to print one time */ while (n > 0 && s < e) { n = pr_cont("%s\n", s); s += n; } } #else pr_notice("Kernel command line: %s\n", saved_command_line); #endif ... }
接著 kernel 繼續執行,最後會嘗試再將控制權移交給 Operating System,做法是執行檔案系統中的 init 檔案。
相關程式碼位於 kernel/init/main.c 的 kernel_init() 函式中,如果無法順利執行,將會導致 kernel panic:
static int __ref kernel_init(void *unused)
{
....
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/admin-guide/init.rst for guidance.");
}
Operating System
在 Debian 系統作確認,可以看到 /sbin/init 的 PID 為 1,也就是 linux 系統的第一個啟動程序。
# ps aux | grep init
root 1 0.4 0.1 164844 9780 ? Ss 02:52 0:17 /sbin/init
可以看到 /sbin/init 是一個連結,連接到 /lib/systemd/systemd:
# ls -l /sbin/init /etc/init /bin/init /bin/sh
ls: 无法访问 '/etc/init': 没有那个文件或目录
ls: 无法访问 '/bin/init': 没有那个文件或目录
lrwxrwxrwx 1 root root 4 12月 15 2021 /bin/sh -> dash
lrwxrwxrwx 1 root root 20 7月 13 2021 /sbin/init -> /lib/systemd/systemd
也就是說,實際上,是以 systemd 為啟動服務管理機制。
systemd 是一個專用於 Linux 作業系統的系統與服務管理器。 當作為啟動程序(PID=1)執行時,它將作為初始化系統執行, 也就是啟動並維護各種使用者空間的服務。
Kernel 主動呼叫 systemd 程式後,會以 default.target 流程開機:
systemd 執行 sysinit.target 初始化系統及 basic.target 準備作業系統;
systemd 啟動 multi-user.target 下的本機與伺服器服務;
systemd 執行 multi-user.target 下的 /etc/rc.d/rc.local 檔案;
systemd 執行 multi-user.target 下的 getty.target 及登入服務;
systemd 執行 graphical 需要的服務
bash 如何被執行? 何時被執行?
在進入桌面時,bash 會被執行。
以 ssh 登入,bash 會被執行。
以 UART 介面登入,bash 會被執行。
接下來是 bash,在 bash 的 manual 說明得很清楚:
當 Bash 作為 interactive login shell 或作為 non-interactive shell with --login option,如果 /etc/profile 存在,會先從文件中讀取並執行該檔案。
讀取該文件後,接著會尋找 ~/.bash_profile , ~/.bash_login 和 ~/.profile ,然後從存在的第一個可讀檔案中,讀取並執行命令。
在啟動 shell 程序時,可以使用 --noprofile 選項,來禁止此行為。
當 interactive login shell 退出時,或非 non-interactive shell 執行 exit 內置命令時,Bash 會從文件中讀取並執行命令 ~/.bash_logout (如果該檔案存在的話)。
在 /etc/profile 中,會一一執行 /etc/profile.d/ 下的 *.sh 檔案:
...
if [ -d /etc/profile.d ]; then
for i in /etc/profile.d/*.sh; do
if [ -r $i ]; then
. $i
fi
done
unset i
fi
Reference
systemd, init - systemd 系統與服務管理器
文字內容 或 影像內容 部份參考、引用自網路,如有侵權,請告知,謝謝。
留言列表