自研操作系统OS类Unix内核开发环境搭建与问题分析(GRUB, Multiboot, xorriso, clangd)
0 环境准备
windows11宿主机, intel core系列芯片
vmplayer 17虚拟机(免费)
ubuntu24.04
vscode
clangd (vscode插件)
xorriso (xorriso和bear都可以通过sudo apt install安装)
bear (生成build json文件,供clangd代码分析,内核开发一般用clangd分析)
1 前言
1.1. 大学课程里的操作系统课程主要是针对内核而言。和字面意思理解可能不一样,真正的内核,在计算机的运行框架里,更像是千层饼里的一层,而不是想象中的一个球体。
1.2. 在内核之下,有硬件,bios,寄存器,grub等为内核提供操作硬件的接口指令(这也是内核需要适配不同架构的原因。不同架构的接口指令不同,内核只能分开处理,统一逻辑),严格可以说内核代码是不直接操作硬件的。
1.3. 在内核之上,则是用户态程序。而用户态是内核分级权限指令的一种状态。所以可以称为运行在内核用户态之上的程序。怎么运行的呢?内核针对用户态会有专门的syscall_abi接口,以提供给用户态程序使用,包括文件处理,内存处理,打印服务,网络服务等等。这些都有对应的syscall_abi接口提供给用户程序。内核只需要保证abi接口稳定,就可以实现内核和用户程序的更新隔离,以及编译隔离。常见的用户程序比如(图形界面,终端等)。
1.4. 我们常见的OS镜像包,比如windows的iso安装包,ubuntu的iso安装包,这些里面包含的是两部分:内核,以及常驻用户程序。我们可以把内核比作一栋毛坯房,把常驻用户程序比作精装修。我们日常使用的电脑系统就是一个精装修的房子。也就是说,一栋毛坯房,不同的精装修就是不同的居住体验,类似不同的linux系统发行商版本arch, ubuntu等。从这个比喻也可以看出,即便只有内核,也可以算作一个OS,可以运行了。就像毛坯房也可以住人不是,就是条件简陋点。而我们的目标,就是做毛坯房。一个人喔,精装房就不用想了,没必要。我们是要学习操作系统的,不是要做一个新系统开公司和华为,苹果,windows竞争喔(狗头)。
tips: 为什么华为排第一呢,因为俺是中国人(^v^)
1.5 可以看出,常驻用户程序这一层,更侧重留住用户,是用户导向,功能优先;而内核这一层,更侧重稳定,性能,时间,安全。当然常驻用户程序肯定也是侧重稳定,性能,时间,安全的。但就像刚才的比喻一样,常驻用户程序即便在这四项上不达标或表现不好,也就是居住体验不好,能忍。但内核如果在这四项上不达标或表现不好,那就是危房了,怎么会有用户敢住呢。
2 具体内核选择
2.1. 有两个选择方向,一个是看早期版本内核,这个时候内核是完整的,相对简单的,能够帮助理解整个内核架构和全貌。这也够了,对于想要深入了解内核的同学。至于比如2026年现在的linux内核,那都是好多人经过几十年积累写的呢,一个人怎么看的完!至于为什么不提windows内核呢,因为是闭源的,俺不想吃国家饭(狗头)。另外个人也觉得windows内核估计很丑陋,光看mfc相关就难受。至于另外一个选择呢,自然就是自己手搓了。
2.2. 那么我们手搓肯定是推荐参考linux的。那这不就矛盾了吗,linux那么大,我手搓本来就是为了避免看linux那么大的代码。ok,现在是2026年,我们有AI大人了。
2.3. 从创建内核项目目录开始,我们就需要了解从引导程序到内核入口,这段路程。这其中涉及到内核层与内核层之下的层级交互。会出现很多的名词。我个人推荐在遇到不熟悉的名词时,搜索了解一下这些名词背后的发展历史,对于自写内核有很大帮助,就会觉得,喔,原来在几十年的诞生历史里,内核是这样随着计算机产生,长大的。
3. 工具编译相关
3.0. 首先,我们需要在编译系统禁用c库文件,以及若干针对内核的编译项,比如:
CPARAMS = -m32 -I. -nostdlib -fno-builtin -fno-exceptions \
-fno-leading-underscore -std=gnu2x \
-ffreestanding -fno-stack-protector \
-fno-pic -fno-pie \
-Wall -Wextra -Wno-address-of-packed-member \
-mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx \
-O2 -g
其中-std=传统经典的内核比如linux,一般都选择gnu11,也就是c11,但这里为什么选c23呢。因为理论上c23比c11更有优势一些。那为什么linux不用呢,我问AI大人,说是历史包袱,更换代价大,且收益不高。
3.1. 之前已经讲过,内核只是计算机千层饼的中间一层,那么要做一个内核呢:
| cpu架构 | 引导程序 | 引导程序提供给内核的统一接口规范 | 内核 | 打包成可运行iso镜像工具 |
|---|---|---|---|---|
| x86 | grub | multiboot | 自研内核 | xorriso |
| arm | uboot | boom/bootz(arm相比于x86在引导程序有很大不同,这里放在一列只是为了好看) | 自研内核 | xorriso |
3.2 首先,我们要先明确手搓的各个阶段。既然我们要盖房子,那就必须得有地基,也就是引导程序。引导程序选择呢根据你的电脑的cpu型号来定。我是大部分人,所以选择grub。那么我们写内核时,怎么就算选择到grub呢:
3.2.1. 首先我们需要在内核编译文件里添加grub编译命令:
$(JLOS).bin: arch/$(ARCH)/linker.ld $(OBJS)
@ld $(LDPARAMS) -T $< -o $(JLOS).bin $(OBJS)
install: $(JLOS).bin
@sudo cp $< /boot/$(JLOS).bin
$(JLOS).iso: $(JLOS).bin
@rm -rf iso
@mkdir -p iso/boot/grub
@cp $< iso/boot/$(JLOS).bin
@echo 'set timeout=0' > iso/boot/grub/grub.cfg
@echo 'set default=0' >> iso/boot/grub/grub.cfg
@echo '' >> iso/boot/grub/grub.cfg
@echo 'menuentry "johnlove operating system" {' >> iso/boot/grub/grub.cfg
@echo ' multiboot /boot/$(JLOS).bin' >> iso/boot/grub/grub.cfg
@echo ' boot' >> iso/boot/grub/grub.cfg
@echo '}' >> iso/boot/grub/grub.cfg
@grub-mkrescue --output=$@ iso
@rm -rf iso
3.2.2. 我们需要.ld链接脚本来定义内核文件链接规则,包括段配置,虚拟地址边界,输出.bin文件的架构和格式,以及引导程序加载内核iso后,第一条cpu指令执行地址名称。比如:
ENTRY(loader)
OUTPUT_FORMAT(elf32-i386)
OUTPUT_ARCH(i386:i386)
这里我们指定了入口地址名是loader,链接器输出.bin的格式是32位x86 ELF,架构是i386。
3.2.3. 然后我们需要一个.s文件来定义loader入口,比如:
.global loader
.type loader, @function
loader:
movl $boot_stack_top, %esp
cmpl $MULTIBOOT_BOOTLOADER_MAGIC, %eax
jne _stop_bad_magic
/* 保存 mbinfo 物理地址和 magic 到 .boot 段全局变量 */
movl %ebx, boot_mbinfo_pa
movl %eax, boot_magic
/* 启动页表: 4MB 大页, 恒等 + 高半核各映射 64MB */
movl $0x00, %ecx
movl $0x83, %eax
1:
movl %eax, boot_page_dir(, %ecx, 4)
movl %eax, boot_page_dir + (768 * 4)(, %ecx, 4)
addl $0x00400000, %eax
incl %ecx
cmpl $16, %ecx
jl 1b
/* 开分页: CR4.PSE → CR3 → CR0.PG → jmp 序列化 */
/* CR4: PSE(bit4,4MB页) | OSFXSR(bit9,启用FXSAVE/FXRSTOR/SSE) */
movl %cr4, %eax
orl $0x210, %eax
movl %eax, %cr4
movl $boot_page_dir, %eax
movl %eax, %cr3
movl %cr0, %eax
andl $~0x4, %eax
orl $0x80000022, %eax /* PG(bit 31) | MP(bit 1) | NE(bit 5) */
movl %eax, %cr0
jmp .Lpg_flush
.Lpg_flush:
lea high_half, %ecx
jmp *%ecx
_stop_bad_magic:
1: cli
hlt
jmp 1b
.size loader, . - loader
/* high_half: 链接在 .text, VMA = 0xC01xxxxx */
.section .text
.extern john_beauty_main
.extern _kernel_end
.extern _bss_start
.extern _bss_end
.type high_half, @function
high_half:
/* 从 .boot 段读取 multiboot 参数 (高半核映射: boot_mbinfo_pa PA → VA = PA + KERNEL_VIRTUAL_BASE) */
movl boot_mbinfo_pa, %ebx
movl boot_magic, %eax
/* 切永久内核栈 */
movl $kernel_stack, %esp
/* 清零 BSS (不影响 .boot 段的 boot_mbinfo_pa/boot_magic) */
movl $_bss_start, %edi
movl $_bss_end, %ecx
subl %edi, %ecx
xorl %eax, %eax
cld
rep stosb
/* mbinfo 物理地址 → 虚拟地址 */
movl boot_mbinfo_pa, %ebx
addl $0xC0000000, %ebx
pushl $_kernel_end
pushl %ebx
call john_beauty_main
1:
cli
hlt
jmp 1b
.size high_half, . - high_half
在这里,我们定义了loader符号,也就是cpu会在grub把内核加载到内存完成后从这里开始执行。包括校验内核是否遵照multiboot协议,存mb信息,建临时栈,开分页,没问题跳转到high_half继续执行。可以看到,在high_half中,我们让cpu跳转到了john_beauty_main这个内核入口函数。内核真正的逻辑入口处。
3.2.4. 然后,生成.bin后,我们就需要通过grub-mkrescue,来打包.bin。它会调用xorriso来制作grub iso镜像。这样,镜像就会被grub识别加载到内存。
3.2.5. 综上,可以看出,我们只需要使用grub来生成grub镜像,并在内核可执行文件中定义加载入口地址,就算是使用grub了。
3.3. 在知道怎么从引导程序加载到内核入口函数后,我们就需要编写内核入口函数了,比如:
#include <common/multiboot.h>
#include <hal/mmu.h>
#include <hal/context.h>
#include <hal/hal_arch.h>
#include <hal/paging.h>
#include <hal/smp.h>
#include <kernel/multitask.h>
#include <kernel/memory_manager.h>
#include <kernel/page_frame_allocator.h>
#include <kernel/device.h>
#include <kernel/initcall.h>
#define JLOS_KERNEL_LOG_SUBSYS "boot"
#include <kernel/printk.h>
void john_beauty_main(const multiboot_info_t *multiboot_structure, uint32_t kernel_end)
{
jlos_hal_arch_init();
jlos_printk_init();
printk_info("princess yihan is safe and happy!\n");
jlos_smp_init();
jlos_mmu_init();
jlos_arch_tss_init();
jlos_device_init(multiboot_structure);
jlos_pfa_boot_alloc_init(VIRT_TO_PHYS(kernel_end));
jlos_arch_paging_initialize_kernel(jlos_pfa_boot_alloc_page);
jlos_page_frame_allocator_init();
printk_info("paging initialized\n");
jlos_do_initcalls();
for (;;) {
jlos_hal_halt();
}
}
3.3.1. 从这个入口函数,我们就可以看出很多内核子系统初始化,包括hal, 打印,smp多核,mmu, tss, 设备管理,pfa, paging分页,以及initcall分级初始化。至于为什么有一行princess yihan is safe and happy呢,因为yihan是俺喜欢的女孩子喔。手搓内核嘛。
3.3.2. 基本到这里就可以了,后续就是内核子系统的实现了,那就已经进入内核开发了,包括:

4. 结束
4.1. 在计算机分支里,学习操作系统可以用到很多计算机知识。但是手搓内核是很耗时耗力的。所以呢,大学的时候不是正是有时间琢磨的时候。fighting。最后附上基本几个推荐书籍,网站,以及个人手搓内核项目(^v^):
《c程序设计语言》第2版,brian w. kernighan, dennis m. ritchie著,徐宝文,李志译。经典K&R
《数据结构与算法分析》c语言描述,第2版,mark allen weiss著,冯舜玺译。
https://wiki.osdev.org/Expanded_Main_Page
Linux Kernel Teaching — The Linux Kernel documentation
johnbeauty-os:基于 C++ 的操作系统项目 - AtomGit (俺手搓的,实际是基于c,没法改链接名称了)
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)