手搓Linux发行版

该文章主要引用自于小乐大佬的B站视频
自己跟着视频手搓了一遍并把视频教程做成博客,同时记录一些自己出现的问题

编译内核

第一步就是从网上找到现成的linux内核并下载下来,Linux Kernel Archives
手搓一个linux发行版-娱乐向-2025-10-17-13-33-07

我下载的时候,与视频中的版本不相同,这也导致我后面出现了一些无法解决的问题。在视频中的稳定内核版本为6.13.2,而我的版本为6.17.3。我出现的问题是我无法打开FB_EFI。(这在第二弹视频中有提到过)。启动系统后只有一个光标,但是一直无法显示后续内容的黑屏。在下面我给出GPT的相关回答。

这是我问GPT给出的相关答案
在 Linux 内核 6.17.3 这一版本中,如果你在 make menuconfigmake xconfig 里 看不到 “EFI-based Framebuffer Support” 选项(CONFIG_FB_EFI),这是正常的现象 —— 从 内核 6.x 开始,FramebufferEFI 的支持方式发生了较大变化。

  • 内核逐步弃用老旧的 fbdev 驱动(如 efifb、uvesafb)。
  • DRM(Direct Rendering Manager) 体系成为主流。
  • EFI framebuffer 功能被整合进 simpledrm 驱动 中,而不再独立显示 CONFIG_FB_EFI。

最终我的解决方案是手动修改.config文件,设置了下面这些参数

1
2
3
4
5
6
CONFIG_FB=y
CONFIG_FRAMEBUFFER_CONSOLE=y
CONFIG_DRM_SIMPLEDRM=y
CONFIG_DRM_FBDEV_EMULATION=y
CONFIG_EFI=y
CONFIG_EFI_VARS=y

下面给一串代码,包括下载,解压和编译的过程
注意下面跑代码的时候可能会出现很多缺包,版本不匹配的问题。你直接将代码复制粘贴给GPT就可以解决。这不是问题的重点,所以自行ask gpt就成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 下载
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.17.3.tar.xz # 注意版本,你需要下载自己所需要的版本

# 解压
tar -xf linux-6.17.3.tar.xz

# 进入目录并设置编译配置
cd linux-6.17.3
make defconfig # 生成一个默认配置.config
vim .config # 根据我上面引用的GPT内容进行修改。关于vim如何查找,使用。请自行google。

make -j 8 # 开始编译,并以8个线程同步运行
# 编译完成后,会在arch/x86/boot/下生成一个bzImage,这就是你需要使用的内核文件

# 模拟跑一次
qemu-system-x86_64 -kernel arch/x86/boot/bzImage #不要ssh跑,他不会显示黑框框的。

自制shell,模拟一次运行过程

编写一个shell.c文件,这个代码会作为一个启动程序,当我们启动linux会直接启动这个shell脚本。

1
2
3
4
5
6
7
#include<stdio.h>
int main(){
while(1){
printf("Hello World\n");
scanf("%d");
}
}

将c文件编译成一个可执行文件,同时将其设置为static,取消动态链接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#先直接编译
gcc shell.c
#生成一个a.out
ldd ./a.out # 查看这个文件动态链接了那些库,可以看到有一个
# libc.so.6 => lib/x86_64-linux-gnu/libc.so.6
# 这个就是动态链接,因此我们添加一个编译参数-static把所有动态库打包到一个执行文件中
gcc shell.c -static
mv a.out init # 改名
# 将a.out用cpio打包成启动linux内核专门使用的init
echo "init" | cpio -H newc -o > init.cpio

# 执行一遍
qemu-system-x86_64 -kernel linux-6.17.3/arch/x86/boot/bzImage -initrd init.cpio

解释为什么要用cpio打包
在 Linux 启动过程中,内核会加载一个临时根文件系统(initramfs 或 initrd),它通常是一个 cpio 打包的归档文件。
cpio 是一种打包工具,常用于制作 Linux 的 initramfs 等文件系统镜像,因为内核能直接识别并解压它。

1
2
3
4
cpio -H newc -o
-o:表示 “copy out”,即创建打包文件;
-H newc:指定输出格式为 newc(一种常用于 initramfs 的新 ASCII 格式,支持大文件与设备号);
cpio 会根据输入的文件名列表(这里是 "init")把对应文件打包输出到标准输出。

构建一个完整的linux发行版

首先,一个完整的linux需要一个内核,一个shell,一个bootloader,一个根文件系统。

busybox

BusyBox 是一个把众多常用 Linux 命令集成到单一可执行文件中的轻量级工具集合,被称为 “嵌入式 Linux 的瑞士军刀”。
从网站中下载出busybox然后解压,执行一些编译操作。具体内容请直接看视频。这里只说几个具体的指令
make menuconfig 打开配置菜单并关闭tc选项
手搓一个linux发行版-娱乐向-2025-10-17-14-28-35

然后就将解压后的内容安装到一个文件夹下备用。
make install CONFIG_PREFIX=you_dir

构建一个根文件系统

1
2
3
4
5
sudo dd if=/dev/zero of=/linux.img bs=1M count=512 #构建一个文件,内容全为0,共有512MB

# 使用gdisk编辑这个文件,把他构建成一个虚拟磁盘,然后分两个区,一个EFI引导分区,一个数据分区。具体操作请接着看视频吧,这些部分用文字来的并不如视频直接。

# 当你完成两个分区的创建后,可以对这两个分区进行格式化并配置其内容

要对这个两个分区进行格式化,首先先使用losetup把他们模拟成磁盘

losetup 是用于管理环回设备(loop device)的命令。
环回设备允许你把一个普通文件(例如镜像文件 linux.img)当作一个块设备使用,就像一块真正的磁盘一样,这样你就可以对它进行挂载、分区操作等
losetup -f -P linux.img
-f表示自动查找空闲的 loop 设备
-P会告诉 losetup 在建立环回设备时自动扫描镜像中的分区表,并为每个分区创建对应的设备节点。
执行后,会生成一个loop设备。

1
2
3
4
5
6
7
8
9
# 查看设备
lsblk # 可以查看到一个512M的loop磁盘,分成了两个49MB和480MB的分区。

# 对这两个分区进行格式化
mkfs.fat F32 /dev/loop13p1 # 将第一个EFI分区格式化为32分区
mkfs.ext4 /dev/loop13p2 # 将数据分区变为ext4格式

# 新建一个mnt目录,并把EFI分区挂载到这个目录上
mkdir mnt && mount /dev/loop13p1 mnt

使用grub-install配置引导分区
执行下面的命令把grub安装到EFI分区中。

1
grub-install --target=x86_64-efi --efi-directory=$(realpath mnt) --bootloader-id=GRUB --removable --recheck

配置grub.cfg
修改grub.cfg并复制一份到boot/grub下,避免因不同硬件的原因导致的引导失败。

1
2
3
4
5
6
7
8
9
# 修改grub.cfg
vim mnt/EFI/BOOT/grub.cfg
# 主要将其中的uuid进行更改,修改成你对应的数据盘的uuid,可以使用下面的命令查询你的第二个分区的uuid
blkid /dev/loop13p2 # 然后将对应的uuid复制并修改原来的cfg中uuid的部分
# 然后复制到另一目录
mkdir mnt/boot/grub/grub.cfg

# 然后卸载该分区
umount mnt

然后配置数据分区
主要是这几个内容。

  1. 将busybox移动进去
  2. 把bzImage移动进去
  3. 设置对应的grub.cfg(与引导分区的grub不一样)
  4. 添加对应的init文件

前两个步骤无需多言,稍微注意一点,在mnt中新建一个boot文件夹,然后将bzImage移动到这个boot文件夹下,busybox直接放在mnt目录下即可。

然后在boot中新建一个grub目录,然后创建grub.cfg文件

1
2
3
4
5
6
7
8
9
menuentry "xiaohe_linux" {
insmod part_gpt
insmod fat
insmod ext2
insmod normal

search --no-floppy --fs-uuid --set=root 你的数据盘UUID
linux /boot/bzImage root=PARTUUID=你的数据盘PARTUUID rw init=/boot/init rootdelay=3
}

uuid和part_uuid都是通过blkid来获取的,可以很直观的看到。

接着在boot目录下创建一个init文件,内容如下

1
2
3
4
5
6
7
!/bin/sh

mount -t sysfs none /sys
mount -t proc none /proc
mount -t devtmpfs devtmpfs dev

exec /bin/sh

事后别忘了给init添加执行权限,即chmod +x init

然后在mnt目录下创建出sys,proc,dev目录,用来再后面启动时进行装载。

以上,完成这些步骤后,基本就算构建好了一个可运行的最小linux开发版本。

启动一次

退出mnt目录,然后将这个磁盘卸载并脱机

1
2
3
4
5
6
cd ../ # 回到原本的目录
umount mnt
losetup -d /dev/loop13

# 使用qemu-system再模拟执行一遍
qemu-system-x86_64 -drive file=./linux.img -bios /usr/share/ovmf/OVMF.fd -m 1G -serial stdio

接着应该就可以看到正常的界面展示了

将虚拟磁盘装载到U盘上

1
2
3
# 这里你需要判断一下你插入的U盘是什么名字,因为dd指令会把u盘格式化,别格式化错了文件
lsblk | grep sd # 可以查看sd开头的磁盘名,你需要通过磁盘大小判断那个才是你的U盘
dd if=linux.img of=/dev/sdb

然后就可以直接插在电脑上当作正常启动盘来使用了。

我在这里还遇到了一个问题
手搓一个linux发行版-娱乐向-2025-10-17-15-24-33
目前还没能解决,因为我也才刚刚实现。