什么是操作系统?
操作系统是一种控制计算机系统及其资源运行的软件。说到底,一个操作系统在无依托的环境运行是任务重要的工作,因为上面所有的应用都在OS(操作系统简称),里面运行。
需要的必备知识?
初次接触操作系统的学习应该掌握不用太多的知识,现学现用和保持兴趣比一开始的完美更重要,所有直接开始这个不太困难的学习吧。
怎样的开发环境?
这个看你第一次接触是选择32位的、还是64位的操作系统开始写,需要提醒的是32位是一切开发操作系统的理解底层,虽然现在的64位操作系统是日常使用的主流。
GCC 交叉编译器?
交叉编译器是需要经常接触的朋友,在现在的编译已经由Makefile来组织,可还能使用build.sh这样的Shell脚本文件来使用,算是一个专业的组织、一个临时的运行。
接下来进入正式的学习
下载需要的编程环境
Windows、macOS(苹果电脑)、Linux(开源操作系统),WSL(Linux环境的Windows子系统),安装需要工具的麻烦程度不同

这个自己找AI去安装需要的开发工具,环境多样、命令复杂、习惯不同,就不浪费篇幅了。
引导程序是什么东西
[bits 16]
[org 0x7c00]
start:
mov ax, 0
mov es, ax
mov bx, 0x1000
mov ah, 0x02
mov al, 1
mov ch, 0
mov cl, 2
mov dh, 0
int 0x13
jc disk_error
cli
lgdt [gdt_desc]
mov eax, cr0
or eax, 1
mov cr0, eax
jmp 0x08:pm_entry
[bits 32]
pm_entry:
mov ax, 0x10
mov ds, ax
mov es, ax
mov ss, ax
mov esp, 0x7c00
mov edi, 0x2000
mov ecx, 1024
xor eax, eax
rep stosd
mov dword [0x2000], 0x3003
mov edi, 0x3000
mov ecx, 1024
xor eax, eax
rep stosd
mov ebx, 0x3000
mov eax, 0x00000003
mov ecx, 1024
fill_pt:
mov [ebx], eax
add ebx, 4
add eax, 0x1000
loop fill_pt
mov eax, 0x2000
mov cr3, eax
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
jmp 0x08:0x1000
disk_error:
cli
hlt
gdt_start:
dq 0
gdt_code:
dw 0xffff
dw 0
db 0
db 0x9a
db 0xcf
db 0
gdt_data:
dw 0xffff
dw 0
db 0
db 0x92
db 0xcf
db 0
gdt_end:
gdt_desc:
dw gdt_end – gdt_start – 1
dd gdt_start
times 510 – ($-$$) db 0
dw 0xaa55

语法讲解(NASM 汇编,Intel 风格)
1. 方括号开头的三条是 "编译指示"(伪指令),不是 CPU 指令
- [bits 16] / [bits 32]:告诉 NASM 生成 16 位还是 32 位机器码。代码前半段是实模式(16 位),后半段切到保护模式(32 位),所以一文件两段
- [org 0x7c00]:声明代码的加载地址。BIOS 约定把引导扇区放到 0x7C00,NASM 依据它计算所有标号的绝对地址
2. 标号(label) start:、disk_error:、gdt_start:、pm_entry: 都是地址符号,供 jmp、loop、jc 定位。pm_entry 前面没有分隔指令,靠 jmp 0x08:pm_entry 这个远跳转直接跨过去。
3. 数据定义伪指令
- db/dw/dd/dq:依次定义 1/2/4/8 字节数据。GDT 的三个描述符就是用手写的 8 字节拼出来的
- times 510 – ($-$$) db 0:填充语句。$ = 当前位置,$$ = 本段起始,$-$$ = 已写入字节数,整体意思是 "把扇区填满到 510 字节"
- dw 0xaa55:引导扇区魔数(55 AA),BIOS 靠它确认这是个可启动盘
4. GDT 手工构造 gdt_start: dq 0(空描述符)+ gdt_code + gdt_data。lgdt [gdt_desc] 把 GDTR 加载。两个描述符都是扁平模型(base=0,limit=4GB),代码段类型 0x9A、数据段 0x92。0x08 是代码段选择子(index 1,RPL=0),0x10 是数据段选择子(index 2)。
5. 关键指令
表格
| int 0x13(AH=0x02) | BIOS 磁盘服务:读扇区。AL = 数量、CH/CL = 柱面 / 扇区、DH = 磁头、ES:BX = 目标地址 → 读 loader.bin 到 0x1000 |
| jc disk_error | 进位标志 CF=1(读取失败)则跳转 |
| cli / lgdt | 关中断 / 加载全局描述符表 |
| mov eax,cr0; or eax,1; mov cr0,eax | 读 – 改 – 写 CR0,置 PE 位 = 进入保护模式 |
| rep stosd | 把 EAX 反复写入 ES:EDI(每次 +4,ECX 递减到 0),用于快速清零 4KB 区域 |
| mov dword [0x2000], 0x3003 | 页目录第 0 项 = 页表地址 0x3000 | 0x3(Present + 可写) |
| loop fill_pt | ECX-1,不为 0 则跳回,循环填 1024 个页表项,每项映射一页 4KB,共 4MB(恒等映射) |
| mov cr3, eax / or eax, 0x80000000 | 载入页目录基址 / 置 CR0 的 PG 位 = 开启分页 |
属于什么编程风格
Intel 语法 + NASM 方言 操作数顺序是 "目的在前、源在后"(mov ds, ax),这是 Intel 语法;与 GNU 汇编的 AT&T 语法(movl %ax, %ds)正好相反。[org]、times、$/$$ 这些是 NASM 特有的写法。
要怎么能打印 "颜色字符"?
void kernel_main() {
char *video_memory = (char*) 0xb8000;
video_memory[0] = 'H';
video_memory[1] = 0x0e;
video_memory[2] = 'e';
video_memory[3] = 0x0e;
video_memory[4] = 'l';
video_memory[5] = 0x0e;
video_memory[6] = 'l';
video_memory[7] = 0x0e;
video_memory[8] = 'o';
video_memory[9] = 0x0e;
video_memory[10] = ' ';
video_memory[11] = 0x0e;
video_memory[12] = 'W';
video_memory[13] = 0x0e;
video_memory[14] = 'o';
video_memory[15] = 0x0e;
video_memory[16] = 'r';
video_memory[17] = 0x0e;
video_memory[18] = 'l';
video_memory[19] = 0x0e;
video_memory[20] = 'd';
video_memory[21] = 0x0e;
video_memory[22] = '!';
video_memory[23] = 0x0e;
while(1);
}
ENTRY(kernel_main)
SECTIONS {
. = 0x1000;
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
}

为什么能打印 "颜色字符"?
因为 0xB8000 是 VGA 文本模式显存,而写的是 "字符 + 颜色属性" 对,不是普通字符串。
- 低字节(video_memory[0])= 字符的 ASCII 码('H' = 0x48)
- 高字节(video_memory[1])= 颜色属性(0x0E)
- 0x0E = 0000 1110 → 前景 1110(14 = 亮黄)、背景 000(黑)→ 黑底黄字
这就是裸机编程里的 "最原始输出":没有 printf、没有系统调用,直接写显存。改一下属性字节(比如 0x1e、0x07),字色就变。
这个链接脚本的作用是什么?
它的作用是:告诉链接器 "代码放在哪个物理地址、段怎么排、入口在哪"—— 因为裸机内核没有操作系统来帮你加载。
怎么编译运行?
nasm -f bin entry.asm -o entry.bin
gcc -m32 -ffreestanding -fno-pic -c loader.c -o loader.o
ld -m elf_i386 -Ttext 0x1000 –oformat binary -o loader.bin loader.o
dd if=/dev/zero of=os.img bs=512 count=100
dd if=entry.bin of=os.img bs=512 count=1 conv=notrunc
dd if=loader.bin of=os.img bs=512 seek=1 conv=notrunc
qemu-system-x86_64 -drive format=raw,file=os.img

编译的运行结果







