基于 Free Pascal 从零编写裸机操作系统(一)

发布时间:2026/8/1 3:41:37
基于 Free Pascal 从零编写裸机操作系统(一) 前言绝大多数入门操作系统教程都会选用 C 语言很少有人尝试用 Pascal 开发裸内核。OSDev 官网提供了一套完整的 Free Pascal Bare Bones 最小操作系统示例无需依赖 DOS、Linux、Windows 标准库完全运行在 32 位保护模式下兼容 Multiboot 引导规范能直接被 GRUB、QEMU 加载。本文仅拆解全部源码汇编入口、控制台驱动、内核主程序、Multiboot 规范定义编译、链接、镜像制作、QEMU 运行脚本不在本文介绍范围。 参考原文https://wiki.osdev.org/Pascal_Bare_Bones前置说明编译器Free PascalFPC嵌入式裸机编译目标架构x86 32 位保护模式显示VGA 文本缓冲区0xB8000引导Multiboot v1 标准兼容 GRUB无标准库不能使用WriteLn、MemAlloc等系统相关函数所有 IO、内存读写手动实现一、汇编入口文件stub.asm作用声明 Multiboot 头部让 GRUB 识别这是合法内核镜像切换 32 位保护模式环境设置栈导出符号供 Pascal 调用跳转至 Pascal 内核主函数MainKernelnasm;///////////////////////////////////////////////////////// ;// // ;// Freepascal barebone OS // ;// stub.asm // ;// // ;///////////////////////////////////////////////////////// ;// ;// By: De Deyn Kim kimdedeynskynet.be ;// License: Public domain ;// ; ; Kernel stub ; ; ; We are in 32bits protected mode ; [bits 32] ; ; Export entrypoint ; [global kstart] ; ; Import kernel entrypoint ; [extern kmain] ; ; Posible multiboot header flags ; MULTIBOOT_MODULE_ALIGN equ 10 MULTIBOOT_MEMORY_MAP equ 11 MULTIBOOT_GRAPHICS_FIELDS equ 12 MULTIBOOT_ADDRESS_FIELDS equ 116 ; ; Multiboot header defines ; MULTIBOOT_HEADER_MAGIC equ 0x1BADB002 MULTIBOOT_HEADER_FLAGS equ MULTIBOOT_MODULE_ALIGN | MULTIBOOT_MEMORY_MAP MULTIBOOT_HEADER_CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC MULTIBOOT_HEADER_FLAGS) ; ; Kernel stack size ; KERNEL_STACKSIZE equ 0x4000 section .text ; ; Multiboot header ; align 4 dd MULTIBOOT_HEADER_MAGIC dd MULTIBOOT_HEADER_FLAGS dd MULTIBOOT_HEADER_CHECKSUM ; ; Entrypoint ; kstart: mov esp, KERNEL_STACKKERNEL_STACKSIZE ;Create kernel stack push eax ;Multiboot magic number push ebx ;Multiboot info call kmain ;Call kernel entrypoint cli ;Clear interrupts hlt ;Halt machine section .bss ; ; Kernel stack location ; align 32 KERNEL_STACK: resb KERNEL_STACKSIZE源码关键点解读Multiboot 头三行 magic/flags/checksum 是 GRUB 识别内核的核心位置不能偏移.bss段预留 16KB 静态栈裸机无动态内存分配栈必须静态声明GRUB 会把内存、磁盘等硬件信息结构体地址存入ebx入栈传递给 Pascal 内核内核执行完毕后死循环停机防止指令越界。二、VGA 控制台驱动单元console.pas作用封装底层 VGA 文本缓冲区操作实现清屏、打印字符、字符串、数字、十六进制是内核唯一输出接口对外导出可被汇编 / 内核主程序调用的过程。pascal{ ///////////////////////////////////////////////////////// // // // Freepascal barebone OS // // console.pas // // // ///////////////////////////////////////////////////////// // // By: De Deyn Kim kimdedeynskynet.be // License: Public domain // } unit console; interface var xpos: Integer 0; ypos: Integer 0; procedure kclearscreen(); procedure kwritechr(c: Char); procedure kwritestr(s: PChar); procedure kwriteint(i: Integer); procedure kwritedword(i: DWORD); implementation var vidmem: PChar PChar($b8000); procedure kclearscreen(); [public, alias: kclearscreen]; var i: Integer; begin for i : 0 to 3999 do vidmem[i] : #0; end; procedure kwritechr(c: Char); [public, alias: kwritechr]; var offset: Integer; begin if (ypos 24) then ypos : 0; if (xpos 79) then xpos : 0; offset : (xpos shl 1) (ypos * 160); vidmem[offset] : c; offset 1; vidmem[offset] : #7; offset 1; xpos : (offset mod 160); ypos : (offset - xpos) div 160; xpos : xpos shr 1; end; procedure kwritestr(s: PChar); [public, alias: kwritestr]; var offset, i: Integer; begin if (ypos 24) then ypos : 0; if (xpos 79) then xpos : 0; offset : (xpos shl 1) (ypos * 160); i : 0; while (s[i] Char($0)) do begin vidmem[offset] : s[i]; offset 1; vidmem[offset] : #7; offset 1; i 1; end; xpos : (offset mod 160); ypos : (offset - xpos) div 160; xpos : xpos shr 1; end; procedure kwriteint(i: Integer); [public, alias: kwriteint]; var buffer: array [0..11] of Char; str: PChar; digit: DWORD; minus: Boolean; begin str : buffer[11]; str^ : #0; if (i 0) then begin digit : -i; minus : True; end else begin digit : i; minus : False; end; repeat Dec(str); str^ : Char((digit mod 10) Byte(0)); digit : digit div 10; until (digit 0); if (minus) then begin Dec(str); str^ : -; end; kwritestr(str); end; procedure kwritedword(i: DWORD); [public, alias: kwritedword]; var buffer: array [0..11] of Char; str: PChar; digit: DWORD; begin for digit : 0 to 10 do buffer[digit] : 0; str : buffer[11]; str^ : #0; digit : i; repeat Dec(str); str^ : Char((digit mod 10) Byte(0)); digit : digit div 10; until (digit 0); kwritestr(str); end; end.源码关键点解读VGA_BUFFER_ADDR $B8000是 x86 实模式 / 保护模式通用文本显存地址无需 BIOS 中断每个字符占用 2 字节低字节 ASCII高字节颜色属性内置自动滚动逻辑打印超出 25 行时整体上移一行清空底部PChar兼容 C 字符串0 终止符方便和汇编交互数字打印不依赖标准库SysUtils手动实现进制转换缓冲区。三、Multiboot 规范常量定义multiboot.pas作用定义 Multiboot v1 标准的内存信息结构体、魔数、标志位内核主程序读取 GRUB 传入的硬件信息时使用。pascalunit multiboot; interface const KERNEL_STACKSIZE $4000; MULTIBOOT_BOOTLOADER_MAGIC $2BADB002; type Pelf_section_header_table_t ^elf_section_header_table_t; elf_section_header_table_t packed record num: DWORD; size: DWORD; addr: DWORD; shndx: DWORD; end; Pmultiboot_info_t ^multiboot_info_t; multiboot_info_t packed record flags: DWORD; mem_lower: DWORD; { Amount of memory available below 1mb } mem_upper: DWORD; { Amount of memory available above 1mb } boot_device: DWORD; cmdline: DWORD; mods_count: DWORD; mods_addr: DWORD; elf_sec: elf_section_header_table_t; mmap_length: DWORD; mmap_addr: DWORD; end; Pmodule_t ^module_t; module_t packed record mod_start: DWORD; mod_end: DWORD; name: DWORD; reserved: DWORD; end; Pmemory_map_t ^memory_map_t; memory_map_t packed record size: DWORD; { You can declare these two as a single qword if your compiler supports it } base_addr_low: DWORD; base_addr_high: DWORD; { And again, these can be made into one qword variable. } length_low: DWORD; length_high: DWORD; mtype: DWORD; end; implementation end.源码关键点解读packed record禁用内存对齐严格匹配 Multiboot 二进制布局mem_lower/mem_upper是本示例核心用于计算整机物理内存大小结构体指针由汇编kernel_entry.asm中ebx传入内核主程序。四、内核主程序kernel.pas作用程序入口MainKernel整合控制台、Multiboot 模块完成初始化、硬件信息打印、停机逻辑是整个操作系统顶层业务代码。pascal{ ///////////////////////////////////////////////////////// // // // Freepascal barebone OS // // kernel.pas // // // ///////////////////////////////////////////////////////// // // By: De Deyn Kim kimdedeynskynet.be // License: Public domain // } unit kernel; interface uses multiboot, console; procedure kmain(mbinfo: Pmultiboot_info_t; mbmagic: DWORD); stdcall; implementation procedure kmain(mbinfo: Pmultiboot_info_t; mbmagic: DWORD); stdcall; [public, alias: kmain]; begin kclearscreen(); kwritestr(Freepascal barebone OS booted!); xpos : 0; ypos 1; if (mbmagic MULTIBOOT_BOOTLOADER_MAGIC) then begin kwritestr(Halting system, a multiboot-compliant boot loader needed!); asm cli hlt end; end else begin kwritestr(Booted by a multiboot-compliant boot loader!); xpos : 0; ypos 2; kwritestr(Multiboot information:); xpos : 0; ypos 2; kwritestr( Lower memory ); kwriteint(mbinfo^.mem_lower); kwritestr(KB); xpos : 0; ypos 1; kwritestr( Higher memory ); kwriteint(mbinfo^.mem_upper); kwritestr(KB); xpos : 0; ypos 1; kwritestr( Total memory ); kwriteint(((mbinfo^.mem_upper 1000) div 1024) 1); kwritestr(MB); end; asm loop: jmp loop end; end; end.源码关键点解读MainKernel带[public]导出汇编call MainKernel可直接调用入参PMultibootInfo接收 GRUB 传入的硬件信息指针先校验 Multiboot 魔数防止非 GRUB 引导导致内存读取崩溃纯汇编内嵌clihlt关闭中断停机裸机无操作系统异常处理末尾默认begin..end是 FPC 标准程序占位裸机环境不会触发执行。五、整体源码结构梳理plaintextPascal-BareBones/ ├─ kernel_entry.asm 汇编引导入口Multiboot头 ├─ multiboot.pas Multiboot硬件信息结构体定义 ├─ console.pas VGA控制台底层驱动单元 └─ kernel.pas 内核主程序系统业务逻辑六、源码运行逻辑完整流程GRUB 加载镜像识别.multiboot头部把硬件信息存入 EBX执行kernel_entry.asm初始化栈压入 EBX 指针调用MainKernelPascal 内核执行清屏 → 校验 Multiboot 合法性 → 打印内存硬件信息 → 死循环停机所有屏幕输出均调用console.pas封装的 VGA 显存操作无任何 BIOS/DOS 系统调用。七、拓展改造方向基于现有源码完善 VGA 颜色属性实现彩色文字输出增加键盘中断 IRQ1 驱动实现字符输入新增简单堆内存管理器替代静态变量扩展 Multiboot 模块读取磁盘、模块加载信息增加简单多任务调度框架基于 Pascal 过程实现线程切换。结尾这套源码完整实现了最小可用 Pascal 裸内核全程不依赖任何操作系统标准库所有硬件交互代码自主可控。相比 C 语言 OS 教程Pascal 强类型、单元模块化的特性让底层硬件结构体、驱动代码可读性更高。下一篇博文我会讲解编译脚本、链接脚本、GRUB 镜像制作与 QEMU 运行完整流程。