前言

绝大多数入门操作系统教程都会选用 C 语言,很少有人尝试用 Pascal 开发裸内核。OSDev 官网提供了一套完整的 Free Pascal Bare Bones 最小操作系统示例,无需依赖 DOS、Linux、Windows 标准库,完全运行在 32 位保护模式下,兼容 Multiboot 引导规范,能直接被 GRUB、QEMU 加载。

本文仅拆解全部源码(汇编入口、控制台驱动、内核主程序、Multiboot 规范定义),编译、链接、镜像制作、QEMU 运行脚本不在本文介绍范围。 参考原文:https://wiki.osdev.org/Pascal_Bare_Bones

前置说明

  1. 编译器:Free Pascal(FPC),嵌入式裸机编译目标
  2. 架构:x86 32 位保护模式
  3. 显示:VGA 文本缓冲区 0xB8000
  4. 引导:Multiboot v1 标准,兼容 GRUB
  5. 无标准库:不能使用WriteLnMemAlloc等系统相关函数,所有 IO、内存读写手动实现

一、汇编入口文件:stub.asm

作用

  1. 声明 Multiboot 头部,让 GRUB 识别这是合法内核镜像
  2. 切换 32 位保护模式环境,设置栈
  3. 导出符号供 Pascal 调用,跳转至 Pascal 内核主函数MainKernel

nasm

;/////////////////////////////////////////////////////////
;//                                                     //
;//               Freepascal barebone OS                //
;//                      stub.asm                       //
;//                                                     //
;/////////////////////////////////////////////////////////
;//
;//     By:             De Deyn Kim <kimdedeyn@skynet.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     1<<0
MULTIBOOT_MEMORY_MAP            equ     1<<1
MULTIBOOT_GRAPHICS_FIELDS       equ     1<<2
MULTIBOOT_ADDRESS_FIELDS        equ     1<<16

;
; 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_STACK+KERNEL_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

源码关键点解读

  1. Multiboot 头三行 magic/flags/checksum 是 GRUB 识别内核的核心,位置不能偏移;
  2. .bss段预留 16KB 静态栈,裸机无动态内存分配,栈必须静态声明;
  3. GRUB 会把内存、磁盘等硬件信息结构体地址存入ebx,入栈传递给 Pascal 内核;
  4. 内核执行完毕后死循环停机,防止指令越界。

二、VGA 控制台驱动单元:console.pas

作用

封装底层 VGA 文本缓冲区操作,实现清屏、打印字符、字符串、数字、十六进制,是内核唯一输出接口,对外导出可被汇编 / 内核主程序调用的过程。

pascal

{
/////////////////////////////////////////////////////////
//                                                     //
//               Freepascal barebone OS                //
//                       console.pas                   //
//                                                     //
/////////////////////////////////////////////////////////
//
//      By:             De Deyn Kim <kimdedeyn@skynet.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.

源码关键点解读

  1. VGA_BUFFER_ADDR = $B8000 是 x86 实模式 / 保护模式通用文本显存地址,无需 BIOS 中断;
  2. 每个字符占用 2 字节:低字节 ASCII,高字节颜色属性;
  3. 内置自动滚动逻辑:打印超出 25 行时整体上移一行,清空底部;
  4. PChar 兼容 C 字符串(0 终止符),方便和汇编交互;
  5. 数字打印不依赖标准库SysUtils,手动实现进制转换缓冲区。

三、Multiboot 规范常量定义:multiboot.pas

作用

定义 Multiboot v1 标准的内存信息结构体、魔数、标志位,内核主程序读取 GRUB 传入的硬件信息时使用。

pascal

unit 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.

源码关键点解读

  1. packed record 禁用内存对齐,严格匹配 Multiboot 二进制布局;
  2. mem_lower/mem_upper 是本示例核心,用于计算整机物理内存大小;
  3. 结构体指针由汇编kernel_entry.asmebx传入内核主程序。

四、内核主程序:kernel.pas

作用

程序入口MainKernel,整合控制台、Multiboot 模块,完成初始化、硬件信息打印、停机逻辑,是整个操作系统顶层业务代码。

pascal

{
/////////////////////////////////////////////////////////
//                                                     //
//               Freepascal barebone OS                //
//                      kernel.pas                     //
//                                                     //
/////////////////////////////////////////////////////////
//
//      By:             De Deyn Kim <kimdedeyn@skynet.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.

源码关键点解读

  1. MainKernel[public]导出,汇编call MainKernel可直接调用;
  2. 入参PMultibootInfo接收 GRUB 传入的硬件信息指针;
  3. 先校验 Multiboot 魔数,防止非 GRUB 引导导致内存读取崩溃;
  4. 纯汇编内嵌cli+hlt关闭中断停机,裸机无操作系统异常处理;
  5. 末尾默认begin..end是 FPC 标准程序占位,裸机环境不会触发执行。

五、整体源码结构梳理

plaintext

Pascal-BareBones/
├─ kernel_entry.asm   汇编引导入口+Multiboot头
├─ multiboot.pas      Multiboot硬件信息结构体定义
├─ console.pas        VGA控制台底层驱动单元
└─ kernel.pas         内核主程序,系统业务逻辑

六、源码运行逻辑完整流程

  1. GRUB 加载镜像,识别.multiboot头部,把硬件信息存入 EBX;
  2. 执行kernel_entry.asm,初始化栈,压入 EBX 指针,调用MainKernel
  3. Pascal 内核执行:清屏 → 校验 Multiboot 合法性 → 打印内存硬件信息 → 死循环停机;
  4. 所有屏幕输出均调用console.pas封装的 VGA 显存操作,无任何 BIOS/DOS 系统调用。

七、拓展改造方向(基于现有源码)

  1. 完善 VGA 颜色属性,实现彩色文字输出;
  2. 增加键盘中断 IRQ1 驱动,实现字符输入;
  3. 新增简单堆内存管理器,替代静态变量;
  4. 扩展 Multiboot 模块,读取磁盘、模块加载信息;
  5. 增加简单多任务调度框架,基于 Pascal 过程实现线程切换。

结尾

这套源码完整实现了最小可用 Pascal 裸内核,全程不依赖任何操作系统标准库,所有硬件交互代码自主可控。相比 C 语言 OS 教程,Pascal 强类型、单元模块化的特性让底层硬件结构体、驱动代码可读性更高。下一篇博文我会讲解编译脚本、链接脚本、GRUB 镜像制作与 QEMU 运行完整流程。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐