当前位置:首页>Linux>Windows下的Linux内核编译和测试

Windows下的Linux内核编译和测试

  • 2026-09-05 00:17:58
Windows下的Linux内核编译和测试

环境

这里使用WSL(Windows Subsystem for Linux)下安装的Linux系统进行内核的编译,使用的版本:

work@HOME:~$ uname -aLinux HOME 6.6.87.2-microsoft-standard-WSL2 #1 SMP PREEMPT_DYNAMIC Thu Jun  5 18:30:46 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

内核代码下载

使用如下的命令下载源码:

work@HOME:~/code$ wget https://mirrors.aliyun.com/linux-kernel/v5.x/linux-5.4.239.tar.xz--2026-01-07 22:01:36--  https://mirrors.aliyun.com/linux-kernel/v5.x/linux-5.4.239.tar.xzResolving mirrors.aliyun.com (mirrors.aliyun.com)... 111.1.160.30, 39.174.180.10, 39.174.180.4, ...Connecting to mirrors.aliyun.com (mirrors.aliyun.com)|111.1.160.30|:443... connected.HTTP request sent, awaiting response... 200 OKLength: 113011896 (108M) [application/octet-stream]Saving to: ‘linux-5.4.239.tar.xz’linux-5.4.239.tar.xz          100%[=================================================>] 107.78M  10.3MB/s    in 11s2026-01-07 22:01:46 (10.2 MB/s) - ‘linux-5.4.239.tar.xz’ saved [113011896/113011896]

这里从镜像网站下载某版本的内核。然后解压缩:

work@HOME:~/code$ tar -xvf linux-5.4.239.tar.xz# 中间略work@HOME:~/code$ lslinux-5.4.239  linux-5.4.239.tar.xz

内核编译

  1. 首先要保证工具齐全,下载如下的工具:
work@HOME:~/code$ sudo apt install -y build-essential libncurses-dev bison flex libssl-dev libelf-dev cpio rsync make dosfstools

可能并不是所有工具都需要,不过装了也没有什么坏处。

  1. 编译内核之前需要先配置,这里直接使用默认配置:
work@HOME:~/code/linux-5.4.239$ make defconfig  HOSTCC  scripts/kconfig/conf.o  HOSTCC  scripts/kconfig/confdata.o  HOSTCC  scripts/kconfig/expr.o  LEX     scripts/kconfig/lexer.lex.c  YACC    scripts/kconfig/parser.tab.[ch]  HOSTCC  scripts/kconfig/lexer.lex.o  HOSTCC  scripts/kconfig/parser.tab.o  HOSTCC  scripts/kconfig/preprocess.o  HOSTCC  scripts/kconfig/symbol.o  HOSTLD  scripts/kconfig/conf*** Default configuration is based on 'x86_64_defconfig'## configuration written to .config#
  1. 然后就可以进行编译了:
work@HOME:~/code/linux-5.4.239$ make -j$(nproc) bzImage

$(nproc)指定了编译使用的CPU核心数。

一切正常的话就可以得到bzImage文件:

work@HOME:~/code/linux-5.4.239$ ls ./arch/x86/boot/bzImage -al-rw-r--r-- 1 work work 9048896 Jan  7 22:04 ./arch/x86/boot/bzImage

制作文件系统

光一个内核还不够,直接启动(通过QEMU虚拟机)的话会得到如下的结果:

从报错信息可以看到,为了能够正常启动,还需要一个根文件系统。下面介绍如何创建一个简单的根文件系统:

  1. 首先创建一个目录,用于存放根文件系统需要的内容:
work@HOME:~/code$ mkdir buildwork@HOME:~/code$ cd buildwork@HOME:~/code/build$ mkdir -p rootfs/{bin,dev,proc,sys,tmp,lib64,lib}work@HOME:~/code/build$ cd ./rootfs/work@HOME:~/code/build/rootfs$ lsbin  dev  lib  lib64  proc  sys  tmp
  1. 在文件系统中需要一些可以执行的程序,busybox是一个Linux下的工具集,这里就使用它。不过它需要额外的编译。首先下载busybox:
work@HOME:~/code$ wget https://busybox.net/downloads/busybox-1.37.0.tar.bz2--2026-01-07 22:08:26--  https://busybox.net/downloads/busybox-1.37.0.tar.bz2Resolving busybox.net (busybox.net)... 140.211.167.122Connecting to busybox.net (busybox.net)|140.211.167.122|:443... connected.HTTP request sent, awaiting response... 200 OKLength: 2565764 (2.4M) [application/x-bzip2]Saving to: ‘busybox-1.37.0.tar.bz2’busybox-1.37.0.tar.bz2        100%[=================================================>]   2.45M  6.83KB/s    in 7m 5s2026-01-07 22:15:26 (5.89 KB/s) - ‘busybox-1.37.0.tar.bz2’ saved [2565764/2565764]

然后解压:

work@HOME:~/code$ tar -xvf busybox-1.37.0.tar.bz2

再进行配置,还是使用默认配置:

work@HOME:~/code/busybox-1.37.0$ make defconfig

不过需要注意使用静态编译,因为它依赖的动态库在前面内核构成的系统中并不存在,会导致异常。

这里需要修改.config(通过make defconfig生成)中的如下配置:

## Build Options## CONFIG_STATIC is not set

可以通过make menuconfig打开图形配置界面,勾选“Settings”下的如下配置:

另外由于WSL内核版本与busybox的版本兼容问题,还需要关闭另外的选项(当然也可以通过更新busybox代码来解决),它是位于“Networking Utilities”下的如下配置:

之后就可以进行编译了:

work@HOME:~/code/busybox-1.37.0$ make -j$(nproc)

最后安装到前面创建的目录中,这通过指定CONFIG_PREFIX参数来实现:

work@HOME:~/code/busybox-1.37.0$ make install CONFIG_PREFIX=~/code/build/rootfs/  /home/home/code/build/rootfs///bin/arch -> busybox  /home/home/code/build/rootfs///bin/ash -> busybox  /home/home/code/build/rootfs///bin/base32 -> busybox  /home/home/code/build/rootfs///bin/base64 -> busybox  /home/home/code/build/rootfs///bin/cat -> busybox  /home/home/code/build/rootfs///bin/chattr -> busybox  /home/home/code/build/rootfs///bin/chgrp -> busybox  /home/home/code/build/rootfs///bin/chmod -> busybox  /home/home/code/build/rootfs///bin/chown -> busybox# 后面略

该步骤相当于往文件系统中安装了很多的工具。

查看之前创建的目录:

work@HOME:~/code/build/rootfs$ lsbin  dev  lib  lib64  linuxrc  proc  sbin  sys  tmp  usr

可以看到多了很多新的内容。

  1. 之后还有一些必要的操作,包括创建节点:
work@HOME:~/code/build/rootfs$ sudo mknod dev/console c 5 1work@HOME:~/code/build/rootfs$ sudo mknod dev/null c 1 3

创建init文件:

work@HOME:~/code/build/rootfs$ cat > init << EOF#!/bin/shmount -t proc none /procmount -t sysfs none /sysmount -t tmpfs none /tmpmount -t devtmpfs none /devexec /bin/shEOFwork@HOME:~/code/build/rootfs$ chmod +x ./init

init文件很重要,它是内核启动后在用户空间创建的第一个进程,它的核心作用是初始化系统运行环境、启动后续的系统服务,这里的init就是一个脚本程序,它调用的/bin/sh就是busybox安装的,开启了一个shell。

  1. 最终构建出根文件系统:
work@HOME:~/code/build/rootfs$ find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz

如下所示:

work@HOME:~/code/build$ lsinitramfs.cpio.gz  rootfs

initramfs.cpio.gz就是最终需要的。

内核使用

这里使用Windows下的QEMU,执行如下的命令:

PS E:\Linux> qemu-system-x86_64.exe -kernel .\bzImage -initrd .\initramfs.cpio.gz -nographic -append "console=ttyS0 root=/dev/ram rw init=/init"

参数说明如下:

  • -kernel:指定编译好的内核镜像文件。
  • -initrd:指定根文件系统镜像文件。
  • -nographic:无图形界面,使用终端输出。
  • -append:内核启动参数:
    • console=ttyS0:将控制台输出重定向到串口(适配 QEMU)。
    • root=/dev/ram:根文件系统在内存中。
    • rw:根文件系统可读写。
    • init=/init:指定启动的第一个程序。

最终能够正常启动:

启动硬盘

前面的示例是直接使用内核和文件系统来进行启动,这里将内核和文件系统放到虚拟硬盘中进行启动,这更像是一般的桌面系统所做的事情。

创建硬盘

  1. 创建一个虚拟硬盘文件:
root@HOME:/home/work/code/build# dd if=/dev/zero of=disk.img bs=1M count=6464+0 records in64+0 records out67108864 bytes (67 MB, 64 MiB) copied, 0.0244948 s, 2.7 GB/s
  1. 然后格式化:
root@HOME:/home/work/code/build# mkfs.vfat disk.imgmkfs.fat 4.2 (2021-01-31)

注意这里进行了简化,不在进行分区。

  1. 挂载硬盘,并放置需要的文件:
root@HOME:/home/work/code/build# mkdir tmproot@HOME:/home/work/code/build# mount -o loop disk.img ./tmp/root@HOME:/home/work/code/build# mkdir tmp/efiroot@HOME:/home/work/code/build# mkdir tmp/efi/bootroot@HOME:/home/work/code/build# cp grub.cfg bootx64.efi ./tmp/efi/boot/root@HOME:/home/work/code/build# cp bzImage initramfs.cpio.gz ./tmp/

最终硬盘中的文件:

root@HOME:/home/work/code/build# tree ./tmp/./tmp/├── bzImage├── efi│   └── boot│       ├── bootx64.efi│       └── grub.cfg└── initramfs.cpio.gz3 directories, 4 files

bzImage和initramfs.cpio.gz已经在前面介绍过,bootx64.efi和grub.cfg将在下面进一步介绍。

  1. 最后退出硬盘文件:
root@HOME:/home/work/code/build# umount ./tmp/

GRUB编译

  1. 下载GRUB源码:
work@HOME:~/code$ wget https://ftp.gnu.org/gnu/grub/grub-2.06.tar.xz--2026-01-07 22:32:30--  https://ftp.gnu.org/gnu/grub/grub-2.06.tar.xzResolving ftp.gnu.org (ftp.gnu.org)... 209.51.188.20, 2001:470:142:3::bConnecting to ftp.gnu.org (ftp.gnu.org)|209.51.188.20|:443... connected.HTTP request sent, awaiting response... 200 OKLength: 6581924 (6.3M) [application/x-xz]Saving to: ‘grub-2.06.tar.xz’grub-2.06.tar.xz              100%[=================================================>]   6.28M  6.33KB/s    in 23m 45s2026-01-07 22:54:31 (4.51 KB/s) - ‘grub-2.06.tar.xz’ saved [6581924/6581924]

然后解压:

work@HOME:~/code$ tar -xvf grub-2.06.tar.xz
  1. 再进行配置:
work@HOME:~/code/grub-2.06$ ./configure  --target=x86_64 --with-platform=efi --disable-werror

这里使用了几个参数:

  • --target参数用来指定对应的平台,这里指定了x86的64位平台。
  • --with-platform用来指定用于UEFI。
  • --disable-werror用来防止告警变成错误导致编译异常。
  1. 之后进行编译:
work@HOME:~/code/grub-2.06$ make -j$(nproc)
  1. 生成最终的GRUB二进制:
work@HOME:~/code/grub-2.06$ ./grub-mkimage -p /efi/boot -d ./grub-core/ -O x86_64-efi -o bootx64.efi boot linux part_msdos part_gpt fat normal serial efi_gop minicmd configfile

参数的说明如下:

  • -p指定了grub.cfg、独立模块等文件所在的目录,这里指定与bootx64.efi在同一个目录/efi/boot。注意这个参数不能错,否则会找不到grub.cfg文件。
  • -d指定了我们刚才编译出来的image和module的位置,因为它们才是我们需要的,而不是系统中已经存在的。
  • -O指定了二进制的格式,我们需要x86平台下UEFI的格式。
  • -o用来指定输出的文件名。
  • 最后接的是GRUB模块,就是grub-core目录下的*.module文件:
work@HOME:~/code/grub-2.06$ ls ./grub-core/ -altotal 30692drwxr-xr-x 30 work work   36864 Jan  7 23:02 .drwxr-xr-x 15 work work    4096 Jan  7 23:02 ..drwxr-xr-x  2 work work    4096 Jan  7 23:02 .deps-coredrwxr-xr-x  2 work work    4096 Jan  7 23:02 .deps-util-rw-r--r--  1 work work       0 Jan  7 23:02 .dirstamp-rw-r--r--  1 work work 3265263 Jan  7 23:02 Makefile-rw-r--r--  1 work work   20471 Mar  2  2021 Makefile.am-rw-r--r--  1 work work 1135139 Jun  8  2021 Makefile.core.am-rw-r--r--  1 work work   50130 Mar  2  2021 Makefile.core.def-rw-r--r--  1 work work    3403 Jun  8  2021 Makefile.gcry.def-rw-r--r--  1 work work 4431360 Jun  8  2021 Makefile.in-rw-r--r--  1 work work    1151 Jan  7 23:02 acpi.marker-rw-r--r--  1 work work   15872 Jan  7 23:02 acpi.mod-rw-r--r--  1 work work   81600 Jan  7 23:02 acpi.module-rw-r--r--  1 work work       0 Jan  7 23:02 adler32.marker-rw-r--r--  1 work work    1952 Jan  7 23:02 adler32.mod# 后面略

这里并没有包含所有模块,具体需要哪些文件可以根据实际情况选择。

  1. 此外还需要写一个grub.cfg文件作为bootx64.efi的配置文件:
set default=0 set timeout=0menuentry 'UEFI Linux' {set root=(hd0)  linux /bzImage root=/dev/sda rw console=tty0 init=/init  initrd /initramfs.cpio.gz}

注意这里指定的set root=后的内容需要根据实际情况来设置,如果包含分区(比如GPT分区),则可能是写成(hd0,gpt1)这样的格式。可以在GRUB的Shell下通过ls命令查看到底有哪些硬盘。

执行硬盘镜像

这里使用Windows下的QEMU,执行的命令来自https://gitee.com/jiangwei0512/edk2-beni:

E:\Gitee\edk2-beni>boot.cmd start disk

实际上执行的命令如下:

qemu-system-x86_64 -display gtk,show-menubar=off -m 1024 -machine q35,smm=on -drive if=pflash,format=raw,unit=0,file=OVMF.fd -drive format=raw,file=disk.img

得到的结果:

应用执行

这里创建一个简单的应用,代码如下:

#include <stdio.h>int main(void){printf("Hello world!\n");return 0;}

通过gcc进行编译:

work@HOME:~/code/test$ gcc ./helloworld.c -static -o hellowork@HOME:~/code/test$ lltotal 780drwxr-xr-x 2 work work   4096 Jan  7 23:10 ./drwxr-xr-x 7 work work   4096 Jan  7 23:09 ../-rwxr-xr-x 1 work work 785368 Jan  7 23:10 hello*-rw-r--r-- 1 work work     84 Jan  7 23:10 helloworld.c

注意参数-static,这样应用就不会依赖于外部的系统。

将这个应用放到前面创建的虚拟硬盘disk.img中并重新启动(示例中修改了内核的输出,变成从串口输出了,通过修改grub.cfg中的内核的console参数来完成):

~ # mount /dev/sda tmp[   10.734360] mount (84) used greatest stack depth: 14632 bytes left~ # cd tmp/tmp # lsappdp.efi              event.efi              helloworld_signed.efiboot                   example.json           memtest.efiefi                    hello                  startup.nsh[   13.919596] ls (85) used greatest stack depth: 14064 bytes left/tmp # ./helloHello world!

实际上前面的代码还有点问题,所以并没有真正进入硬盘的根文件系统(暂时不管),所以还需要挂载硬盘文件,然后执行其中的hello程序,得到上述的结果。

参考

  • https://gitee.com/jiangwei0512/edk2-beni

最新文章

随机文章