基于qemu-riscv从0开始构建嵌入式linux系统ch13. 最小根文件系统完成

busybox中init进程解析

init进程代码主入口在busybox-1.33.1/init/init.c:1039,我们从上到下粗略分析下流程:

  • 创建信号集并且接管一系列系统信号,比如SIGINT、SIGQUIT、SIGPWR、SIGUSR1、SIGTERM、SIGUSR2等等。这些用于接管处理系统关机、重启等事件,因为init进程是不能直接退出的。

  • 设置默认的环境变量

HOME=/
PATH=/sbin:/usr/sbin:/bin:/usr/bin
SHELL=/bin/sh
USER=root
  • parse_inittab()函数读取文件/etc/inittab,如果未找到正确的配置文件,则通过以下代码配置移植默认触发动作。busybox-1.33.1/init/init.c:679。所以我们需要正确配置该文件才能让系统启动正确。

/* No inittab file - set up some default behavior */
/* Sysinit */
new_init_action(SYSINIT, INIT_SCRIPT, "");
/* Askfirst shell on tty1-4 */
new_init_action(ASKFIRST, bb_default_login_shell, "");
//TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users
new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
new_init_action(ASKFIRST, bb_default_login_shell, VC_3);
new_init_action(ASKFIRST, bb_default_login_shell, VC_4);
/* Reboot on Ctrl-Alt-Del */
new_init_action(CTRLALTDEL, "reboot", "");
/* Umount all filesystems on halt/reboot */
new_init_action(SHUTDOWN, "umount -a -r", "");
/* Swapoff on halt/reboot */
new_init_action(SHUTDOWN, "swapoff -a", "");
/* Restart init when a QUIT is received */
new_init_action(RESTART, "init", "");
  • 调用run_actions函数依次传入SYSINIT、WAIT、ONCE、RESPAWN、ASKFIRST,将注册的命令依次执行完成初始化。函数调用链为void run_actions(int action_type)–>pid_t run(const struct init_action *a)–>static void init_exec(const char *command)(fork后)。

  • 进入while(1)等待所以子进程退出重新回到run_actions(RESPAWN|ASKFIRST),相当于退出登录了。

/etc/inittab编写

ok,了解了init进程做了什么再来编写配置脚本就轻松很多,我们本节编写一个最基础简单的启动配置环境,/etc/inittab内容如下:

::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh

即系统启动时,执行一次/etc/init.d/rcS脚本,然后进入登录界面执行/bin/sh,前面的“-”表示这是个登录shell,会连接stdin到这个tty设备。busybox-1.33.1/init/init.c:470这里可以清楚看到对命令的处理逻辑。

/etc/init.d/rcS编写

rcS启动脚本我们就简单的执行mount -a和mdev -s两个命令。

#! /bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin

/bin/mount -a
/sbin/mdev -s
/bin/mount -a

echo "---------------------------------------------"
echo " Welcome debugging on Qemu Quard Star board! "
echo "---------------------------------------------"

busybox中mount命令解析

mount实现源码位于busybox-1.33.1/util-linux/mount.c:2259:mount_main,mount是用于文件系统挂载的,这里我们仅分析-a参数,其意味着读取/etc/fstab文件进行自动挂载。因此执行mount -a就必须编写对应的/etc/fstab配置文件。

busybox中mdev命令解析

mdev实现源码位于busybox-1.33.1/util-linux/mdev.c:1239:mdev_main,mdev -s的目的就是自动创建/dev目录以及相关设备,-s调用了initial_scan函数,阅读源码可以清楚看到,其实现是通过查询/sys/dev创建设备,这里要注意下,如果sysfs没有挂载,mdev是无法正确执行的,因此要先完成mount -a,但是mount里可能会挂载一些文件系统到/dev,因此又需要在mdev -s后再执行另一些文件系统的挂载。当然mdev也有配置文件/etc/mdev.conf,不过这里我们不去编写这个文件一切使用默认值。

/etc/fstab编写

fstab主要用来描述早期需求挂载的各种虚拟文件系统,proc、sysfs、debugfs都是最常见的,对应用户程序访问内核信息非常关键。其余的我们创建一些ramfs,让这些路径下的内容(不需要掉电保存的信息)为临时内容不去访问硬盘提高效率。

<file system>   <mount point>           <type>     <options>    <dump>  <pass>
proc			/proc					proc		defaults    0	     0
none			/tmp					ramfs		defaults    0	     0
sysfs			/sys					sysfs		defaults    0	     0
mdev		    /dev					ramfs		defaults	0	     0
debugfs	        /sys/kernel/debug 	    debugfs	    defaults    0        0

busybox中/bin/sh命令解析

busybox的sh源码位于busybox-1.33.1/shell/ash.c:14385:ash_main。(这个对应关系可以查找busybox-1.33.1/include/applet_tables.h:16和803行的表确定,并非绝对的XX对应XX_main)

这里我们SYSINIT阶段完成指行ASKFIRST就是去执行/bin/sh来打开shell。这里可以看到早期会执行/etc/profile脚本和$HOME/.profile脚本,这就是用户级的登录配置脚本,/etc/profile被应用于所有用户,而$HOME/.profile只对当前登录用户使用。因此我们编写/etc/profile内容如下:

# /etc/profile: system-wide .profile file for the Bourne shells

echo -n "Processing /etc/profile... "
# no-op
echo "Done"

这个文件我们预留在这里,不做任何内容,因为本节我们只想完成一个简单的配置启动linux内核,因此关于多用户管理的相关配置我们这里都没有添加,因此ash启动后将无需账户密码直接使用管理员权限登录到系统。其实关于ash是源自dash的一个分支,dash是ubuntu内置的shell,但其实这些都不是最为广泛使用的bash,后面我们会考虑替换bash为默认的shell,但不影响本节内容。

启动qemu

将我们今天编写的相关配置脚本拷贝到文件系统映像中,然后启动qemu。

cp -r $SHELL_FOLDER/target_root_script/* $SHELL_FOLDER/output/rootfs/rootfs/
mkdir $SHELL_FOLDER/output/rootfs/rootfs/proc
mkdir $SHELL_FOLDER/output/rootfs/rootfs/sys
mkdir $SHELL_FOLDER/output/rootfs/rootfs/dev
mkdir $SHELL_FOLDER/output/rootfs/rootfs/tmp

熟悉的界面终于出现。

ch13-0

ok,本节对busybox的用户级别初始化内容进行了深入探究,最终完成了进入shell工作。下一节开始我们需要在回过头来将busybox编译为动态链接库的版本,并借此机会探究linux内核是如何支持动态链接程序的加载执行,为后续我们扩展更多用户态应用程序做好准备。

本教程的
github仓库:https://github.com/QQxiaoming/quard_star_tutorial
gitee仓库:https://gitee.com/QQxiaoming/quard_star_tutorial
本节所在tag:ch13