windows内核研究(驱动开发-多核同步之临界区和自旋锁)
驱动开发
多核同步之临界区
并发和同步:
- 并发是指多个线程在同时执行
- 单核(是分时执行,不是真正的同时)
- 多核(在某一时刻,会同时有多个线程在执行)
- 同步则是保证在并发执行的环境中各个线程可以有序的执行
// 在多线程中改变全局变量int g_num = 0;// Thread代码
void add(){g_num++; // 线程不安全
}
从汇编的角度看
在上面的thread线程代码块中看似代码只有一行:g_num++
但是在汇编代码中是有三行对应的汇编指令
// 编译后的汇编
1.mov eax,[g_num]
2.add eax,1
3.mov [g_num],eax// 在执行多线程代码时,先执行了第一行和第二行汇编代码
// 这里发生了CPU线程切换(可能是主动切换,也可能是被动切换比如CPU时间片到时)
// 这样以来第一行和第二行代码分别被执行了两次,但最后的结果却只加了一次
我们再看另一条汇编代码
inc dword ptr ds:[g_num] // 这时再执行之前的线程代码还安全吗?
先说结论:
- 如果当前CPU是单核的,那么这条指令就是安全的(单核CPU不会出现两个CPU同时执行一条指令的情况,要么执行完,要么还没执行 )
- 如果当前CPU是多核的,那么这条指令就是不安全的(多核CPU会出现两个CPU执行同一条指令的情况,导致最后的执行结果不符合预期)
lock inc dword ptr ds:[g_num] // 改为以下指令后,即使在多核CPU下执行也不会发生线程安全的问题
lock 指令锁当前操作的地址
先读的线程先锁住,后执行的线程在前一个线程执行完后,再继续上锁执行,这样以来在多核CPU下也不会出现线程安全的问题
windows为我们提供的线程安全的API
InterlockedIncrement // 原子地增加一个32位/64位整数
InterlockedDecrement // 原子地减少一个32位/64位整数
InterlockedExchange // 原子交换一个变量的值,并返回旧值
InterlockedCompareExchange // 原子比较并交换,并返回原始值
InterlockedExchangeAdd // 原子地加上一个值,并返回原始值
InterlockedFlushSlist // 原子地清空链表,并返回所有节点
InterlockedPopEntrySList // 原子地弹出 链表头节点
InterlockedPushEntrySList // 原子地插入一个节点到链表头部
windows是如何实现多核线程安全的InterIockedIncrement函数分析:
多行代码的原子操作
lock只能锁住一行指令,无法锁住多行
windwos使用临界区的概念来达到一次只允许一个线程进入直到离开
// 全局变量: flag = 0进入临界区:
Lab:mov eax,1lock xadd[flag],eaxcmp eax,0jz endLab:dec [flag]// 线程等待jpm Lab
endLab:call A() // 执行方法call A():mov esp,ebpsub 1c...
离开临界区:lock dec [flag]
多核同步之自旋锁
在多核windows系统下,ntoskrnl.exe模块的代码才会加锁进行判断
SwapContext线程切换函数中的关键上锁位置
在单核中也有这个函数,只不过什么都不做为了兼容
总结:
- 自旋锁只对多核有意义
- 自旋锁与临界区,事件,互斥体一样,都是一种同步机制,都可以让当前线程处于等待状态,区别在于自旋锁不用切换线程,性能更好