计算机:操作系统的知识
Published:
操作系统,操作系统内核那些事儿
Linux kernel 编译
前置准备
linux 环境
- WSL2 Ubuntu 20.04
- 依赖:flex,bison,libelf-dev,libssl-dev 等
- 一键安装依赖可以参考 https://github.com/microsoft/WSL2-Linux-Kernel?tab=readme-ov-file#build-instructions
源码位置:
- 进入 https://www.kernel.org/,或者搜索 Linux kernel 找到这个
- 使用 git 下载的话就点击 git:https://git.kernel.org/
- 有很多 repo,从里面搜索 Torvalds,找到
kernel/git/torvalds/linux.git
- 点进去下方就有下载方式,git clone 或者 wget *.tar.gz 都可以,推荐 wget,源码特别大
busybox
- 用户空间的工具集,否则启动之后没有任何工具可以使用
- 链接:https://www.busybox.net/
- git clone,或者 wget *.tar.bz2:
wget https://busybox.net/downloads/busybox-1.37.0.tar.bz2
开始编译
编译 kernel
- make defconfig
- (optional)make menuconfig
- make -j$(nproc)
- 编译产物会显示:./arch/x86_64/boot/bzImage
编译 busybox
- 从源码编译
- make defconfig
- make nemuconfig
- 打开:Settings —> Build static binary (no shared libs)
- 关闭:Networking Utilities —> tc (8.3 kb)
- make -j$(nproc)
- make install
打包 initramfiles
- mkdir rootfs
- cp -r busybox-1.37.0/_install/* rootfs/
- vim init,内容如下
- #!/bin/sh
- echo “hello masteryi! this is your initramfs!”
- /bin/sh
- chmod +x init
find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz
使用 qemu 仿真
使用 qemu 仿真
- sudo apt install qemu-system-x86
- qemu-system-x86_64 -kernel linux-6.16-rc2/arch/x86_64/boot/bzImage -initrd initramfs.cpio.gz -nographic -append “console=ttyS0”
- 可以看到日志出现了“hello masteryi! this is your initramfs!”
- 使用
ctrl+A, x
退出 qemu
替换 WSL 的内核
将编译好的 vmlinux 拷贝到 Windows 下的路径
设置.wslconfig,这一步只替换 kernel,不影响发行版
- 查看状态,running 的话关机
- wsl -l -v
- wsl –shutdown
- vim ~/.wslconfig
- [wsl2]
- kernel=D:\project\vmlinux
- wsl -d ubuntu
查看 2 次的
uname -a
,发现 kernel 版本已经变了
替换 WSL 的用户空间
- 将 rootfs 打包:
tar -zcvf ../initramfs.tar.gz .
- 拷贝到 Windows 下的路径
- wsl –import mylinux D:\project\wsl_mylinux D:\project\initramfs.tar.gz
- wsl -d mylinux
编译 ISO 镜像文件
- sudo apt install isolinux syslinux genisoimage
- make isoimage FDARGS=”initrd=../initramfs.cpio.gz console=ttyS0” FDINITRD=../initramfs.cpio.gz
- qemu-system-x86_64 -cdrom arch/x86/boot/image.iso -nographic
编译 hd 镜像文件
和编译 ISO 基本是一样的
- sudo apt install ovmf(安装了还是找不到*** Need a shell.efi file for x64, please install EDK2/OVMF.)
- make hdimage FDARGS=”initrd=../initramfs.cpio.gz console=ttyS0” FDINITRD=../initramfs.cpio.gz
- qemu-system-x86_64 -hda arch/x86/boot/hdimage -nographic
但是安装 ovmf 后还是没有 shell.efi 这个东西,似乎是 ubuntu 的已知问题,于是源码下载,发现 master 分支没有这个文件,只能通过已有的 release 链接下载:
- wget https://github.com/tianocore/edk2/blob/UDK2018/ShellBinPkg/UefiShell/X64/Shell.efi(无效)
- wget https://github.com/tianocore/edk2/raw/UDK2018/ShellBinPkg/UefiShell/X64/Shell.efi
GitHub 的 blob URL 会返回一个 HTML 页面(包含代码渲染),而不是二进制文件 Shell.efi。所以下载还是要用 raw 的。
- sudo cp Shell.efi /usr/share/OVMF
但是编译好 hdimage 后,还是无法通过 qemu 来测试,因为没有 bootloader,需要自己制作。直接使用 qemu 就会出现 Boot failed: not a bootable disk 的问题。
具体的制作脚本可以参考:https://github.com/masteryi-0018/software-notes/blob/main/shell/test.sh
通过qemu-system-x86_64 -hda hd.img --nographic
成功启动!但是话说回来,其实并没有 hdimage 什么事。
编译 seabios
- git clone https://git.seabios.org/seabios.git(这个比较慢)
- git clone git@github.com:coreboot/seabios.git(github 镜像,快一些)
命令
- cd seabios
- make
- qemu-system-x86_64 -bios out/bios.bin –nographic
这时候就可以用自己的 bios 和自己制作的带有 bootloader 的 linux os 来测试了:
- qemu-system-x86_64 -hda hd.img -bios seabios/out/bios.bin –nographic
问题
为什么不能 apt install busybox?如果这样安装的话,会有依赖本地的动态库:
- $: ldd /usr/bin/busybox
- linux-vdso.so.1 (0x00007fff0cff3000)
- libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007f8b0c435000)
- libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8b0c243000)
- /lib64/ld-linux-x86-64.so.2 (0x00007f8b0c514000)
为什么需要手动设置 busybox?如果动态链接,还是有一样的问题;tc 这个是头文件,我也懒得继续 debug 了
安装操作系统
也就是通常讲的装系统,普罗大众会接触到的概念。给一个电脑安装操作系统,或者重装操作系统,最通常的情况是制作 U 盘启动盘。我们一般在另一个可以运行的电脑上下载对应的操作系统,这里一般是 ISO,也就是镜像,这个镜像也不直接搞到对应电脑的硬盘中,所以我们用 U 盘是最省事的,但是这个 ISO 不能直接使用,需要将 U 盘制作为启动盘之后才可以使用。
启动盘的作用和以前使用光盘启动是类似的,只不过更加方便,直接将 ISO 解压到 U 盘不能用,因为这个引导程序(Bootloader)没有被放置在正确的位置上,导致无法被识别为一个操作系统。
在编译 linux kernel 的时候,有几个选项:
- fd(软盘)
- hd(硬盘)
- cdrom(光盘)
这里的 cdrom 会强制安装 bios 到 iso 中,也就是解压后能看到的isolinux.bin
和isolinux.cfg
,这个 bin 和 seabios 的 out.bin 不是一个概念的东西,bios.bin 会执行 isolinux.bin,相当于光盘的 Bootloader。
使用 qwmu 仿真的时候,也有几个对应的选项:
- kernel + initrd
- fda
- hda
- cdrom
这里 cdrom 就是对应光盘,现在使用较少,而 hd 就是对应硬盘,U 盘启动盘就是模拟硬盘的;又因为光盘和 U 盘天生不同,光盘是 ROM,在刻录时,ISO 镜像的引导信息会自动写入光盘的引导扇区,因此,只要直接刻录完整的系统 ISO 文件,光盘本身就具备启动能力。U 盘是 RAM,所以需要制作启动盘这一步。
给电脑安装
不论是 PC 或者笔记本,都属于 x86 架构的通用计算机,这里的通用基本是指拥有主板,cpu,gpu,内存,等组件的一个计算机。主板是哪个厂商的,主板上的 bios 芯片就是哪个厂商已经写好的烧录好的,我们可以制作操作系统启动盘,但也是由这个 bios 进行引导的,也就是我们控制了 bootloader,os 和 kernel 的部分。如果我们需要修改 bios,需要专门写一个 bios,并且通过烧录,移植到主板上面。
上面的示例是 x86 架构的命令,如果是 arm64 架构的命令,有点不一样:
- sudo apt install gcc-aarch64-linux-gnu
- make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
- 下面如果不加 Image 会全部编译,目前 intel 有报错
- make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image
- qemu-system-aarch64 -kernel arch/arm64/boot/Image -machine virt -cpu cortex-a57 -nographic -append “console=ttyAMA0”,append 可以不加
如果是 arm32 位的:
- sudo apt install gcc-arm-linux-gnueabihf
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) zImage modules dtbs,后面的不加好像也行
- qemu-system-arm -kernel arch/arm/boot/zImage -dtb arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb -machine vexpress-a9 -nographic -append “console=ttyAMA0”,append 可以不加
为什么 arm32 需要指定 dtb?在 ARM 架构中,设备树是内核识别硬件配置的唯一方式。与 x86 架构不同(通过 BIOS/ACPI 自动检测硬件),ARM 架构没有自动硬件检测机制,依赖设备树二进制文件(DTB)描述 CPU、内存、外设等所有硬件信息,没有 DTB 时,内核完全”看不见”任何硬件,包括串口控制台。所以没有任何输出。
通过以下命令查看 dtb
ls arch/arm/boot/dts/*/*.dtb
ls arch/arm64/boot/dts/*/*.dtb
在 ARM64 (AArch64) 架构下使用 QEMU 启动时不需要显式指定 DTB 参数,这与 ARM32 不同,因为 QEMU 的 virt 机器类型(-machine virt)会自动生成设备树并传递给内核
为什么 arm64 需要指定 cpu 参数?ARM32:QEMU 的 vexpress-a9 等机器有默认 CPU(如 cortex-a9),不指定时仍能工作。ARM64:virt 机器没有默认 CPU,必须显式指定,否则 QEMU 无法创建有效的虚拟硬件环境。
还有一个有趣的 config,这个 config 可以用 arm32 和 arm64 来仿真:
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- multi_v7_defconfig
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
- (optional)qemu-system-arm -kernel arch/arm/boot/zImage -dtb arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb -machine vexpress-a9 -nographic
- (optional)qemu-system-aarch64 -kernel arch/arm64/boot/Image -machine virt -cpu cortex-a57 -nographic
给手机安装
手机从概念上面讲,甚至是一个广义的嵌入式设备,我的理解就是以 SoC 为主导的主板高度集成,手机的 bios/uefi 也在 SoC 上只读,之后进入存储分区,开始 bootloader 以及 Android 的加载。但是手机厂商一般会将 bootloader 上锁,只有解锁了之后,才能有 root 权限,才能使用已有的 bios/uefi 安装自己的操作系统,很折腾。
给树莓派安装
计算机可以这么分:
- 通用计算机:PC,笔记本
- SBC(单板计算机):树莓派,nv jetson 等。有操作系统,实际上是一个微型计算机。除了 cpu 还有 gpu 的一个 soc
- MCU(单片机):stm32,adruino。无操作系统,软件需要烧录运行
还有一些其他的:
- fpga:Xilinx 的一些板子(Spartan 系列,Artix 系列,Kintex 系列,Virtex 系列)
- SoC:zynq 系列,也是 Xilinx 的,但是使用了 arm+fpga 的异构体系
- ACAP:Versal 系列,自适应计算加速平台,感觉是加强版的 SoC,使用了 arm+fpga+AIE 的异构体系
树莓派可以安装官方的操作系统,也可以安装 ubuntu 等 linux 发行版,所以应该也可以安装自己编译的 linux 系统,但不同的是,树莓派也是基于 arm 的架构,和手机一样,基于 arm 架构没有传统意义的 bios/uefi,叫做 bootrom,但实际上也一样,以后也就用 bios 指代这个启动过程。
使用 linux 官方的 kernel
编译 linux kernel
- 查看 linux kernel 支持的树莓派 config,
ls arch/arm/configs/*bcm*
- 发现只有一个 bcm2835_defconfig
- 安装依赖:sudo apt install gcc-arm-linux-gnueabihf
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2835_defconfig
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
- 查看 linux kernel 支持的树莓派 config,
使用 qemu 仿真
- sudo apt install qemu-system-arm
- qemu-system-arm -kernel arch/arm/boot/zImage -machine raspi2 -dtb arch/arm/boot/dts/broadcom/bcm2836-rpi-2-b.dtb -nographic
- qemu-system-arm -kernel arch/arm/boot/zImage -machine raspi2 -dtb arch/arm/boot/dts/broadcom/bcm2837-rpi-2-b.dtb -nographic
- qemu-system-arm -kernel arch/arm/boot/zImage -machine raspi2 -dtb arch/arm/boot/dts/broadcom/bcm2837-rpi-cm3-io3.dtb -nographic
- 推荐直接用 qemu-system-aarch64,其兼容 arm
- 或者使用 win 下的 qemu,qemu-system-arm.exe -kernel arch/arm/boot/zImage -machine raspi2b -dtb arch/arm/boot/dts/broadcom/bcm2836-rpi-2-b.dtb。同样推荐用 qemu-system-aarch64.exe,其兼容 arm
- 经过实验,只有 2 系列是 ok 的
其他的系列,比如0, 1ap
是因为 QEMU 的 raspi1ap 串口初始化时序问题,需要强制指定早期串口参数:-append “console=ttyAMA0,115200 earlycon=pl011,0x20201000 loglevel=8”
使用 qemu 仿真其他树莓派型号
- (0 系列:bcm2835-rpi-zero-w.dtb,bcm2835-rpi-zero.dtb,bcm2837-rpi-zero-2-w.dtb)
- qemu-system-arm.exe -kernel arch/arm/boot/zImage -machine raspi0 -dtb arch/arm/boot/dts/broadcom/bcm2835-rpi-zero-w.dtb -append “console=ttyAMA0, earlycon=pl011,0x20201000”
- (1 系列:bcm2835-rpi-a-plus.dtb,bcm2835-rpi-a.dtb,bcm2835-rpi-b-plus.dtb,bcm2835-rpi-b-rev2.dtb,bcm2835-rpi-b.dtb,bcm2835-rpi-cm1-io1.dtb)
- qemu-system-arm.exe -kernel arch/arm/boot/zImage -machine raspi1ap -dtb arch/arm/boot/dts/broadcom/bcm2835-rpi-a-plus.dtb -append “console=ttyAMA0, earlycon=pl011,0x20201000”
- (3 系列:未知原因无输出,可能本身这个 config 就不支持)
使用 raspi 的 kernel
参考文档:https://www.raspberrypi.com/documentation/computers/linux_kernel.html#cross-compile-the-kernel
编译 linux kernel
- 这里编译一个 64 位的,按照官网的说明
- KERNEL=kernel8
- make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig
- make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image modules dtbs
使用 qemu 仿真,以上命令编译的产物可以在 64 位 3 和 4 上运行
- qemu-system-aarch64.exe -kernel arch/arm64/boot/Image -machine raspi4b -dtb arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb -nographic -append “console=ttyAMA0,115200 earlycon=pl011,0xfe201000 loglevel=8”
- qemu-system-aarch64.exe -kernel arch/arm64/boot/Image -machine raspi3b -dtb arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dtb
这里 4b 也是有问题,用图形界面的时候就是显示正常的,使用-nographic
后必须再显示指定一下串口。
使用 raspi 的 os
从官网下载了 os,注意不需要下载图形界面之类的。解压得到 img,设置为 loop 设备,挂载,找到 kernel 文件和 dtb 文件,仿真
命令:https://github.com/masteryi-0018/software-notes/blob/main/shell/raspi-linux/test.sh
启动过程
- 上电
- BIOS/UEFI:烧录在主板 bios 芯片,执行 POST(硬件自检)等
- bootloader:lilo/grub,位于 os 镜像中,执行加载 kernel
- kernel:接管资源,执行 init 进程,暴露 shell 给用户