前面实验都是依赖于 QEMU 的 -kernel 来将内核拷贝到内存并启动, 为了模拟更真实的硬件, 我们必须舍弃该方法, 通过 u-boot 来启动
u-boot 和 kernel 的编译和启动参考QEMU 实验(一), 这里不会再过多描述
在 u-boot 命令行中通过 sd 卡启动
首先创建一个 sd 卡的镜像文件用来模拟真实 sdcard, 将内核和设备树放到镜像文件中, 然后使用 u-boot 命令行把内核和设备树加载到内存并运行内核
dd if=/dev/zero of=sdcard.img bs=1024 count=102400
mkfs.fat sdcard.img
mount -o loop sdcard.img /mnt
cp zImage vexpress-v2p-ca9.dtb /mnt
umount /mnt
使用 QEMU 启动 u-boot, 参考
$ qemu-system-arm -M vexpress-a9 -m 256M -kernel u-boot -sd sdcard.img -nographic
=> mmcinfo
=> load mmc 0:0 0x60008000 zImage
=> load mmc 0:0 0x61000000 vexpress-v2p-ca9.dtb
=> setenv bootargs "root=/dev/mmcblk0 rw console=ttyAMA0"
=> bootz 0x60008000 - 0x61000000
- mmcinfo 可查看有哪些 mmc 设备
- load <硬件接口> <设备号:分区> <加载地址> <文件名>
- setenv bootargs 用来传递环境变量给内核, 这里只告知了将sd卡挂载为可读写根文件系统, 并将控制台定向到串口0
- bootm <kernel地址> <ramdisk地址> <dtb地址> 可使用
-
略过参数,比如上面不需要 ramdisk,所以中间有-
内核成功启动后将会去根文件系统运行第一个用户程序 /sbin/init
, 但是我们没有拷贝任何程序到 sdcard, 所以内核返回 No working init found
. 不过参考QEMU 实验(一)可以很轻松地将 Busybox 拷贝到 SD 卡, 这里不再赘述
从 tftp 网络中启动
- 安装 tftp 服务器
apt-get install tftpd-hpa # tftp server apt-get install tftp-hpa # tftp client, for test mkdir ~/tftpboot chmod 777 ~/tftpboot vim /etc/default/tftpd-hpa
更改 TFTP_DIRECTORY 为 ~/tftpboot
- 本地测试
$ touch ~/tftpboot/a.txt $ tftp 127.0.0.1 tftp> get a.txt
- u-boot 启动测试
参考 uboot通过tftp的方式加载Linux内核文件$ cp zImage vexpress-v2p-ca9.dtb ~/tftpboot $ ifconfig # 拷贝ip地址, 例如 192.168.1.20 => setenv ipaddr 192.168.1.21 # 设置本机 ip 地址 => setenv serverip 192.168.1.20 # 设置服务器 ip 地址 => tftp 0x60008000 zImage => tftp 0x61000000 vexpress-v2p-ca9.dtb => setenv bootargs "root=/dev/mmcblk0 rw console=ttyAMA0" => bootz 0x60008000 - 0x61000000
自动脚本
u-boot 支持运行脚本, 我们可以使用脚本来让u-boot自动运行上面的命令行, 不过 u-boot 并不会直接运行纯文本文件, 他需要对脚本文件使用 mkimage 进行打包, 加入头信息, 这是为了安全考虑的.
新建一个文件 boot.cmd , 然后直接拷贝上面的命令行内容到文件
boot.cmd
load mmc 0:0 0x60008000 zImage
load mmc 0:0 0x61000000 vexpress-v2p-ca9.dtb
setenv bootargs "root=/dev/mmcblk0 rw console=ttyAMA0"
bootz 0x60008000 - 0x61000000
然后执行
mkimage -C none -A arm -T script -d boot.cmd boot.scr
将 boot.scr 拷贝到 sdcard 的根目录, 就像我们拷贝内核和设备树那样, 只要在 u-boot 中能够将它加载进内存即可.
$ qemu-system-arm -M vexpress-a9 -m 256M -kernel u-boot -sd sdcard.img -nographic
=> load mmc 0:0 0x62000000 boot.scr
=> source 0x62000000
source <addr> 将执行在放在内存 addr 处的 .scr 脚本
让 u-boot 启动后自动加载 sd 卡中的 kernel
在 u-boot 中, 通过 printenv 可以看到环境变量信息, 其中有如下信息
boot_scripts=boot.scr.uimg boot.scr
boot_targets=mmc1 mmc0 pxe dhcp
变量 boot_scripts 指定了u-boot启动后默认加载的命令脚本, boot_targets 指定了从那个地方加载, 据此我们可以生成一个 boot.scr 放在 SD 卡, u-boot 启动后会自动加载并执行. 但是在前一节自动脚本
中, 我们也放在了sdcard 且名称也为 boot.scr 但是为什么没有自己启动? 这是因为 u-boot 默认是识别设备的第一个分区, 我们没有给sdcard进行分区, 因此无法启动. (只有一个分区和不分区是不同的, 不分区压根没有分区表, 也就没分区的概念, 分区后即使只有一个分区, 也是有分区表的)
此外不要忘记更改 load mmc 的磁盘位置, 要更改为分区1
boot.cmd
load mmc 0:1 0x60008000 zImage
load mmc 0:1 0x61000000 vexpress-v2p-ca9.dtb
setenv bootargs "root=/dev/mmcblk0p1 rw console=ttyAMA0"
bootz 0x60008000 - 0x61000000
重新生成一下:
mkimage -C none -A arm -T script -d boot.cmd boot.scr
然后是重新生成 sdcard.img:
dd if=/dev/zero of=sdcard.img bs=1024 count=102400
fdisk sdcard.img # 简单起见这里只分一个区
losetup -f # 查询可用的块设备地址, 记下来如 /dev/loop16
losetup -P /dev/loop16 sdcard.img # 关联块设备
lsblk # 可以看到块设备已经关联, 并且看到分区信息
mkfs.fat /dev/loop16p1 # 格式化第一分区
mount /dev/loop16p1 /mnt # 挂载块设备的第一分区
cp boot.scr zImage vexpress-v2p-ca9.dtb /mnt # 复制到 第一分区
umount /mnt # 卸载设备
启动
qemu-system-arm -M vexpress-a9 -m 1024M -kernel u-boot -sd sdcard.img -nographic
[注意] 这里设置了 1024M 内存, 因为vexpress-a9板子执行 printenv 时看到环境变量默认 scriptaddr=0x88000000, 启动脚本检测存在后会被拷贝到这个地方并使用 source 执行. 而 bdinfo 显示DRAM bank 启始地址是 0x60000000, 如果内存只有 256M, 则RAM到不了0x88000000, 是无法加载启动脚本的. 虽然这一项可以更改该参数并重新编译 u-boot, 不过, 目前最好还是按官方原本设置为好.
启动后, u-boot 会被 Qemu 加载到内存, 然后 u-boot 初始化设备, 并探测到 sdcard 的第一分区有 boot.scr, 它会将其加载到 scriptaddr 地址处并执行, 而执行的 boot.scr 被我们定义为 加载同目录下的内核以及设备树并执行, 因此 Linux 内核被成功启动.
u-boot 打包
可将 u-boot kernel dtb rootfs 都打包在一个镜像文件中然后下载到 NorFlash 中直接运行, 使用 dd 进行操作
mkimage -A arm -C none -O linux -T kernel -d zImage -a 0x00010000 -e 0x00010000 zImage.uimg
mkimage -A arm -C none -O linux -T ramdisk -d rootfs.img.gz -a 0x00800000 -e 0x00800000 rootfs.uimg
dd if=/dev/zero of=flash.bin bs=1 count=6M
dd if=u-boot.bin of=flash.bin conv=notrunc bs=1
dd if=zImage.uimg of=flash.bin conv=notrunc bs=1 seek=2M
dd if=rootfs.uimg of=flash.bin conv=notrunc bs=1 seek=4M
然后可以直接将文件加载到内存运行:
qemu-system-arm -M vexpress-a9 -device loader,file=flash.bin,addr=0x60800000,cpu-num=0,force-raw=on -nographic