用官方源码编译内核,在qemu中使其尽快跑至shell。以为很简单的事,?果折腾了半个下午。
表面上看来,这件事就像极端简化的LFS。Ubuntu的仓库里居然还有静态编译的busybox!辅以initramfs,用户态的初始环境很容易就构造了。
比较麻烦的是bootloader,内核要怎么到内存中来?好在qemu有个-kernel参数,后加bzImage文件就可以完成bootloader所做的事。看起来只需要两步就OK了:
1.make defconfig && make bzImage
2.qemu -kernel arch/x86/boot/bzImage
?果是Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(8,5)
暂不想操心硬盘的事,只想在内存里跑。新建文件夹,把静态编译好的busybox复制至其中并命名为init。在menuconfig里将initramfs路径指向文件夹,再来。另外不想让qemu用图形界面。
qemu -kernel arch/x86/boot/bzImage -nographic
?果,显示qemu的几行启动信息之后,神马反应也没有,杀也杀不掉。想了一下,默认的console应该是VGA显卡,在cmdline里指定为串口试一下。
qemu -kernel arch/x86/boot/bzImage -append “cOnsole=ttyS0” -nographic
好了,有输出了,但是最后停在[ 1.577268] Switching to clocksource tsc,怎么按键盘都没反应。往上看两行注意到了[ 1.253342] Warning: unable to open an initial console。看了一下本机上的console文件,字符设备,5,1.在initramfs文件夹里新建dev目录,再在其中sudo mknod console c 5 1,重编译,再试。终于出现init找不到配置文件之类的提示。
暂不想管inittab什么的,直接让shell做1号进程。在initramfs文件夹里新建bin目录,将init移动为bin下的sh。编译,cmdline中指定init,再试。
qemu -kernel arch/x86/boot/bzImage -append "cOnsole=ttyS0 init=/bin/sh" -nographic
?果居然是[ 1.402899] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(8,5)!
为什么有initramfs还会去尝试挂载根文件系统?研究了一下代码,发现是自己以前看代码不仔细。再试。
qemu -kernel arch/x86/boot/bzImage -append "cOnsole=ttyS0 rdinit=/bin/sh" -nographic
终于启动成功了,shell也可以跑了。里面什么命令也没有,预先在initramfs文件夹里建一些指向busybox的软链接就好了。
=========================================================
关于启动的细节。以前的认识有些偏差。且不说initrd,内核镜像里总有一个内嵌initramfs的cpio包,好像还压缩了。在初始化的时候,先初始rootfs,在内存中构建了最初的文件系统。然后不管三七二十一,将镜像中的cpio包释放出来。
怎么会报Unable to mount root fs的错呢?进行到kernel_init时,有这样的代码:
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
也就是说,如果在初始的文件系统中找不到ramdisk_execute_command(默认是init,可以在cmdline中覆盖),则进入prepare_namespce。这个里面就会有尝试mount root fs的动作。如果只想在内存中跑,一定要使sys_access成功。而覆盖ramdisk_execute_command的命令行参数不是init=而是rdinit=!在其后的init_pos中。如果ramdisk_execute_command非空,则执行该程序,内核的工作到此为此,带起系统的事是initramfs里的init负责。如果initramfs里没有能带起系统的init,内核则尝试挂载真正的物理根文件系统(prepare_namespace),然后再执行真正根文件系统的init程序。这个程序的路径由init=所指定,如果不指定,则做如下尝试:
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
总结:
1.如果initramfs里有init(rdinit指定或者是/init),1号进程会执行它,系统启动则完全交给它了。
2.initramfs里的init执行失败(不存在或者其它原因),内核会默不作声地进入老式启动流程。
3.从上一步滑下来了,则尝试挂载根文件系统所在设备(老式的启动流程),设备号默认为编译内核的主机上挂载在/下设备的设备号,在cmdline中可以用root=覆盖之。VFS: Unable to mount root fs就是这时产生的。挂载完根文件系统中之后,执行execute_command,即cmdline中init=所指定的。如果没有指定,则尝试默认四种可能。如果killing init导致panic并且提示你加init=的话,那应该就是挂载了真正的根文件系统里没有init=所指定的程序且四个默认的init程序皆执行失败。
4.如果没有指定initramfs source,内核会生成一个最小的initramfs cpio包,这个包不是空的!它有一个dev目录,下面有一个console结点,它还有一个root目录。如果指定initramfs source,则自己的initramfs目录里最好有dev目录及其下在的console结点。另外,如果内核去挂载根文件系统所在设备,这个设备会被挂载在/root下!假设自己提供initramfs,内核里没能执行其中的init,这个initramfs里又没有/root目录,在内核尝试挂载根文件系统所在设备时会失败,因为挂载点不存在(报错却报却是VFS: Cannot open root device;当然也有可能真没那个设备)。
5.由于代码流程,如果initramfs里有init,cmdline里的root=和init=都不起作用.