25、Windows驱动程序的同步处理(1)
驱动程序的同步处理
可重入,是指函数的执行结果不和执行顺序有关。同步机制很大程度上依赖于中断请求级。
IRQ编号 |
设备名称 |
用途 |
IRQ0 |
Tine |
计算机系统计时器 |
IRQ1 |
KeyBoard |
键盘 |
IRQ2 |
RedirectI RQ9 |
与IRQ9相接,MPU-401 MDI使用该IRQ |
IRQ3 |
COM2 |
串口设备 |
IRQ4 |
COM1 |
串口设备 |
IRQ5 |
LPT2 |
建议声卡使用该IRQ |
IRQ6 |
FDD |
软驱传输控制用 |
IRQ7 |
LPT1 |
打印机传输控制用 |
IRQ8 |
CMOSAlert |
即时时钟 |
IRQ9 |
RedirectI RQ2 |
与IRQ2相接。可设定给其他硬件使用 |
IRQ10 |
Reversed |
建议保留给网卡使用该IRQ |
IRQ11 |
Reversed |
建议保留给AGP显卡使用 |
IRQ12 |
PS/2 Mouse |
按PS/2鼠标,若无也可以设定给其他硬件使用 |
IRQ13 |
FPU |
协处理器用,例如FPU(浮点运算器) |
IRQ14 |
Primary IDE |
主硬盘传输控制用 |
IRQ15 |
Secondary lde |
从硬盘传输控制用 |
图 PIC中断向量 P220
APIC 高级PIC(Programmable interrupt controller),共24个中断。
1、中断请求级
1)基本概念
Windows 把中断请求级IRQL分成了32个,0~2级别为软件中断,3~31级为硬件中断。0~31优先级别逐步递增。硬件中断请求级别称为设备中断请求级 DIRQL。Windows大部分时间运行在软件中断请求级中。当设备中断来临时,操作系统提升IRQL到DIRQL,并运行中断处理函数。当中断处理函数结束后,操作系统把IRQL降到原来的级别。
图 IRQL P222
用户模式的代码是运行在最低优先级的passive_level;驱动程序的DriveEntry函数,派遣函数,AddDevice等函数一般都运行在PASSIVE_LEVEL级别,在必要时申请进入DISPATCH_LEVEL级别。
Windows 负责线程调度的组件运行在DISPATCH_LEVEL级别。当前线程运行完毕后,系统自动从PASSIVE_LEVEL提升到 DISPATCH_LEVEL级别,当切换完毕后,操作系统又从DISPATCH_LEVEL降回到PASSIVE_LEVEL级别。
驱动程序的StartIO函数和DPC(deferred procedure call)函数,中断服务例程也运行在DISPATCH_LEVEL级别。
线程运行在PASSIVE_LEVEL级别。如果提升到DISPATCH_LEVEL级别,则不会发生线程的切换,这是一种很常见的同步处理机制。
页故障允许在PASSIVE_LEVEL级别(出现页故障时,调用缺页机制,进行物理内存和磁盘文件进行切换),如果在DISPATCH_LEVEL或更高级别,则系统崩溃。对于DISPATCH_LEVEL或更高级别程序必须使用非分页内存。
2)控制IRQL提升与降低
主要用几个函数:
KeGetCurrentIrql
KeRaiseIrql
KeLowerIrql
1 VOID RasieIRQL_Test()
2 {
3 KIRQL oldirql;
4 ASSERT(keGetCurrentIrql() <= DISPATCH_LEVEL);
5 keRaiseIrql(DISPATCH_LEVEL, &oldirql);
6 //...
7 kelowrIrql(oldirql);
8 }
示例程序 P224
2、自旋锁
同步处理机制。不同于线程中的等待事件;操作系统会把等待某一个事件的线程处于休眠状态,CPU运行其它线程;而自旋锁不会切换到其它线程,而是让这个线程一直自旋等待。所谓自旋,就是一直不停的询问:是否可以获取自旋锁。
在单CPU中,获取自旋锁仅仅是将当前的IRQL从PASSIVE_LEVEL级别升到DISPATCH_LEVEL级别,多CPU中要复杂一点。驱动程序必须在不大于DISPATCH_LEVEL级别中使用自旋锁。
自旋锁一般作用是为各派遣函数实现间同步。尽量不要把自旋锁放在全局变量中,而应当把自旋锁放在设备扩展里。
示例 参见http://www.cnblogs.com/mydomain/archive/2010/10/18/1855118.html
KeAcquireSpinLock
KeInitializeSpinLock
KeAcquireSpinLockAtDpcLevel
3、用户模式下的同步对象
同步对象包括事件,互斥体,信号灯等。用户模式下的同步对象是对内核模式下的同步对象的再封装。
1)等待
WaitForSingleObject
WaitForMultipleObjects
2)开启多线程
CreateThread
[1]中推荐_beginthread
http://msdn.microsoft.com/en-us/library/kdzttdcb%28VS.80%29.aspx
3)事件
典型的同步对象。
CreateEvent
所有形如CreateXXX的Win32 API,如果第一个参数是lpEventAttributes,则这种API内部都会创建一个相应的内核对象;这种API返回一个句柄(32位无符号整数),OS通过这个句柄找到具体的内核对象。
#include "windows.h" #include "process.h" #include "stddef.h" #include "conio.h" UNIT WINAPI Thread1(LPVOID para) { printf("Enter Thread\n"); HANDLE *phEvent = (HANDLE*)para; SetEvent(*phEvent); printf("Leave Thread1\n"); return 0; } int main() { HANDLE hEvent = CreateEvent(NULL, 0, FALSE, NULL); HANDLE hThread1 = (HANDLE)_beginthreadex(NULL, 0, Thread1, &hEvent, 0, NULL); WaitForSingleObject(hEvent, INFINITE); return 0; }
示例代码 P228
4)信号灯
常见的同步对象。
一般同步对象有两种状态:激发状态和未激发状态。
信号灯有个计数器,代表N个灯,只要有一个灯亮着,就说明处于激发状态。
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // SD
LONG lInitialCount, // initial count
LONG lMaximumCount, // maximum count
LPCTSTR lpName // object name
);
lInitialCount ,如果初始值大于0,则处理激发状态。
ReleaseSemaphore
对信号灯执行一次等待操作,减少一个读数,相当于熄灭一个灯泡。
1 #include "windows.h"
2 #include "process.h"
3 #include "stdio.h"
4
5 UINT WINAPI Thread1(LPVOID para)
6 {
7 printf("Enter Thread1\n");
8 HANDLE *hSemaphore = (HANDLE*)para;
9 Sleep(5000);
10 printf("Leave Thread1\n");
11 ReleaseSemaphore(*hSemaphore, 1, NULL);
12 return 0;
13 }
14 int main()
15 {
16 HANDLE hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
17 WaitForSingleObject(hSemaphore, INFINITE);
18 WaitForSingleObject(hSemaphore, INFINITE);
19 HANDLE hThread1 = (HANDLE)_beginthreadex(NULL, 0, Thread1, &hSemaphore, 0, NULL);
20 WaitForSingleObject(hSemaphore, INFINITE);
21 }
示例代码 P229
5)互斥体
用互斥体来避免多个线程使用一个资源。互斥体类似于同步事件;不同的是同一个线程可以递归获得互斥体,而同步事件不能。
如果线程获得互斥体时,互斥体此时的状态是未激发(nonsignaled),而释放互斥体时,互斥体的状态为激发态。
激发有点拗口,就是signaled,也就是接到了信号了。激发与未激发,都是相对于其它线程而言的。
CreateMutex
1 #include "windows.h"
2 #include "process.h"
3 #include "stdio.h"
4
5 UINT WINAPI Thread1(LPVOID para)
6 {
7 HANDLE *phMutex = (HANDLE*)para;
8 WaitForSingleObject(phMutex, INFINITE);
9 WaitForSingleObject(phMutex, INFINITE);//对于同一个线程,可以获得多次
10
11 printf("Enter Thread1\n");
12 Sleep(5000);
13 printf("Leave Thread1\n");
14 ReleaseMutex(*phMutex);
15 return 0;
16 }
17
18 UINT WINAPI Thread2(LPVOID para)
19 {
20 HANDLE *phMutex = (HANDLE*)para;
21 WaitForSingleObject(phMutex, INFINITE);
22 WaitForSingleObject(phMutex, INFINITE);//对于同一个线程,可以获得多次
23
24 printf("Enter Thread2\n");
25 Sleep(5000);
26 printf("Leave Thread2\n");
27 ReleaseMutex(*phMutex);
28 return 0;
29 }
30
31 int main()
32 {
33 HANDLE hMutex = CreateMutex(NULL, NULL, NULL);
34 WaitForSingleObject(hSemaphore, INFINITE);
35 WaitForSingleObject(hSemaphore, INFINITE);
36 HANDLE hThread1 = (HANDLE)_beginthreadex(NULL, 0, Thread1, &hMutex, 0, NULL);
37 HANDLE hThread2 = (HANDLE)_beginthreadex(NULL, 0, Thread2, &hMutex, 0, NULL);
38 Sleep(5000);
39 return 0;
40 }
示例代码 P230
6)等待线程完成
线程对象。每个对象同样有两种状态:运行之中时为未激发,当终止时为激发状态。
1 #include "windows.h"
2 #include "process.h"
3 #include "stdio.h"
4
5 UINT WINAPI Thread(LPVOID para)
6 {
7 printf("Enter Thread2\n");
8 Sleep(5000);
9 return 0;
10 }
11
12 int main()
13 {
14 HANDLE hThread[2];
15 WaitForSingleObject(hSemaphore, INFINITE);
16 WaitForSingleObject(hSemaphore, INFINITE);
17 hThread[0] = (HANDLE)_beginthreadex(NULL, 0, Thread, &hMutex, 0, NULL);
18 hThread[1] = (HANDLE)_beginthreadex(NULL, 0, Thread, &hMutex, 0, NULL);
19 WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
20 return 0;
21 }
示例代码 P232