author: hjjdebugdate: 2026年 04月 29日 星期三 17:39:34 CSTdescrip: 制作最简根文件系统根文件系统是linux内核启动完成后,要挂载的第一个文件系统,内核必需要从该文件系统中找到一个启动文件, 例如tty-shell,然后把执行权就交给该shell文章目录1. 什么是根文件系统?2. 创建一个最小可用 Linux 根文件系统rootfs2.1 创建符合linux规范的目录及文件集合.2.2 把linux 根目录打包成.cpio.gz文件, 这就是initramfs 文件3. 步骤2的所有操作可以用一个脚本来完成.4. 用途4.1 qemu 启动脚本4.2 qemu 调试1. 什么是根文件系统?根,就是最顶层的意思.最基础的意思.文件系统,就是多个文件构成系统.所谓mount文件系统,安装文件系统,就是找到根目录位置,有时候我们把根叫/目录的分割也是用/来分割.根文件系统构成 目录结构 基础工具 启动脚本 必要库文件根文件系统制作过程.建标准目录树放基础命令busybox 最常用放动态库 (busybox 编译成静态就可以不用动态库了)init 启动脚本打包成镜像cpio 或 ext4制作cpio 文件是为了制作内存镜像文件.所谓内存镜像文件,就是文件系统存在于内存中, 而不是存放在磁盘上.典型目录结构都在根下如下示例/bin、/sbin基础命令/etc系统配置/lib动态库/dev设备文件/usr、/var 等这些都是根目录下的子目录.也是linux-base目录结构各个目录下有相关的文件.2. 创建一个最小可用 Linux 根文件系统rootfs我们用busybox 创建一个根文件系统.busybox 是一个命令的集合.假定你已经有了一个静态的busybox, 位置/bin/busybox动态busybox可能由于动态库的问题(找不到,或者版本)而跑不起来.所以别用.2.1 创建符合linux规范的目录及文件集合.创建 bin dev etc proc sys host目录, host 目录是一个文件系统挂载点创建 init 脚本, 内核启动完就会首先执行这个init 脚本, 其mount -a 会读取fstab 文件创建 etc/fstab 文件, 这是init脚本执行时的配置文件,会自动挂载文件系统,省得你手工挂载创建 profile 文件, 这是login shell 的登录执行初始化文件,你可以把一些初始化操作放到这里把busybox copy 到bin 目录, 并在bin 目录下建立若干软链接2.2 把linux 根目录打包成.cpio.gz文件, 这就是initramfs 文件用内存盘根文件系统很方便3. 步骤2的所有操作可以用一个脚本来完成.脚本中还有一些细节注释.$ cat build_initramfs.sh #!/bin/bash # 配置项 BUSYBOX/bin/busybox# 改成你自己的静态 busybox 路径,要静态的,否则不能运行 OUTPUTinitramfs.cpio.gzWORKDIR./initramfs.tmp# 清理 rm-rf$WORKDIR$OUTPUTmkdir-p$WORKDIRcd$WORKDIR||exit1#1.创建目录结构 mkdir-p bin dev etc proc sys host #2.创建 etc/fstabmount-a 调用,9P 自动挂载 catetc/fstabEOFdevtmpfs/dev devtmpfs defaults00proc/proc proc defaults00sysfs/sys sysfs defaults00host_share/host9p transvirtio,version9p2000.L00EOF#3.创建 profile,login shell 调用 catetc/profileEOFalias lsls --colorEOF#4.生成 init 脚本,内核默认调用该用户态文件 catinitEOF#!/bin/sh # 按 fstab 自动挂载 mount-a#setsid,cttyhack 是为了消除一个Cant access tty;job control turned off的警告#-l 会启用login shell,从而执行profile文件 exec/bin/setsid/bin/cttyhack/bin/sh-lEOFchmodx init #5.放入 busybox cp$BUSYBOXbin/chmodx bin/busybox # 建立软链接 cd bin||exit1forcmd in sh setsid cttyhack mount modprobe ls cat mkdir dmesg insmod rmmod;doln-s busybox$cmddone cd..#6.打包 cpiogzip find.|cpio-o-H newc|gzip-9../$OUTPUT# 完成 cd..rm-rf$WORKDIRecho✅ 生成成功$OUTPUT4. 用途用qemu 启动内核并运行到根文件系统的shell.4.1 qemu 启动脚本$ cat qemu-run.shqemu-system-x86_64-m 2G -smp 2-enable-kvm -cpu host-kernel arch/x86/boot/bzImage-initrd ~/test/rootfs/initramfs.cpio.gz-append “root/dev/ram0 consolettyS0 nokaslr”-virtfs local,path/mnt/share,mount_taghost_share,security_modelpassthrough,idhostshare-nographic参数解释:-m 内存大小-smp 核心数 (symmetric_multi_processing)-enable-kvm 就会直接用你电脑 CPU 的硬件虚拟化能力速度直接提升 10100 倍-cpu host 把宿主机的 CPU 型号、特性 1:1 暴露给虚拟机,i7、i9、AMD 等, 所有指令集全部开放,否则就模拟一个假的、老旧的 CPU兼容性好但性能差-kernel 内核 QEMU 可以直接加载Linux 内核镜像, 可以不用从硬盘启动,用bootloader加载等-initrd 根文件 初始ram文件系统,临时根文件系统,就是我们上面制作的文件,内核挂靠的系统-append 启动内核时传递给内核的参数,root/dev/ram0, 告诉内核根文件系统在内存consolettyS0, 控制台输出重定向到ttyS0, 向串行口打印. 配合-nographic 参数,将打印到宿主机的终端nokaslr, no kernel address space layout randomization, 内核地址不要随机化,要固定, 调试内核程序保持地址固定需要它.-virtfs 虚拟文件系统,宿主机和虚拟机通过虚拟文件系统实现文件共享,具体实现:local, 共享本地目录path/mnt/share, 共享的本地路径名称mount_taghostshare, 宿主机和虚拟机的共享标签,沟通的桥梁.security_modelpassthrough, 宿主机的文件属性全部pass给虚拟机idhostshare, 这是给qemu看的一个设备名称, 可以把这种共享功能标记为一个虚拟共享卡,卡的名字叫hostshare其实,宿主机不使用这个id,但qemu会用它, 你还可以创建第二个共享卡(如果愿意),起不同名称就行所以这个id必需要写,因为qemu要管理这个设备,要在log中记录这个设备,要区分设备,所以要为每个设备配置ID,且没个设备ID都不同-nographic 无窗口, 不出现窗口,需配合内核参数consolettyS0, 才能由宿主机接受打印信息把简易的linux 系统先启动起来, 这很不容易,为后续的继续前进铺平了道路. 先到这里吧.4.2 qemu 调试qemu 能跑了, 则可用gdb 来调试它.搞得这么复杂,还不是为了最后能够用gdb远程调试内核或驱动如果仅仅是跑一个linux, 那就不用费这么大劲了,我们的宿主机就是ubuntu linux.而且工具及多,功能很强大.完整qemu调试启动命令:$ cat qemu-run.shqemu-system-x86_64-m 2G -smp 1-no-hpet-enable-kvm -cpu host-kernel arch/x86/boot/bzImage-initrd ~/test/rootfs/initramfs.cpio.gz-append “root/dev/ram0 consolettyS0 nokaslr”-virtfs local,path/mnt/share,mount_taghost_share,security_modelpassthrough,idhostshare-nographic-s-S在qemu启动脚本中再加上 -s -S 选项, 就可以用gdb调试驱动了.-s: -gdb tcp:1234, 在tcp:1234 端口接受gdb 命令-S: Stop, 停止运行, 等待gdb连接具体的调试实例, 后面再补充.命令行上的两个小坑.调试的时候, -smp 1 必需要cpu 核心数为1, 否则,中断后,单步执行时, 走不了几步会被切换到其它cpu. 不能实现单步调试.-no-hpet, 关闭高精度事件定时器(no high performance event timer), 否则中断后,单步时,直接跳转到定时器,没法单步调试解决了这两个问题,就解决了单步调试问题.设置中断时,要用hbreak 命令, 即要用硬中断命令, 用b命令是断不下来的.第一次insmod 时, add-symbol-file 之后, 用b 命令也能断下来,例如b demo_read.但时,rmmod 后, 再insmod, 就断不下来了,虽然info b 看起来都正常, 删除断点重新设置也不行.这是一个大坑. 我不可能为了一次中断而启动一次机器.原因的b 命令就不能在内核代码上设置软中断断点,它改不动内核程序区. 第一次insmod后能改动是因为当时内核还没有关闭修改属性.为什么gdb能对用户态的程序修改代码区指令为int cc 指令,这是因为内核对gdb ptrace 命令开后门, 但修改内核态代码区却没有后门.坑人的地方就在与gdb设置断点没有成功可是它却没有任何提示, 用户以为它成功了. 但时却断不下来.所以,内核中设置断点只能用hbreak 硬件断点. 记住就行了.这样就解决了设置断点问题, 断点断不下来的问题.gdb 调试启动命令$ gdb -x 1.gdb1.gdb 的内容如下,做一个参考其中add-symbol-file 中各个段的地址由 /sys/module/demo_drv/section/隐藏文件.text, .data, .bss, .init.text 等文件内容提供这样就把中断设置在正确位置,打印变量也能得到正确的地址和值$ cat1.gdb file vmlinux target remote localhost:1234#hbreakstart_kerneladd-symbol-file/home/hjj/test/module3/demo_drv.ko0xffffffffa0000000\-s.data0xffffffffa0002000\-s.bss0xffffffffa0002480\-s.init.text0xffffffffa0005000hbreak demo_read hbreak demo_init c贴一张调试图吧! demo_drv.ko 是我调试的一个虚拟字符设备驱动. 中间是若干个单步n 命令,执行成功了.