Windows内核代码严谨而富有逻辑,毕竟Kernel不是简单的Hello World,为了让代码更易于阅读和维护,加入了不少具有编译器特性和协助程序员的代码,同时对于错误和异常,内核必须合适处理,保证稳定和安全。
-
似乎没有实际意义(对于生成二进制指令)的宏
编译器根据"__in/__out/OPTIONAL"的标志知道它代表输入/输出/可选参数。
程序员根据这些符号清晰地了解参数的特性,避免误用。
-
函数开始的"PAGED_CODE"
内核不像普通应用程序,不用太关心硬件和操作系统内部状态,OS本身就需要管理这些。抽象出了IRQ和IRQL, 支援分页内存,任何内核API都必须清晰地确认当前处于哪种中断级别。
PAGED_CODE宏就在判断当前代码是否可运行于可分页内存,否则,debug版本就会抛出异常发现问题。
Windows内核只有PASSIVE和APC使用分页内存 ,更高IRQL只能跑在非分页内存。
APC类似于Unix的Signal, DPC代表线程调度级别。
-
到处可见的"NT_SUCCESS"
内核把大于等于0定义为成功的返回值。
内核必须要处理任何Fail返回值以保证内核正确和安全。
创建Object失败:
从句柄找指针失败:
创建内存地址空间失败:
等等,数不胜数。
Windows定义了4种不同的NT Status, 用高位Bit简洁地用一个数值代表不同状态类型。
NT_SUCCESS (最高两个bit: 00)
NT_INFORMATION (最高两个bit: 01)
NT_WARNING (最高两个bit: 10)
NT_ERROR (最高两个bit: 11)
使用 NTSTATUS 值 - Windows drivers | Microsoft Learn
-
使用全局变量
比如进程工作集Min/Max变量。
经常写上层应用的可能会疑惑,全局变量不是不推荐的吗? 为什么Kernel还明目张胆地使用? 事实上,当你理解内核为何是内核时,就会懂得全局变量其实是内核的基本属性, 用全局变量来表达也许是唯一或最恰当的选项。
-
各式各样的"#pragma"宏
#pragma代表编译器指令,可以"指导"编译器做处理。
Pragma 指令与 __pragma 和 _Pragma 关键字 | Microsoft Learn (Pragma大全)
#pragma alloc_text(PAGE, ...)表示代码段加入到分页内存。
#pragma alloc_text(INIT, ...)表示初始化后就不需要了,可以移除。
#pragma data_seg("PAGEDATA")表示data放入分页内存。
data_seg pragma | Microsoft Learn
-
系统调用开始调用KeGetPreviousMode和Probe操作
一般来说,系统调用会接收用户模式的参数,内核当然默认不信任用户模式,所以会有ProbeXXXX确认参数真的合法。
为什么还要判断是否之前Mode是否是KernelMode呢? 因为此系统调用函数很可能被Kernel或Driver直接调用,默认内核空间是可信赖的,此时就会忽略参数检查,以提高性能。
内核内部使用和私有API
有些API以i后缀代表是内部使用,以p后缀代表是私有不可为外部调用。比如,Ki代表内核管理内部API, Psp代表进程/线程私有API.
内核中还有很多有趣的语言或编译器特性,以后带大家不断领略。