第一阶段:夯实基石 —— 数据在内存中的真实面貌
OS是管硬件的,你必须清楚C语言的每一种数据在内存里到底占多少空间。
- 1. 基础数据类型的尺寸与对齐
- 知识点:
char(1字节)、short(2字节)、int(4字节)、long/指针(在32位系统是4字节,在64位系统是8字节,这个极其重要)。 - OS关联:OS在分配内存页(通常4KB)、划分内核数据结构时,对字节数极其敏感。
- 知识点:
- 2. 整数的unsigned与signed本质
- 知识点:计算机底层没有符号之分,全是补码。
unsigned的溢出是回绕(比如0 - 1会变成极大的正数)。 - OS关联:内核中大量的计数器、时间戳、物理地址,全都是
unsigned,因为物理地址不可能为负数。
- 知识点:计算机底层没有符号之分,全是补码。
- 3. 枚举与宏常量
- 知识点:
enum的本质就是整型;#define的纯文本替换。 - OS关联:OS中充满了状态机。比如进程状态:
TASK_RUNNING = 0, TASK_INTERRUPTIBLE = 1。内核源码里极少用硬编码的数字,全是宏和枚举。
- 知识点:
第二阶段:灵魂核心 —— 指针与内存地址的绝对控制
上一节课我们入门了指针,为了看OS源码,你需要掌握指针的进阶形态。
- 1. 指针的类型与步长(指针运算)
- 知识点:
int *p + 1实际上地址加了4;char *p + 1地址加1。 - OS关联:遍历页目录表、进程描述符链表时,全靠指针加减法来移动。
- 知识点:
- 2.
void *泛型指针- 知识点:
void *可以接收任意类型的地址,但不能直接解引用(因为不知道它指向多大空间)。 - OS关联:内核最爱用的指针! 比如内核的
kmalloc()分配内存函数,返回的就是void *,因为内核分配时根本不管你要存int还是存结构体,只管给你一块纯净的内存地址。
- 知识点:
- 3. 函数指针
- 知识点:指向代码区(函数)的指针,可以通过指针调用函数。
- OS关联:这是OS实现“多态”和“回调”的唯一手段。比如Linux内核中的中断向量表、文件系统的VFS(虚拟文件系统),就是用一堆函数指针数组实现的(
struct file_operations里面全是函数指针)。
- 4. 指针与数组彻底等价
- 知识点:
a[i]完全等价于*(a + i)。 - OS关联:内核中绝对不会用数组下标去访问大块内存,全是用指针解引用,因为指针解引用生成的汇编代码更短、更快。
- 知识点:
第三阶段:捏泥巴 —— 手工组装数据结构(结构体进阶)
OS是由无数个庞大的结构体组成的(比如Linux的 task_struct 进程控制块有几百行)。
- 1. 结构体(
struct)的声明与指针引用- 知识点:用
->运算符通过指针访问结构体成员(p->name等价于(*p).name)。 - OS关联:内核中传递实体太浪费栈空间,传递结构体指针是唯一做法,所以源码里满天飞的都是
->。
- 知识点:用
- 2. 结构体内存对齐
- 知识点:编译器会在结构体内部塞入空白字节,让数据按其自身大小对齐(比如4字节的int必须放在4的倍数地址上)。
- OS关联:极其重要! 因为CPU读内存是按块读的,不对齐会导致性能下降甚至硬件异常。OS设计结构体必须精心排布成员顺序来节省内存。
- 3. 联合体(
union)- 知识点:所有成员共享同一块内存空间,同一时间只能用一个。
- OS关联:常用于“类型强转”的优雅替代。比如,想看一个32位整数的最高8位是什么,不用去移位,直接定义一个union包含
int和char[4],直接读char即可。
- 4. 位域
- 知识点:在结构体里,用冒号指定某个成员占几个 bit(如
unsigned int mode : 3;)。 - OS关联:硬件的寄存器控制字都是按位划分的(比如第0-1位控制电源状态,第3位控制中断)。OS通过位域直接映射硬件寄存器,极度节省空间。
- 知识点:在结构体里,用冒号指定某个成员占几个 bit(如
第四阶段:直击硬件 —— 位操作与底层修饰符
这是C语言能写OS的根本原因:C语言能直接操作二进制位,并能干预编译器的行为。
- 1. 纯熟掌握位操作符
- 知识点:
&(清零/屏蔽)、|(置位)、^(翻转)、~(取反)、<<(左移,相当于乘2)、>>(右移)。 - OS关联:每天的日常。比如要把一个寄存器的第3位置1:
reg |= (1 << 3);把第3位清0:reg &= ~(1 << 3)。操作系统的页表项(PTE)全是用位操作设置的。
- 知识点:
- 2.
volatile关键字(极其关键)- 知识点:告诉编译器:“这个变量的值可能会在你看不见的地方改变,绝对不准优化,每次必须老老实实去内存里重新读!”
- OS关联:
- 内存映射I/O(MMIO): 当OS要读写外设(如网卡、串口)时,外设的寄存器被映射到内存地址。OS读写这个地址,实际上是在跟物理硬件通信,硬件会自己改变里面的值,编译器不知道,不用
volatile就会读出脏数据。 - 多线程/中断: 中断服务程序可能会修改主程序里的变量,也必须加
volatile。
- 内存映射I/O(MMIO): 当OS要读写外设(如网卡、串口)时,外设的寄存器被映射到内存地址。OS读写这个地址,实际上是在跟物理硬件通信,硬件会自己改变里面的值,编译器不知道,不用
- 3. 内联汇编基础(了解即可)
- 知识点:在C代码中嵌入
asm volatile("...")。 - OS关联:有些操作C语言做不了,比如关中断、读写特定的控制寄存器(CR0, CR3),必须用汇编。OS中C和汇编是混合编写的。
- 知识点:在C代码中嵌入
第五阶段:内存的底层真相 —— 变量的生命周期
OS本身就是一个“内存分配器”,你必须知道C语言程序的内存是怎么划分的。
- 1. 全局变量/静态变量(
.data和.bss段)- 知识点:在程序编译时就确定了大小,存在于整个程序生命周期。
- OS关联:内核里的全局配置、驱动程序的全局状态,都放在这里。
- 2. 局部变量(栈区 Stack)
- 知识点:函数调用时分配,函数结束时销毁。由CPU的栈指针(ESP/RSP)自动管理。
- OS关联:OS需要为每个进程/线程分配独立的“内核栈”。理解栈溢出对写内核驱动极其重要。
- 3. 动态内存(堆区 Heap)
- 知识点:
malloc和free。 - OS关联:不要只知道用
malloc!你要去思考malloc底层是怎么实现的? 它实际上是向操作系统申请内存(通过系统调用如brk或mmap)。学习OS,就是要去实现malloc背后的那个分配器(比如伙伴系统、Slab分配器)。
- 知识点:
第六阶段:C语言的编译与链接(连接OS的桥梁)
OS源码不是一整个文件,而是成千上万个 .c 文件编译链接而成的。
- 1. 预处理与条件编译
- 知识点:
#ifdef,#ifndef,#if。 - OS关联:Linux内核要支持几十种CPU架构(x86, ARM, RISC-V),怎么做到一份代码兼容所有硬件?全靠条件编译。比如:
#ifdef CONFIG_X86 ... #elif CONFIG_ARM ...
- 知识点:
- 2. 链接器脚本基础
- 知识点:了解C语言编译后的
.text(代码段)、.rodata(只读数据段)、.data(数据段)。 - OS关联:写OS时,你需要告诉链接器:“把我的内核代码放在内存的
0x100000地址,把我的栈放在0x900000地址”。这需要写特殊的.ld链接脚本。
- 知识点:了解C语言编译后的
第七阶段:脱离标准库生存
在应用层,我们用 printf 打印,用 fopen 读文件。
但在刚写出的OS内核里,根本没有标准库(没有stdio.h),你什么都没有!
- 1. 字符串的底层本质
- 知识点:字符串就是以
\0结尾的字符数组。
- 知识点:字符串就是以
- 2. 手写底层基础函数
- 知识点:自己实现
strlen,strcpy,strcmp,memset,memcpy。 - OS关联:Linux内核源码里有一个专门的文件夹叫
lib/,里面全是内核自己实现的字符串和内存拷贝函数,因为内核不能依赖外部的C标准库。
- 知识点:自己实现
- 3. 可变参数函数
- 知识点:
stdarg.h里的va_list,va_start,va_arg宏。 - OS关联:内核里也要打印日志(如
printk),所以你必须知道printf是怎么做到接收"hello %d", num这种不定参数的。
- 知识点:
