基于qemu-riscv从0开始构建嵌入式linux系统ch11-1. 向着linux Kernel出发!——加载引导

kernel编译

首先,我们添加linux内核源码到我们的项目中,内核官网:https://www.kernel.org/。这里我们选择5.10.42版本,一般来讲如果没有特殊需求选择一个比较新的longterm版本是比较稳妥可靠的。说起来linux kernel的编译是非常容易的,得益于linux内核良好的工程架构,只需要向编译脚本添加如下命令,即可生成Image文件。这里我们使用了riscv架构下的默认defconfig,kernel默认运行在riscv的S模式下。

if [ ! -d "$SHELL_FOLDER/output/linux_kernel" ]; then  
mkdir $SHELL_FOLDER/output/linux_kernel
fi
cd $SHELL_FOLDER/linux-5.10.42
make ARCH=riscv CROSS_COMPILE=$CROSS_PREFIX- defconfig
make ARCH=riscv CROSS_COMPILE=$CROSS_PREFIX- -j16
cp $SHELL_FOLDER/linux-5.10.42/arch/riscv/boot/Image $SHELL_FOLDER/output/linux_kernel/Image

制作文件系统映像

有了内核Image接下来只要将设备树文件和Image打包生成文件系统映像,就可以使用uboot加载到内存里引导执行了,接下来我们来一步步制作文件系统映像。

  • 首先创建工作目录./rootfs/rootfs和./rootfs/bootfs,其中rootfs是将来存放根文件系统的,本节我们不添加任何文件到该目录,bootfs目录下存放用于uboot访问加载的映像,包括三个文件。内核Image、用于内核的设备树文件quard_star.dtb、用于uboot的Distro Boot Script文件。然后使用dd命令创建1G大小的文件rootfs.img,即为文件系统原始映像。

if [ ! -d "$SHELL_FOLDER/output/rootfs" ]; then  
mkdir $SHELL_FOLDER/output/rootfs
fi
if [ ! -d "$SHELL_FOLDER/output/rootfs/rootfs" ]; then  
mkdir $SHELL_FOLDER/output/rootfs/rootfs
fi
if [ ! -d "$SHELL_FOLDER/output/rootfs/bootfs" ]; then  
mkdir $SHELL_FOLDER/output/rootfs/bootfs
fi
cd $SHELL_FOLDER/output/rootfs
if [ ! -f "$SHELL_FOLDER/output/rootfs/rootfs.img" ]; then  
dd if=/dev/zero of=rootfs.img bs=1M count=1024
fi
  • 拷贝Image和dtb到bootfs

cp $SHELL_FOLDER/output/linux_kernel/Image $SHELL_FOLDER/output/rootfs/bootfs/Image
cp $SHELL_FOLDER/output/uboot/quard_star_uboot.dtb $SHELL_FOLDER/output/rootfs/bootfs/quard_star.dtb
  • 创建文件quard_star_uboot.cmd,编写uboot的Distro Boot Script。这个脚本是uboot的脚本,你可以任务是uboot下的shell脚本,内容就三条命令,通过load加载文件系统第一个分区下的根目录内Image文件到内存0x80000000,加载文件系统第一个分区下的根目录内quard_star.dtb文件到内存0x82000000,然后使用booti命令引导boot程序,booti用于Image文件的boot,第一个参数是Image所在内存地址,第二个参数为内存文件系统地址(如果不需要可以传入’-‘),第三个参数为设备树所在内存地址。

load virtio 0:1 0x80000000 /Image
load virtio 0:1 0x82000000 /quard_star.dtb
booti 0x80000000 - 0x82000000
  • 使用uboot的mkimage工具将Distro Boot Script转换为可以执行的boot.scr文件,这个文件会在uboot的启动时自动在文件系统内搜索该文件名然后执行其中的命令。

$SHELL_FOLDER/u-boot-2021.07/tools/mkimage -A riscv -O linux -T script -C none -a 0 -e 0 -n "Distro Boot Script" -d $SHELL_FOLDER/dts/quard_star_uboot.cmd $SHELL_FOLDER/output/rootfs/bootfs/boot.scr
  • OK,用到的文件都准备充分了,下一步我们编写脚本generate_rootfs.sh用于格式化rootfs.img内容。内容如下,这里稍微解释一下,losetup用于挂载$1到本地回环设备/dev/loop70,然后使用fdisk工具对/dev/loop70进行分区表构造生成两个分区,之后重新挂载到本地回环设备上会解析出两个分区块设备/dev/loop70p1和/dev/loop70p2,使用mkfs.vfat和mkfs.ext4分别格式化分区后,解除挂载,即完成了原始映像分区创建。这里要解释一下fdisk是交互式的命令工具,我们使用了echo -e分别将要输入的命令写入到脚本,”I\n$2\nw\n”即为依次输入‘I’,‘回车’,‘$2’,‘回车’,‘w’,‘回车’,对应的操作就是加载$2所执向的sfdisk脚本文件,安装该文件格式化分区,然后w写入分区并退出fdisk。

#!/bin/bash
losetup -o 0 --sizelimit 1073741824 /dev/loop70 $1 -P
echo -e "I\n$2\nw\n" | fdisk /dev/loop70
losetup -d /dev/loop70  
sync
echo "please wait 5s"
sleep 5
losetup -o 0 --sizelimit 1073741824 /dev/loop70 $1 -P
mkfs.vfat /dev/loop70p1
mkfs.ext4 /dev/loop70p2
losetup -d /dev/loop70  
sync
  • 创建文件sfdisk,内容如下,即定义loop70p1和loop70p2的分区起始页和大小

label: dos
label-id: 0x32a47f23
device: /dev/loop70
unit: sectors

/dev/loop70p1 : start=        2048, size=      196608, type=c
/dev/loop70p2 : start=      198656, size=     1898496, type=83
  • build.sh添加generate_rootfs.sh rootfs.img sfdisk即可完成分区格式化。pkexec用来提权,因为上述losetup、fdisk命令必须使用管理员权限才能运行。

pkexec $SHELL_FOLDER/build_rootfs/generate_rootfs.sh $SHELL_FOLDER/output/rootfs/rootfs.img $SHELL_FOLDER/build_rootfs/sfdisk
  • 编写build_rootfs/build.sh文件用于拷贝文件到文件系统映像

#!/bin/bash
losetup -o 0 --sizelimit 1073741824 /dev/loop70 $1/rootfs.img -P
if [ -d "$1/target" ]; then  
rm -rf $1/target
fi
mkdir $1/target
mkdir $1/target/bootfs
mkdir $1/target/rootfs
mount /dev/loop70p1 $1/target/bootfs 
mount /dev/loop70p2 $1/target/rootfs

cp -r $1/bootfs/* $1/target/bootfs/
cp -r $1/rootfs/* $1/target/rootfs/
sync
echo "please wait 5s"
sleep 5

umount $1/target/bootfs 
umount $1/target/rootfs
losetup -d /dev/loop70  
sync
  • 同样添加./build_rootfs/build.sh rootfs完成文件拷贝。

pkexec $SHELL_FOLDER/build_rootfs/build.sh $SHELL_FOLDER/output/rootfs

运行qemu

直接执行./run.sh nographic就看到这次uboot的引导命令执行成功,内核开始boot,输出如下:

U-Boot 2021.07 (Aug 08 2021 - 14:35:46 +0800)

CPU:   rv64imafdcsu
Model: riscv-quard-star,qemu
DRAM:  1 GiB
Loading Environment from nowhere... OK
In:    uart0@10000000
Out:   uart0@10000000
Err:   uart0@10000000
Net:   No ethernet found.
Hit any key to stop autoboot:  0 

Device 0: QEMU VirtIO Block Device
            Type: Hard Disk
            Capacity: 1024.0 MB = 1.0 GB (2097152 x 512)
... is now current device
Scanning virtio 0:1...
Found U-Boot script /boot.scr
178 bytes read in 3 ms (57.6 KiB/s)
## Executing script at 88000000
17709568 bytes read in 16 ms (1 GiB/s)
5247 bytes read in 2 ms (2.5 MiB/s)
Moving Image from 0x80000000 to 0x80200000, end=8133a000
## Flattened Device Tree blob at 82000000
   Booting using the fdt blob at 0x82000000
   Using Device Tree in place at 0000000082000000, end 000000008200447e

Starting kernel ...

[    0.000000] Linux version 5.10.42 (xiaoming@xiaoming) (riscv64-unknown-linux-gnu-gcc (GCC) 11.1.0, GNU ld (GNU Binutils) 2.36.1) #208 SMP PREEMPT Sun Aug 8 14:35:52 CST 2021
[    0.000000] OF: fdt: Ignoring memory range 0x80000000 - 0x80200000
[    0.000000] efi: UEFI not found.
[    0.000000] Zone ranges:
[    0.000000]   DMA32    [mem 0x0000000080200000-0x00000000bfffffff]
[    0.000000]   Normal   empty
[    0.000000] Movable zone start for each node
[    0.000000] Early memory node ranges
[    0.000000]   node   0: [mem 0x0000000080200000-0x00000000bfffffff]
[    0.000000] Initmem setup node 0 [mem 0x0000000080200000-0x00000000bfffffff]
[    0.000000] software IO TLB: mapped [mem 0x00000000ba9f7000-0x00000000be9f7000] (64MB)
[    0.000000] SBI specification v0.2 detected
[    0.000000] SBI implementation ID=0x1 Version=0x9
[    0.000000] SBI v0.2 TIME extension detected
[    0.000000] SBI v0.2 IPI extension detected
[    0.000000] SBI v0.2 RFENCE extension detected
[    0.000000] SBI v0.2 HSM extension detected
[    0.000000] riscv: ISA extensions acdfimsu
[    0.000000] riscv: ELF capabilities acdfim
[    0.000000] percpu: Embedded 17 pages/cpu s32744 r8192 d28696 u69632
[    0.000000] Built 1 zonelists, mobility grouping on.  Total pages: 258055
[    0.000000] Kernel command line: root=/dev/vda2 rw console=ttyS0
[    0.000000] Dentry cache hash table entries: 131072 (order: 8, 1048576 bytes, linear)
[    0.000000] Inode-cache hash table entries: 65536 (order: 7, 524288 bytes, linear)
[    0.000000] Sorting __ex_table...
[    0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off
[    0.000000] Memory: 938312K/1046528K available (7144K kernel code, 3925K rwdata, 4096K rodata, 223K init, 342K bss, 108216K reserved, 0K cma-reserved)
[    0.000000] Virtual kernel memory layout:
[    0.000000]       fixmap : 0xffffffcefee00000 - 0xffffffceff000000   (2048 kB)
[    0.000000]       pci io : 0xffffffceff000000 - 0xffffffcf00000000   (  16 MB)
[    0.000000]      vmemmap : 0xffffffcf00000000 - 0xffffffcfffffffff   (4095 MB)
[    0.000000]      vmalloc : 0xffffffd000000000 - 0xffffffdfffffffff   (65535 MB)
[    0.000000]       lowmem : 0xffffffe000000000 - 0xffffffe03fe00000   (1022 MB)
[    0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=8, Nodes=1
[    0.000000] rcu: Preemptible hierarchical RCU implementation.
[    0.000000] rcu: 	RCU debug extended QS entry/exit.
[    0.000000] 	Trampoline variant of Tasks RCU enabled.
[    0.000000] 	Tracing variant of Tasks RCU enabled.
[    0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies.
[    0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
[    0.000000] riscv-intc: 64 local interrupts mapped
[    0.000000] plic: plic@c000000: mapped 53 interrupts with 8 handlers for 16 contexts.
[    0.000000] random: get_random_bytes called from start_kernel+0x31c/0x490 with crng_init=0
[    0.000000] riscv_timer_init_dt: Registering clocksource cpuid [0] hartid [0]
[    0.000000] clocksource: riscv_clocksource: mask: 0xffffffffffffffff max_cycles: 0x24e6a1710, max_idle_ns: 440795202120 ns
[    0.000293] sched_clock: 64 bits at 10MHz, resolution 100ns, wraps every 4398046511100ns
[    0.005668] Console: colour dummy device 80x25
[    0.012791] Calibrating delay loop (skipped), value calculated using timer frequency.. 20.00 BogoMIPS (lpj=40000)
[    0.013077] pid_max: default: 32768 minimum: 301
[    0.015827] Mount-cache hash table entries: 2048 (order: 2, 16384 bytes, linear)
[    0.015953] Mountpoint-cache hash table entries: 2048 (order: 2, 16384 bytes, linear)
[    0.064327] rcu: Hierarchical SRCU implementation.
[    0.067752] EFI services will not be available.
[    0.073230] smp: Bringing up secondary CPUs ...
[    0.107641] CPU7: failed to start
[    0.110178] smp: Brought up 1 node, 7 CPUs
[    0.128800] devtmpfs: initialized
[    0.143957] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
[    0.144272] futex hash table entries: 2048 (order: 5, 131072 bytes, linear)
[    0.151876] NET: Registered protocol family 16
[    0.249864] vgaarb: loaded
[    0.252192] SCSI subsystem initialized
[    0.255517] usbcore: registered new interface driver usbfs
[    0.256358] usbcore: registered new interface driver hub
[    0.256600] usbcore: registered new device driver usb
[    0.272238] clocksource: Switched to clocksource riscv_clocksource
[    0.355524] NET: Registered protocol family 2
[    0.357939] IP idents hash table entries: 16384 (order: 5, 131072 bytes, linear)
[    0.366451] tcp_listen_portaddr_hash hash table entries: 512 (order: 2, 20480 bytes, linear)
[    0.366611] TCP established hash table entries: 8192 (order: 4, 65536 bytes, linear)
[    0.366918] TCP bind hash table entries: 8192 (order: 6, 262144 bytes, linear)
[    0.367350] TCP: Hash tables configured (established 8192 bind 8192)
[    0.369283] UDP hash table entries: 512 (order: 3, 49152 bytes, linear)
[    0.369677] UDP-Lite hash table entries: 512 (order: 3, 49152 bytes, linear)
[    0.372257] NET: Registered protocol family 1
[    0.382006] RPC: Registered named UNIX socket transport module.
[    0.382095] RPC: Registered udp transport module.
[    0.382116] RPC: Registered tcp transport module.
[    0.382133] RPC: Registered tcp NFSv4.1 backchannel transport module.
[    0.382278] PCI: CLS 0 bytes, default 64
[    0.391432] workingset: timestamp_bits=62 max_order=18 bucket_order=0
[    0.418671] NFS: Registering the id_resolver key type
[    0.420658] Key type id_resolver registered
[    0.420721] Key type id_legacy registered
[    0.421317] nfs4filelayout_init: NFSv4 File Layout Driver Registering...
[    0.421447] nfs4flexfilelayout_init: NFSv4 Flexfile Layout Driver Registering...
[    0.422671] 9p: Installing v9fs 9p2000 file system support
[    0.425413] NET: Registered protocol family 38
[    0.426299] Block layer SCSI generic (bsg) driver version 0.4 loaded (major 251)
[    0.426699] io scheduler mq-deadline registered
[    0.426880] io scheduler kyber registered
[    0.454289] input: QEMU Virtio Mouse as /devices/platform/soc/10105000.virtio_mmio/virtio2/input/input0
[    0.456811] input: QEMU Virtio Keyboard as /devices/platform/soc/10104000.virtio_mmio/virtio3/input/input1
[    0.467156] Serial: 8250/16550 driver, 4 ports, IRQ sharing disabled
[    0.485057] printk: console [ttyS0] disabled
[    0.487586] 10000000.uart0: ttyS0 at MMIO 0x10000000 (irq = 1, base_baud = 230400) is a 16550A
[    0.549207] printk: console [ttyS0] enabled
[    0.553339] 10001000.uart1: ttyS1 at MMIO 0x10001000 (irq = 2, base_baud = 230400) is a 16550A
[    0.563191] [drm] radeon kernel modesetting enabled.
[    0.568638] [drm] features: -virgl +edid
[    0.570309] [drm] number of scanouts: 1
[    0.570731] [drm] number of cap sets: 0
[    0.597484] [drm] Initialized virtio_gpu 0.1.0 0 for virtio1 on minor 0
[    0.711552] Console: switching to colour frame buffer device 160x45
[    0.731434] virtio_gpu virtio1: [drm] fb0: virtio_gpudrmfb frame buffer device
[    0.791282] loop: module loaded
[    0.814540] virtio_blk virtio0: [vda] 2097152 512-byte logical blocks (1.07 GB/1.00 GiB)
[    0.815582] vda: detected capacity change from 0 to 1073741824
[    0.852595]  vda: vda1 vda2
[    0.878293] libphy: Fixed MDIO Bus: probed
[    0.911475] e1000e: Intel(R) PRO/1000 Network Driver
[    0.912730] e1000e: Copyright(c) 1999 - 2015 Intel Corporation.
[    0.914557] ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver
[    0.915923] ehci-pci: EHCI PCI platform driver
[    0.918434] ehci-platform: EHCI generic platform driver
[    0.920404] ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver
[    0.921664] ohci-pci: OHCI PCI platform driver
[    0.922937] ohci-platform: OHCI generic platform driver
[    0.933734] usbcore: registered new interface driver uas
[    0.935148] usbcore: registered new interface driver usb-storage
[    0.938227] mousedev: PS/2 mouse device common for all mice
[    0.948625] goldfish_rtc 10003000.rtc: registered as rtc0
[    0.952000] goldfish_rtc 10003000.rtc: setting system clock to 2021-08-09T06:36:48 UTC (1628491008)
[    0.965331] usbcore: registered new interface driver usbhid
[    0.965928] usbhid: USB HID core driver
[    0.971386] NET: Registered protocol family 10
[    0.995197] Segment Routing with IPv6
[    0.996810] sit: IPv6, IPv4 and MPLS over IPv4 tunneling driver
[    1.003680] NET: Registered protocol family 17
[    1.009301] 9pnet: Installing 9P2000 support
[    1.012361] Key type dns_resolver registered
[    1.013807] debug_vm_pgtable: [debug_vm_pgtable         ]: Validating architecture page table helpers
[    1.103225] EXT4-fs (vda2): mounted filesystem with ordered data mode. Opts: (null)
[    1.104780] VFS: Mounted root (ext4 filesystem) on device 254:2.
[    1.109586] devtmpfs: error mounting -2
[    1.142338] Freeing unused kernel memory: 220K
[    1.148368] Run /sbin/init as init process
[    1.149677] Run /etc/init as init process
[    1.150884] Run /bin/init as init process
[    1.152613] Run /bin/sh as init process
[    1.153840] Kernel panic - not syncing: No working init found.  Try passing init= option to kernel. See Linux Documentation/admin-guide/init.rst for guidance.
[    1.155653] CPU: 2 PID: 1 Comm: swapper/0 Not tainted 5.10.42 #208
[    1.156597] Call Trace:
[    1.157151] [<ffffffe000202794>] walk_stackframe+0x0/0xaa
[    1.157793] [<ffffffe0008e8706>] show_stack+0x32/0x3e
[    1.158385] [<ffffffe0008eb356>] dump_stack+0x74/0x8e
[    1.158931] [<ffffffe0008e891a>] panic+0x100/0x2b6
[    1.159545] [<ffffffe0008f2c86>] kernel_init+0xec/0xf8
[    1.160115] [<ffffffe000201314>] ret_from_exception+0x0/0xc
[    1.161124] SMP: stopping secondary CPUs
[    1.168836] ---[ end Kernel panic - not syncing: No working init found.  Try passing init= option to kernel. See Linux Documentation/admin-guide/init.rst for guidance. ]---

可以看到内核以及成功运行起来,只是在最后需要执行第一个用户态init进程时报了错误,因为找不到init,到这里本节就完成了,那么后面的init进程是什么,内核启动过程又是什么的流程,下一节我将对其做以分析解释。

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