如何根据启动时的串口打印信息,从宏观上找到对应的启动逻辑?
一、uboot启动
uboot启动信息:
loading Environment from NAND... OK
In: seriala40700000
Out: seriala40700000
Err: seriala40700000
Net: No ethernet found.
Hit any key to stop autoboot:
Booting from nand ... ...
NAND read: device 0 offset 0x400000,size 0x1000000
16777216 bytes read: OK
NAND read: device 0 offset 0x3c0o00,size 0x40000
262144 bytes read: OK
## Flattened Device Tree blob at 85000000
Booting using the fdt blob at 0x85000000
Loading Device Tree to0000000086ccc000,end oooo000086cd8e17... OK
根据以上串口信息,可以看到,uboot从nand中获取环境参数并启动,在跳过bootdelay后,从nand中读了两个分区,代码中找到对应的启动参数如下,
setenv bootargs noinitrd rootfstype=squashfs,ubifs console=ttyS0,115200n8 rdinit=/sbin/init mem=${kernelmem} boot=nand; \
nand read ${openwrt_addr_r} firmware 0x1000000; \
nand read ${fdt_addr_r} device-tree; booti ${kernel_addr_r} - ${fdt_addr_r} ;
可以发现这两个分区正是kernel和device-tree, 这两个分区使用nand命令读取,找到uboot源码中的nand命令的实现(do_nand),U_BOOT_CMD 宏将do_nand注册到uboot命令段(u_boot_list)中,
read操作是将nand中的kernel和device-tree拷贝到DDR中对应的位置
拷贝完成后,进入下一步的内核启动。
二、内核启动
内核启动信息(已删除部分非必要信息)
[ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd040]
[ 0.000000] Linux version 5.x.x
[ 0.000000] NUMA: No NUMA configuration found
...
[ 0.000000] psci: probing for conduit method from DT.
...
[ 0.000000] percpu: Embedded 17 pages/cpu s32576 r8192 d28864 u69632
[ 0.109832] pinctrl pinctrl: initialized NVT pinctrl driver
...
[ 0.228443] cryptd: max_cpu_qlen set to 1000
[ 0.246017] SCSI subsystem initialized
[ 0.248824] videodev: Linux video capture interface: v2.00
[ 0.260803] Advanced Linux Sound Architecture Driver Initialized.
[ 0.265851] clocksource: Switched to clocksource arch_sys_counter
[ 0.268969] NET: Registered protocol family 2
[ 0.269199] IP idents hash table entries: 4096 (order: 3, 32768 bytes, linear)
[ 0.270197] tcp_listen_portaddr_hash hash table entries: 128 (order: 1, 8192 bytes, linear)
[ 0.270240] TCP established hash table entries: 2048 (order: 2, 16384 bytes, linear)
[ 0.270297] TCP bind hash table entries: 2048 (order: 4, 114688 bytes, linear)
[ 0.270448] TCP: Hash tables configured (established 2048 bind 2048)
[ 0.270588] UDP hash table entries: 256 (order: 3, 32768 bytes, linear)
[ 0.270659] UDP-Lite hash table entries: 256 (order: 3, 32768 bytes, linear)
[ 0.271070] NET: Registered protocol family 1
[ 0.274660] RPC: Registered named UNIX socket transport module.
[ 0.274671] RPC: Registered udp transport module.
[ 0.274677] RPC: Registered tcp transport module.
[ 0.274682] RPC: Registered tcp NFSv4.1 backchannel transport module.
[ 0.289480] Initialise system trusted keyrings
[ 0.292459] workingset: timestamp_bits=44 max_order=16 bucket_order=0
[ 0.303852] squashfs: version 4.0 (2009/01/31) Phillip Lougher
[ 0.305383] NFS: Registering the id_resolver key type
[ 0.305460] Key type id_resolver registered
[ 0.305466] Key type id_legacy registered
[ 0.386903] NET: Registered protocol family 38
[ 0.386925] Key type asymmetric registered
[ 0.440567] nand: device found, Manufacturer ID: 0xef, Chip ID: 0xda
[ 0.440580] nand: Winbond W29N02GV
[ 0.440586] nand: 256 MiB, SLC, erase size: 128 KiB, page size: 2048, OOB size: 64
[ 0.440600] attach: page 2048, SMRA size 64, 60
[ 0.441382] Bad block table not found for chip 0
[ 0.442035] Bad block table not found for chip 0
[ 0.442050] Scanning device for bad blocks
[ 0.525290] Bad block table written to 0x00000ffe0000, version 0x01
[ 0.526384] Bad block table written to 0x00000ffc0000, version 0x01
[ 0.526932] 5 fixed-partitions partitions found on MTD device nand0
[ 0.526945] Creating 5 MTD partitions on "nand0":
...
[ 0.534645] 2 uimage-fw partitions found on MTD device firmware
...
[ 0.558965] spi-nor spi1.0: w25q128 (16384 Kbytes)
[ 0.559696] 1 fixed-partitions partitions found on MTD device spi1.0
...
[ 0.581585] CAN device driver interface
[ 0.582513] gmac 40120000.ethernet: IRQ eth_wake_irq not found
[ 0.605634] PPP generic driver version 2.4.2
[ 0.606086] PPP Deflate Compression module registered
[ 0.606094] NET: Registered protocol family 24
[ 0.607210] dwc2 40200000.usb: supply vusb_d not found, using dummy regulator
[ 0.607564] dwc2 40200000.usb: supply vusb_a not found, using dummy regulator
[ 0.815001] dwc2 40200000.usb: Configuration mismatch. dr_mode forced to device
[ 0.815098] dwc2 40200000.usb: EPs: 9, dedicated fifos, 3000 entries in SPRAM
[ 0.817200] ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver
[ 0.817212] ehci-platform: EHCI generic platform driver
...
[ 0.985934] i2c /dev entries driver
[ 1.008168] rtc-bl5372 0-0032: registered as rtc0
...
[ 1.067483] xxxxxx spi0.0: fbtft_property_value: width = 128、
...
[ 1.169065] graphics fb0: xxxxxx frame buffer, 128x64, 1 KiB video memory, 1 KiB buffer memory, fps=30, spi0.0 at 500000 Hz, mode : 3
[ 3.494913] ubi0: scanning is finished
...
[ 3.529200] block ubiblock0_0: created from ubi0:0(rootfs)
[ 3.547855] input: gpio_keys_test as /devices/platform/gpio_keys_test/input/input0
[ 3.549224] cfg80211: Loading compiled-in X.509 certificates for regulatory database
[ 3.567604] cfg80211: Loaded X.509 cert 'sforshee: 00b28ddf47aef9cea7'
[ 3.568037] platform regulatory.0: Direct firmware load for regulatory.db failed with error -2
[ 3.568055] platform regulatory.0: Falling back to sysfs fallback for: regulatory.db
[ 3.592695] VFS: Mounted root (squashfs filesystem) readonly on device 31:8.
[ 3.593097] Freeing unused kernel memory: 384K
[ 3.605069] Run /sbin/init as init process
[ 4.450565] init: Console is alive
[ 5.021450] kmodloader: loading kernel modules from /etc/modules-boot.d/*
[ 5.748349] kmodloader: done loading kernel modules from /etc/modules-boot.d/*
[ 5.757337] init: - preinit -
noinitrd rootfstype=squashfs,ubifs isolcpus=1 console=ttyS0,115200n8 rdinit=/sbin/init mem=248M boot=nand
noinitrd rootfstype=squashfs,ubifs isolcpus=1 console=ttyS0,115200n8 rdinit=/sbin/init mem=248M boot=nand
[ 6.931457] UBIFS (ubi0:1): Mounting in unauthenticated mode
...
[ 7.070570] mount_root: switching to ubifs overlay
[ 7.081731] overlayfs: "xino" feature enabled using 2 upper inode bits.
[ 7.094767] urandom-seed: Seeding with /etc/urandom.seed
[ 7.250743] procd: - early -
[ 8.044287] procd: - ubus -
[ 8.448459] procd: - init -Please press Enter to activate this console.
[ 8.773803] kmodloader: loading kernel modules from /etc/modules.d/*
[ 8.789284] tun: Universal TUN/TAP device driver, 1.6
[ 8.805047] kmodloader: done loading kernel modules from /etc/modules.d/*
[ 10.184646] gmac 40120000.ethernet eth0: PHY [stmmac-0:01] driver
...
[ 10.259244] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
[ 10.407613] device eth0 entered promiscuous mode
[ 10.525230] random: ubusd: uninitialized urandom read (4 bytes read)
...
[ 15.020644] urngd: v1.0.2 started.
...
[ 18.959710] gmac 40130000.ethernet eth1: PHY [stmmac-1:00] driver
...
[ 22.989161] IPv6: ADDRCONF(NETDEV_CHANGE): wlan1: link becomes ready
进入内核后,先加载各类驱动,包括网卡,USB,flash,DDR,准备OS运行环境等。
根据以上日志,在系统启动到第3.6秒时( Run /sbin/init as init process),启动了一个init进程。从内核的启动参数中,
noinitrd rootfstype=squashfs,ubifs isolcpus=1 console=ttyS0,115200n8 rdinit=/sbin/init mem=248M boot=nand
可以看到,init进程是系统启动的第一个进程。
三、init分析
在系统中找到init程序的位置(grep 命令),发现init程序是procd包中的一个程序。procd中的init.c文件main函数如下
int
main(int argc, char **argv)
{
pid_t pid;
ulog_open(ULOG_KMSG, LOG_DAEMON, "init");
sigaction(SIGTERM, &sa_shutdown, NULL);
sigaction(SIGUSR1, &sa_shutdown, NULL);
sigaction(SIGUSR2, &sa_shutdown, NULL);
sigaction(SIGPWR, &sa_shutdown, NULL);
if (selinux(argv))
exit(-1);
early();
cmdline();
watchdog_init(1);
pid = fork();
if (!pid) {
char *kmod[] = { "/sbin/kmodloader", "/etc/modules-boot.d/", NULL };
if (debug < 3)
patch_stdio("/dev/null");
execvp(kmod[0], kmod);
ERROR("Failed to start kmodloader: %m\n");
exit(EXIT_FAILURE);
}
if (pid <= 0) {
ERROR("Failed to start kmodloader instance: %m\n");
} else {
const struct timespec req = {0, 10 * 1000 * 1000};
int i;
for (i = 0; i < 1200; i++) {
if (waitpid(pid, NULL, WNOHANG) > 0)
break;
nanosleep(&req, NULL);
watchdog_ping();
}
}
uloop_init();
preinit();
uloop_run();
return 0;
}
在子进程中调用kmodloader,加载各种内核模块,父进程等待内核模块加载完毕后,进入preinit函数。preinit相关函数如下(preinit.c)
static void
spawn_procd(struct uloop_process *proc, int ret)
{
char *wdt_fd = watchdog_fd();
char *argv[] = { "/sbin/procd", NULL};
char dbg[2];
if (plugd_proc.pid > 0)
kill(plugd_proc.pid, SIGKILL);
unsetenv("PREINIT");
unlink("/tmp/.preinit");
check_sysupgrade();
DEBUG(2, "Exec to real procd now\n");
if (wdt_fd)
setenv("WDTFD", wdt_fd, 1);
check_dbglvl();
if (debug > 0) {
snprintf(dbg, 2, "%d", debug);
setenv("DBGLVL", dbg, 1);
}
execvp(argv[0], argv);
}
static void
plugd_proc_cb(struct uloop_process *proc, int ret)
{
proc->pid = 0;
}
void
preinit(void)
{
char *init[] = { "/bin/sh", "/etc/preinit", NULL };
char *plug[] = { "/sbin/procd", "-h", "/etc/hotplug-preinit.json", NULL };
int fd;
LOG("- preinit -\n");
plugd_proc.cb = plugd_proc_cb;
plugd_proc.pid = fork();
if (!plugd_proc.pid) {
execvp(plug[0], plug);
ERROR("Failed to start plugd: %m\n");
exit(EXIT_FAILURE);
}
if (plugd_proc.pid <= 0) {
ERROR("Failed to start new plugd instance: %m\n");
return;
}
uloop_process_add(&plugd_proc);
setenv("PREINIT", "1", 1);
fd = creat("/tmp/.preinit", 0600);
if (fd < 0)
ERROR("Failed to create sentinel file: %m\n");
else
close(fd);
preinit_proc.cb = spawn_procd;
preinit_proc.pid = fork();
if (!preinit_proc.pid) {
execvp(init[0], init);
ERROR("Failed to start preinit: %m\n");
exit(EXIT_FAILURE);
}
if (preinit_proc.pid <= 0) {
ERROR("Failed to start new preinit instance: %m\n");
return;
}
uloop_process_add(&preinit_proc);
DEBUG(4, "Launched preinit instance, pid=%d\n", (int) preinit_proc.pid);
}
可以看到,preinit又在一个子进程中启动脚本/etc/preinit,其将按照顺序启动/lib/preinit/目录下的所有脚本。在preinit创建的另一个进程中,启动procd程序,procd的状态机代码如下 (state.c)
static void state_enter(void)
{
char ubus_cmd[] = "/sbin/ubusd";
struct passwd *p;
switch (state) {
case STATE_EARLY:
LOG("- early -\n");
watchdog_init(0);
hotplug("/etc/hotplug.json");
procd_coldplug();
break;
case STATE_UBUS:
// try to reopen incase the wdt was not available before coldplug
watchdog_init(0);
set_stdio("console");
p = getpwnam("ubus");
if (p) {
int ret;
LOG("- ubus -\n");
mkdir(p->pw_dir, 0755);
ret = chown(p->pw_dir, p->pw_uid, p->pw_gid);
if (ret)
LOG("- ubus - failed to chown(%s)\n", p->pw_dir);
} else {
LOG("- ubus (running as root!) -\n");
}
procd_connect_ubus();
service_start_early("ubus", ubus_cmd, p?"ubus":NULL, p?"ubus":NULL);
break;
case STATE_INIT:
LOG("- init -\n");
procd_inittab();
procd_inittab_run("respawn");
procd_inittab_run("askconsole");
procd_inittab_run("askfirst");
procd_inittab_run("sysinit");
// switch to syslog log channel
ulog_open(ULOG_SYSLOG, LOG_DAEMON, "procd");
break;
case STATE_RUNNING:
LOG("- init complete -\n");
procd_inittab_run("respawnlate");
procd_inittab_run("askconsolelate");
break;
case STATE_SHUTDOWN:
/* Redirect output to the console for the users' benefit */
set_console();
LOG("- shutdown -\n");
procd_inittab_run("shutdown");
sync();
break;
case STATE_HALT:
// To prevent killed processes from interrupting the sleep
signal(SIGCHLD, SIG_IGN);
LOG("- SIGTERM processes -\n");
kill(-1, SIGTERM);
sync();
sleep(1);
LOG("- SIGKILL processes -\n");
kill(-1, SIGKILL);
sync();
sleep(1);
#ifndef DISABLE_INIT
perform_halt();
#else
exit(EXIT_SUCCESS);
#endif
break;
default:
ERROR("Unhandled state %d\n", state);
return;
};
}
在procd程序运行到STATE_INIT状态时,执行函数procd_inittab以及procd_inittab_run,将会执行/etc/inittab中的内容,inittab如下
::sysinit:/etc/init.d/rcS S boot
::shutdown:/etc/init.d/rcS K shutdown
::askconsole:/usr/libexec/login.sh
第一行将按照rc.d中规定启动的顺序进行执行。至此,openwrt系统完成了启动。