从零打造你的第一个操作系统内核:mkernel极简实现全指南

从零打造你的第一个操作系统内核:mkernel极简实现全指南

【免费下载链接】mkernel A minimalist kernel 【免费下载链接】mkernel 项目地址: https://gitcode.com/gh_mirrors/mk/mkernel

你是否曾好奇操作系统内核(Kernel)如何启动?想亲手编写能在真实硬件上运行的代码却被复杂概念吓退?本文将带你通过mkernel项目(一个仅200行代码的极简内核),从0到1掌握内核开发核心原理,最终让你的代码在计算机启动时显示自定义信息。

读完本文你将掌握:

  • 内核启动的完整流程(从BIOS到GRUB再到内核执行)
  • 实模式与保护模式的切换原理
  • 32位汇编与C语言的混合编程技术
  • 显存操作与字符显示的底层实现
  • 内核编译、链接与GRUB配置全步骤
  • 在QEMU模拟器与真实硬件中测试内核的方法

项目概述:mkernel是什么?

mkernel是一个遵循Multiboot规范的极简内核(Kernel)实现,核心功能是在屏幕上打印"my first kernel"并挂起系统。其代码量不足200行,却完整展示了操作系统内核的启动流程和最基础的硬件交互方式。

mermaid

核心特性与技术栈

特性说明技术细节
代码规模汇编30行 + C语言80行无外部依赖,纯手工编写
启动方式GRUB Multiboot兼容符合Multiboot v1规范
目标架构x86 32位支持i386及兼容处理器
显示输出VGA文本模式直接操作0xB8000显存地址
编译工具链NASM + GCC + LD生成ELF32格式可执行文件

内核启动流程深度解析

从加电到内核执行的四阶段模型

mermaid

关键技术点:实模式到保护模式的切换

x86处理器启动后默认工作在实模式(Real Mode),只能访问1MB内存。GRUB负责完成到保护模式(Protected Mode)的切换,使处理器能够访问4GB内存空间并启用内存分页机制。

mermaid

代码实现详解:汇编与C的协同工作

1. 汇编入口文件(kernel.asm)

汇编代码负责最底层的初始化工作,是连接GRUB与C语言内核的桥梁。

; kernel.asm - 内核启动入口点
bits 32                     ; 告诉NASM生成32位代码
section .text               ; 代码段开始

; Multiboot规范要求的魔术数和校验和
align 4
dd 0x1BADB002               ; Multiboot魔术数(Magic Number)
dd 0x00                     ; 标志位(Flags)
dd - (0x1BADB002 + 0x00)    ; 校验和(Checksum) = -(魔术数 + 标志位)

global start                ; 导出start符号,供链接器使用
extern kmain                ; 声明外部函数kmain(在kernel.c中定义)

start:                      ; 内核执行的第一个指令
    cli                     ; 关闭中断(CLI = Clear Interrupts)
    mov esp, stack_space    ; 设置栈指针(ESP)到栈空间顶部
    call kmain              ; 调用C语言内核主函数
    hlt                     ; 当kmain返回后挂起CPU(HLT = Halt)

section .bss                ; 未初始化数据段
resb 8192                   ; 预留8KB内存作为栈空间
stack_space:                ; 栈空间顶部地址(栈向下生长)
核心功能解析:
  • Multiboot头部:前12字节是GRUB识别内核的必要信息,必须放在代码段开始
  • 栈初始化mov esp, stack_space设置栈指针,为C函数调用准备栈空间
  • 中断控制cli指令关闭中断,防止内核执行时被外部中断干扰
  • 系统挂起hlt指令使CPU进入暂停状态,减少功耗

2. C语言内核实现(kernel.c)

C语言部分负责实现具体功能——清屏和显示字符串,展示了内核如何直接操作硬件。

/* kernel.c - mkernel主功能实现 */
void kmain(void) {
    const char *str = "my first kernel";  // 要显示的字符串
    char *vidptr = (char*)0xb8000;        // 文本模式显存起始地址
    unsigned int i = 0;                   // 显存偏移量
    unsigned int j = 0;                   // 字符串索引
    unsigned int screensize = 80 * 25 * 2; // 屏幕总字节数(80列×25行×2字节/字符)

    /* 第一步:清屏操作 */
    while (j < screensize) {
        vidptr[j] = ' ';         // 空格字符(ASCII 32)
        vidptr[j+1] = 0x07;      // 属性字节(黑底0x0 + 灰字0x7)
        j += 2;                  // 移动到下一个字符位置
    }

    /* 第二步:显示字符串 */
    j = 0;  // 重置字符串索引
    while (str[j] != '\0') {
        vidptr[i] = str[j];      // 字符ASCII值
        vidptr[i+1] = 0x07;      // 保持黑底灰字属性
        j++;                     // 下一个字符
        i += 2;                  // 显存地址+2(字符占2字节)
    }

    return;
}
显存操作核心原理:

VGA(Video Graphics Array)文本模式下,显存(Video Memory)从地址0xB8000开始,每个字符占用2字节:

  • 低字节:字符的ASCII码
  • 高字节:属性值(背景色4位 + 前景色4位)

mermaid

常见属性值组合:

  • 0x07:黑底灰字(默认)
  • 0x0F:黑底白字(高亮)
  • 0x70:灰底黑字(反显)
  • 0x42:红底绿字(错误提示)

3. 链接脚本(link.ld)

链接脚本控制链接器如何组织最终的可执行文件,指定内核加载地址和各段布局。

OUTPUT_FORMAT(elf32-i386)  ; 输出ELF32格式
ENTRY(start)               ; 程序入口点为start符号
SECTIONS {
    . = 0x100000;          ; 内核加载地址(1MB处)
    .text : { *(.text) }   ; 代码段:包含所有.text节
    .data : { *(.data) }   ; 数据段:包含所有.data节
    .bss  : { *(.bss)  }   ; BSS段:未初始化数据
}

为什么内核从0x100000(1MB)开始加载?

  • 低端内存(0x00000~0xFFFFF)被BIOS、GRUB和各种硬件占用
  • 1MB以上内存区域通常对内核可用
  • 符合Multiboot规范的推荐加载地址

编译与测试全流程

开发环境准备

工具作用安装命令(Ubuntu)
NASM汇编器,编译.asm文件sudo apt install nasm
GCCC编译器,生成32位目标文件sudo apt install gcc-multilib
LD链接器,组合目标文件通常随GCC安装
QEMU模拟器,测试内核sudo apt install qemu-system-i386
GRUB引导加载程序sudo apt install grub2

编译步骤详解

第一步:汇编编译
nasm -f elf32 kernel.asm -o kasm.o
  • -f elf32:指定输出格式为32位ELF目标文件
  • kernel.asm:输入汇编源文件
  • -o kasm.o:输出目标文件名(Object File)
第二步:C代码编译
gcc -m32 -c kernel.c -o kc.o -ffreestanding -nostdlib

关键参数解析:

  • -m32:生成32位代码
  • -c:只编译不链接
  • -ffreestanding:告诉GCC这是独立环境程序(无libc支持)
  • -nostdlib:不链接标准库

⚠️ 注意:内核开发不能使用标准C库(如printf、malloc等),因为这些函数依赖操作系统提供的系统调用,而内核本身就是提供这些功能的程序。

第三步:链接生成内核
ld -m elf_i386 -T link.ld -o kernel kasm.o kc.o
  • -m elf_i386:指定链接器生成32位ELF格式
  • -T link.ld:使用自定义链接脚本
  • kasm.o kc.o:输入目标文件(顺序无关)
  • -o kernel:输出内核文件名

模拟器测试

使用QEMU(Quick Emulator)测试内核,无需写入硬盘:

qemu-system-i386 -kernel kernel

成功运行后将看到:

  • QEMU窗口中显示"my first kernel"
  • 屏幕其余区域为黑色背景
  • 系统无进一步响应(内核执行完后挂起)

真实硬件部署指南

GRUB配置详解

GRUB(Grand Unified Bootloader)是大多数Linux发行版使用的引导加载程序,需要正确配置才能启动自定义内核。

1. 重命名内核文件

GRUB要求内核文件名为kernel-<version>格式:

mv kernel kernel-701  # 701可替换为任意版本号
2. 复制到启动分区
sudo cp kernel-701 /boot/kernel-701  # 需要root权限
3. 配置GRUB菜单项

编辑/etc/grub.d/40_custom文件,添加以下内容:

menuentry 'myKernel' {
    set root='(hd0,msdos1)'  # 启动分区(根据实际情况修改)
    multiboot /boot/kernel-701 ro  # 加载内核,ro表示只读
}

⚠️ 注意:set root的值需要根据你的磁盘分区情况调整:

  • hd0表示第一块硬盘
  • msdos1表示MBR分区表的第一个主分区
  • 如果使用GPT分区表,应为gpt1
4. 更新GRUB配置
sudo update-grub  # 更新/boot/grub/grub.cfg文件
5. 重启系统
sudo reboot

启动时选择"myKernel"菜单项,即可看到你的内核运行!

常见问题与解决方案

编译错误排查

错误信息可能原因解决方案
undefined reference to 'kmain'汇编未正确声明extern kmain检查kernel.asm中是否有extern kmain
ld: i386 architecture required未指定32位模式编译时添加-m32参数
error: unknown type name 'uint32_t'使用了标准库类型替换为基本类型或手动定义

启动失败处理

如果系统无法启动,可通过以下方法恢复:

  1. 重启并按住Shift键,进入GRUB菜单
  2. 选择原有操作系统(如Ubuntu)
  3. 检查/修正GRUB配置文件
  4. 重新运行sudo update-grub

调试技巧

  1. 使用QEMU调试
qemu-system-i386 -s -S -kernel kernel  # 启动GDB服务器
gdb -ex "target remote localhost:1234"  # 连接调试器
  1. 打印调试信息: 修改kernel.c,在显存不同位置显示状态信息:
// 在屏幕右上角显示"DBG"
vidptr[80*2*2] = 'D'; vidptr[80*2*2+1] = 0x0F;
vidptr[80*2*2+2] = 'B'; vidptr[80*2*2+3] = 0x0F;
vidptr[80*2*2+4] = 'G'; vidptr[80*2*2+5] = 0x0F;

进阶方向与学习路径

mkernel只是内核开发的起点,以下是值得探索的进阶方向:

mermaid

推荐学习资源

  1. 官方文档

  2. 扩展项目

总结与下一步

通过本文,你已经掌握了:

  • 内核启动流程:从BIOS到GRUB再到内核执行的完整链路
  • 汇编与C混合编程:如何用汇编处理底层初始化,用C实现业务逻辑
  • 显存操作:直接访问硬件地址实现字符显示
  • 内核编译与部署:从源代码到在真实硬件上运行的全过程

下一步行动建议

  1. 修改显示字符串,让内核显示你的名字
  2. 尝试不同的属性字节值,改变文字颜色和背景
  3. 实现滚动显示或居中对齐文本
  4. 添加简单的键盘输入响应功能

希望本文能点燃你对操作系统开发的兴趣!如果觉得有帮助,请点赞收藏本文,并关注后续的内核高级特性讲解。下一篇我们将实现键盘中断处理和简单的shell交互,敬请期待!

【免费下载链接】mkernel A minimalist kernel 【免费下载链接】mkernel 项目地址: https://gitcode.com/gh_mirrors/mk/mkernel

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值