不依赖于线程调度的同步机制
不依赖于线程调度的同步机制,包括提升IRQL、互锁操作、无锁操作、无锁的单链表和自旋锁。这些机制用于IRQL较高(大于等于DISPATCH_LEVEL)的情形。
1 提升IRQL实现数据同步
每个处理器都有一个IRQL属性,即KPCR数据结构的Irql 域,它表示该处理器当前的中断请求级别。一个基本规则是,当处理器在某个IRQL上运行时,它只能被更高级别的中断打断。
在单处理器系统上,提升IRQL就可以确保对资源的独占访问。在多处理器系统上,还需互锁操作或自旋锁等互斥访问机制来配合。
2 互锁操作
函数 | 描述 |
InterlockedIncrement InterlockedDecrement | 对LONG变量加(减)1,如:InterlockedIncrement(&g_iX) (内部使用lock xadd指令) |
InterlockedExchangeAdd | 将一个值加到一个LONG变量,返回变量原值,使用lock xadd指令,如:int g_iX = 0; // InterlockedExchangeAdd(&g_iX,-2); //g_iX -= 2; |
InterlockedCompareExchange | InterlockedCompareExchange( plDest,lExchange, lComperand)。比如*plDestination==lComperand,如果相等将*plDest修改为lExchange,如果不等,则*plDest不变。返回值为*plDest原来的值。 (使用lock cmpxchag指令) |
InterlockedExchange InterlockedExchangePointer | 将第1个参数所指的内存里的当前值,以原子方式替换为第2个参数指定的值。函数返回值为原始值。后面那个函数是改变一个指针本身的值。(如果xchg指令,虽不加lock。但默认为原子操作) |
InterlockedOr | 对一个LONG变量做逻辑或运算,使用lock or指令 |
InterlockedAnd | 对一个LONG变量做逻辑与运算,使用lock and指令 |
InterlockedXor | 对一个LONG变量做逻辑异或运算,使用lock xor指令 |
3 无锁的单链表实现
单链表定义:
typedef struct _SINGLE_LIST_ENTRY {
struct _SINGLE_LIST_ENTRY *Next;
} SINGLE_LIST_ENTRY, *PSINGLE_LIST_ENTRY;
#define SLIST_ENTRY SINGLE_LIST_ENTRY
#define PSLIST_ENTRY PSINGLE_LIST_ENTRY
typedef union _SLIST_HEADER {
ULONGLONG Alignment;
struct {
SLIST_ENTRY Next;
USHORT Depth;
USHORT Sequence;
};
} SLIST_HEADER, *PSLIST_HEADER;
函数 | 描述 |
InitializeSListHead | 创建一个空栈 |
InterlockedPushEntrySList | 在栈顶添加一个元素 |
InterlockedPopentrySList | 移除位于栈顶的元素并将它返回 |
InterlockedFlushSlist | 清空栈 |
QueryDepthSlist | 返回栈中元素的数量 |
4 自旋锁
自旋锁总是在DISPATCH_LEVEL或更高的IRQL上使用。
Windows内核实现了两个自旋锁函数KiAccquireSpinLock 和KiReleaseSpinLock.
以下是KiAccquireSpinLock的汇编代码
VOID FASTCALL KiAccquireSpinLock(IN PKSPIN_LOCK SpinLock);
cPublicFastCall KiAcquireSpinLock,1;由于FASTCALL,ecx 寄存器存放了唯一的参数SpinLock
asl10:lock bts dword ptr [ecx],0 ;测试和设置自旋锁
jc asl20 ;已被占用,跳转到asl20进行旋转
fstRET KiAcquireSpinLock ;成功获取,返回
;旋转
asl20: test dword prt [ecx],1 ;自旋锁是否已被释放?
Jz asl10; ;是的,回到asl10,区抓取该锁
Pause ;Intel X86 处理器专门提供的用于自旋锁的指令
Jmp asl20
fstENDP KiAcquireSpinLock
以下是KiReleaseSpinLock的汇编代码
VOID FASTCALL KiReleaseSpinLock(IN PKSPIN_LOCK SpinLock);
cPublicFastCall KiReleaseSpinLock ,1 ;由于FASTCALL,ecx 寄存器存放了唯一的参数SpinLock
lock and byte ptr [ecx],0 ;仅仅一条原子指令
fstRET KiReleaseSpinLock ;返回
fstENDP KiReleaseSpinLock
Windows 内核提供的自旋锁函数
函数 | 说明 |
KeInitializeSpinLock | 初始化一个自旋锁,将自旋锁赋0 |
KeAcquireSpinLockAtDpcLevel | 在DISPATCH_LEVEL上获取一个自旋锁 |
KeReleaseSpinLockFromDpcLevel | 释放一个自旋锁 |
KeAcquireSpinLockRaiseToSynch | 先将IRQL提升至SYNCH_LEVEL,在获取自旋锁 |
KeAcquireSpinLock | 提升IRQL至DISPATCH_LEVEL,获取自旋锁,返回原IRQL |
KeReleaseSpinLock | 释放自旋锁,并恢复IRQL |
KeTestSpinLock | 检测自旋锁的状态,若已占用,返回0,否则返回1 |
KeTryToAcquireSpinLockAtDpcLevel | 试图获取自旋锁,若成功,返回1,否则返回0 |
KeAcquireSpinLockForDpc | 根据情况提升IRQL至DISPATCH_LEVEL,并获取自旋锁 |
KeReleaseSpinLockForDpc | 释放自旋锁,然后根据情况降低IRQL至原值 |
KeTryToAcquireSpinLock | 试图获取自旋锁,若成功,则IRQL提升至DISPATCH_LEVEL,并返回1,否则返回0 |
每个进程,每个线程都有一个自旋锁。
使用自旋锁时,需注意三点:
1.任何一段代码,其抓住自旋锁的时间不能过长,否则可能会验证影响多处理器的性能。
2.若内核代码持有了一个自旋锁,则它既不能引发页面错误,也不能调度线程,否则系统会崩溃
3.用户程序不能使用自旋锁,因为它们运行在PASSIVE_LEVEL 上。
Windows的执行体层实现了额外的自旋锁操作,它们利用cmpxchg 指令来完成一次对状态值的原子操作。
Windows执行体的自旋锁函数
函数 | 说明 |
ExAcquireSpinLockShared | 提升IRQL至DISPATCH_LEVEL,在循环中测试该锁是否被独占,只要未被独占,则共享计数加一,并返回原IRQL |
ExReleaseSpinLockShared | 通过InterlockedDecrement 将共享计数减一,并降低IRQL |
ExTryAcquireSpinLockExclusive | 若自旋锁已被独占,则返回FALSE。否则,利用cmpxchg 指令设置独占标志位,若成功,则等待其他共享该锁的处理器释放它。注意,自旋锁计数中包含了当前处理器 |
ExAcquireSpinLockExclusive | 先以共享方式获得自旋锁,在调用上一个函数尝试以独占方式获取它,然后释放属于自己的共享获取 |
ExReleaseSpinLockExclusive | 清除独占标志位,恢复IRQL |
执行体自旋锁在Windows中仅被用于大页面内存池的管理。
Windows 内核实现了一种排队自旋锁。在每个处理器的KPRCB结构中都有一个LockQueue数组成员,数组中的每一项对应于一个全局排队自旋锁。
排队自旋锁的工作方式如下。如果自旋锁是空闲的,则其值为0;如果它已被一个处理器获取,则指向该处理器的KPRCB中对应于该锁的LockQueue 数组项。如果有多个处理器正在等待一个排队自旋锁,则以当前已经获取到该自旋锁的处理器的LockQueue数组项为链表头,通过KSPIN_LOCK_QUEUE的Next 成员,构成一个排队单链表,自旋锁本身的值指向最后一个插入进来的处理器的LockQueue数组项。