Windows内核原理与实现读书笔记之并发和同步基础
Windows中的并发和同步
并发是指多个控制流同时在执行,既可能是真正的并行执行(如多处理器或多核的硬件环境),也可能是分时方式的并发执行。
同步时保证在并发执行的环境中各个控制流可以有序地执行,包括对于资源的共享或互斥访问,以及代码功能的逻辑顺序。
1 进程和线程同步基础
1.1 并发性基础
进程或线程的并发性可能的来源:
多处理器环境、多核环境、超线程环境、处理器外部中断、内部中断、线程放弃执行权。
Intel X86 指令体系中,运算指令加上lock 前缀保证其原子性。
使用lock 前缀的两个条件:
- 指令的目标操作数必须是一个内存操作数;
- 仅适用于ADD、ADC、AND、BTC、BTR、BTS、CMPXCHG、CMPXCHB、DEC、INC、NEG、NOT、OR、SBB、SUB、XOR、XADD、XCHG指令。如图:
指令 |
含义 |
指令 |
含义 |
ADD |
带进位的加法 |
INC |
加1 |
ADC |
加法 |
NEG |
二进制求补数 |
AND |
逻辑与 |
NOT |
按位求反 |
BTC |
指定的位保存到CF中,再取反 |
OR |
逻辑或 |
BTR |
指定的位保存到CF中,再清零 |
SBB |
带借位的整数减法 |
BTS |
指定的位保存到CF中,再置位 |
SUB |
减法 |
CMPXCHG |
比较并交换 |
XOR |
逻辑异或 |
CMPXCH8B |
8字节的比较并交换指令 |
XADD |
交换两个操作数,并做加法 |
DEC |
减1 |
XCHG |
交换两个操作数 |
如InterlockedIncrement 实现原子操作。
LONG FASTCALL InterlockedIncrement(IN OUT LONG volatile* Addend)
{
_asm{
Mov eax,1
Mov ecx,Addend
Lock xadd [ecx],eax
Inc eax
}
}
1.2 进程或线程之间的通讯
互斥或互斥体(mutex)、信号量、锁(lock)、临界区(critical section) 、自旋锁(spinlock) 和忙等待、消息。
1.3 经典的同步问题
死锁、饥饿(starvation)、优先级反转(priority inversion)、哲学家进餐问题(dining philosophers problem)
2 Windows中断与异常
中断(interrupt) 是异步的,异常是同步的。
2.1 硬件中断的发生和处理
在Intel X86 体系结构中,处理器的中断向两边也称为中断描述符表(IDT,Interrupt Descriptor Table)。IDT将每个中断或异常与一个处理该中断或异常的服务例程关联了起来。IDT中最多可有256 项。处理器内部的IDTR 寄存器记录了IDT的位置和最大限制。
如图,IDT和IDTR的关系:
异常分为三种类型:错误(fault)、陷阱(trap)和中止(abort)。
Intel x86 的中断和异常一览表,如图:
向量号 |
名称 |
类型 |
错误码 |
描述(或来源) |
0 |
除法错 |
错误 |
无 |
DIV和IDIV指令产生的错误 |
1 |
调试 |
错误/陷阱 |
无 |
调试异常 |
2 |
NMI中断 |
中断 |
无 |
不可屏蔽外部中断 |
3 |
断点 |
陷阱 |
无 |
INT3指令 |
4 |
溢出 |
陷阱 |
无 |
INTO指令 |
5 |
BOUND越界 |
错误 |
无 |
BOUND指令 |
6 |
无效操作码 |
错误 |
无 |
UD2指令或保留的操作码(opcode) |
7 |
协处理器不可用 |
错误 |
无 |
浮点或WAIT/FWAIT指令 |
8 |
双重错误 |
中止 |
是(零) |
可产生异常、NMI或INTR的任何指令 |
9 |
协处理器段溢出 |
错误 |
无 |
浮点指令 |
10 |
无效TSS |
错误 |
是 |
任务切换或TSS访问 |
11 |
段不存在 |
错误 |
是 |
装载段寄存器或访问系统段 |
12 |
栈段错误 |
错误 |
是 |
栈操作和SS寄存器装载 |
13 |
一般保护错 |
错误 |
是 |
任何内存引用或其他保护检查 |
14 |
页面错误 |
错误 |
是 |
任何内存引用 |
15 |
Intel 保留 |
|
|
|
16 |
X87 浮点 |
错误 |
无 |
X87FPU浮点或WAIT/FWAIT指令 |
17 |
对齐检查 |
错误 |
是(零) |
对内存中数据的引用 |
18 |
机器检查 |
中止 |
无 |
错误码和异常来源与特定模型相关 |
19 |
SIMD浮点异常 |
错误 |
无 |
SSE/SSE2/SSE3浮点指令 |
20~31 |
Intel 保留 |
|
|
|
32~255 |
用户定义的中断 |
中断 |
|
外部中断或INT n 指令(n>=32) |
2.2 中断请求级别(IRQL)
Windows 使用0~31 表示优先级,数值越大,优先级越高。
#define PASSIVE_LEVEL 0 // Passive release level
#define LOW_LEVEL 0 // Lowest interrupt level
#define APC_LEVEL 1 // APC interrupt level
#define DISPATCH_LEVEL 2 // Dispatcher level
#define PROFILE_LEVEL 27 // timer used for profiling.
#define CLOCK1_LEVEL 28 // Interval clock 1 level - Not used on x86
#define CLOCK2_LEVEL 28 // Interval clock 2 level
#define IPI_LEVEL 29 // Interprocessor interrupt level
#define POWER_LEVEL 30 // Power failure level
#define HIGH_LEVEL 31 // Highest interrupt level
#if defined(NT_UP)
// synchronization level - UP system
#define SYNCH_LEVEL DISPATCH_LEVEL
#else
// synchronization level - MP system
#define SYNCH_LEVEL (IPI_LEVEL-2) // ntddk wdm ntosp
#endif
2.3 中断对象
Wrk 中中断对象KINTERRUPT 定义:
typedef struct _KINTERRUPT {
CSHORT Type;
CSHORT Size;
LIST_ENTRY InterruptListEntry;
PKSERVICE_ROUTINE ServiceRoutine;
PVOID ServiceContext;
KSPIN_LOCK SpinLock;
ULONG TickCount;
PKSPIN_LOCK ActualLock;
PKINTERRUPT_ROUTINE DispatchAddress;
ULONG Vector;
KIRQL Irql;
KIRQL SynchronizeIrql;
BOOLEAN FloatingSave;
BOOLEAN Connected;
CCHAR Number;
BOOLEAN ShareVector;
KINTERRUPT_MODE Mode;
ULONG ServiceCount;
ULONG DispatchCount;
#if defined(_AMD64_)
PKTRAP_FRAME TrapFrame;
PVOID Reserved;
ULONG DispatchCode[DISPATCH_LENGTH];
#else
ULONG DispatchCode[DISPATCH_LENGTH];
#endif
} KINTERRUPT;
InterruptListEntry :将一组中断对象相互链接起来
SeviceRoutine : 中断对象的处理例程
Vector : 一个中断对象的向量编号
Irql :表示该中断处理例程执行时的IRQL
DispatchCode :包含了基本的中断处理代码
Mode :中断对象的模式,有LevelSensitive 或Latched。LevelSensitive 模式是指,当中断请求信号出现(asserted)时,中断就会发生,因此,中断对象的例程必须消除中断的原因;Latched 模式是指,仅当中断请求信号从无信号(deasserted) 到有信号(asserted)发生变化时,中断才会发生。
当一个中断发生时,其处理过程如下:
- 中断对象得DispatchCode 代码块首先获得控制权, 把该代码块所在得中断对象放到edi寄存器中,然后跳转到对应得中断分发函数。
- 在中断分发函数中,提升IRQL,获得自旋锁,然后调研中断对象得服务例程,即KINTERRUPT 得ServiceRoutine ,待返回后,释放自旋锁,恢复IRQL。
- 中断分发函数通过Kei386EoiHelper 辅助函数返回。Kei386EoiHelper的iretd 指令结束整个中断处理。
如图:两个中断对象连接到同一个中断向量情形下的控制流程。