windows驅動註冊中斷服務程式
一個驅動程式的標準中斷服務例程的必要功能和建立一個ISR
的需求。
1.1 ISR
需求
一個產生中斷的物理裝置的所有驅動程式必須有一個ISR。中斷服務例程由核心定義如下:
BOOLEAN
( *PKSERVICE_ROUTINE ) (
IN PKINTERRUPT Interrupt,
IN PVOID ServiceContext
);
ISR
執行在DIRQL
上,特別是在驅動程式用IoConnectInterrupt
註冊其ISR
時說明的SynchronizeIrql
層上。當驅動程式的ISR
執行時,所有帶一箇中等或較低IRQL
值的中斷被當前處理器所遮蔽。
當然,另一個帶有一個較高的系統分配的DIRQL
的裝置可以中斷,或者一個高IRQL
系統中斷可以在任何時間發生在Windows NT/Windows 2000
機器上。
記住以下情況:
§ 一個驅動程式的ISR
是可中斷的。
因為ISR
執行在一個相對高的、系統分配的DIRQL
上,因此在當前處理器上用一箇中等或較低的IRQL
遮蔽中斷,ISR
應儘可能快的返回控制。
在DIRQL
執行一個ISR
限制了該ISR
可以呼叫的支援例程。關於IRQL
管理的更多資訊,見第16章。關於任何特定支援例程都能被呼叫的IRQL
的說明資訊,見線上DDK。
1.1.1 ISR
效能
Windows NT/Windows 2000在驅動程式ISR方面完全不同於其他一些作業系統。在Windows NT/Windows 2000系統上,如果其ISR能儘可能快的返回控制,而不是試圖保持對CPU的控制並在其ISR中做盡可能多的I/O處理,尤其是在SMP機器上,那麼驅動程式會有更好的表現。
反之,ISR應從中斷處停止裝置,並儲存一切必要的關於導致中斷的操作的狀態資訊或該操作的環境。ISR應在常駐記憶體中儲存這些資訊或環境,這類記憶體通常位於裝置擴充套件中。這時,它應對驅動程式的DpcForIsr例程或一個CustomDpc排隊以完成這個位於一個較低的IRQL(通常是IRQL DISPATCH_LEVEL)上的操作。
ISR
返回一個Boolean
,表明驅動程式的裝置是否產生中斷。對於共享一箇中斷向量或DIRQL的裝置的驅動程式,每個ISR一旦確定其裝置不是中斷源,就應返回FALSE。
1.1.2 附加的需求的驅動程式例程
所有擁有一個ISR的驅動程式也必須擁有DpcForIsr或CustomDpc例程。驅動程式也可以有附加的CustomDpc例程,以用來完成特定的中斷驅動的I/O操作。
如果任何驅動程式例程與驅動程式的ISR分享資料、裝置暫存器或環境資訊,該驅動程式還必須有一個或多個SynchCritSection例程。
ISR執行在比DpcForIsr或CustomDpc例程更高的IRQL上。因此,在一臺Windows NT/Windows 2000單處理器的機器上,ISR必須在DpcForIsr或CustomDpc例程執行之前返回。當然,在一臺SMP機器上,ISR和DpcForIsr(或CustomDpc)可以並行執行。
1.1.3 建立一個ISR
驅動程式通過在裝置啟動時呼叫IoConnectInterrupt
註冊其ISR
。驅動程式在處理一個PnP IRP_MN_START_DEVICE
請求時作為最終步驟應連線中斷。
每個擁有一個ISR
的驅動程式必須為至少一箇中斷物件指標提供常駐記憶體。通常,該指標被存放在代表產生中斷的物理裝置的裝置物件的裝置擴充套件中。如果驅動程式建立一個控制器物件,中斷物件指標可以存放在控制器擴充套件中,或者它可以存放在由驅動程式分配的,非頁式緩衝池中。
如果下列兩者之一為真,驅動程式必須為中斷自旋鎖提供儲存空間以連線其所有裝置的所有中斷物件:
§ 驅動程式有一個單獨ISR
為兩個或多個裝置處理不同向量的中斷。
§ 驅動程式的ISR
處理一個在多個向量上中斷的裝置
驅動程式在註冊其ISR
之前必須通過呼叫KeInitializeSpinLock初始化中斷自旋鎖。驅動程式也必須為它處理的、與中斷物件指標一樣多的IRQ提供儲存區間。
1.2 ISR
基本功能
在入口處,ISR被賦予一個指向驅動程式的中斷物件的指標和一個指向驅動程式在呼叫IoConnectIntertupt時建立的任意區域的ServiceContext指標。大多數驅動程式設定ServiceContext指標以代表產生中斷的物理裝置的裝置物件或者該裝置物件的裝置擴充套件。在裝置擴充套件中,驅動程式可以為驅動程式的DpcForIsr例程設定狀態資訊,DpcForIsr例程通常進行幾乎所有的I/O處理以滿足每個導致裝置中斷的請求。
在沒有重疊裝置I/O操作的驅動程式中,ISR應做以下工作:
1.確定中斷是否為假。如果是的話,立即返回FALSE以使中斷裝置的ISR迅速被呼叫。否則,繼續中斷處理。
2.從中斷處停止裝置。
3.收集所有DpcForIsr
(或CustomDpc)例程需要用來完成為當前操作的I/O處理的環境資訊。
4.存放該環境資訊於DpcForIsr或CustomDpc例程可訪問的區域,通常在處理當前導致中斷的I/O請求的目標裝置物件的裝置擴充套件中。
5.如果驅動程式有一個DpcForIsr
例程,用指向當前IRP
、目標裝置物件和儲存的環境資訊的指標呼叫IoRequestDpc
。IoRequestDpc
對DpcForIsr
例程排隊以便IRQL一低於處理器上的DISPATCH_LEVEL
就執行。
如果驅動程式有一個CustomDpc
例程,用一個指向DPC
物件(與CustomDpc例程連線)的指標和指向任何儲存的環境(CustomDpc例程將需要它來完成操作)的指標呼叫KeInsertQueueDpc
。通常,ISR也傳送指向當前IRP的指標與目標裝置物件。一旦IRQL低於處理器上的DISPATCH_LEVEL ,CustomDpc例程便執行。
6.返回TRUE以表明其裝置產生中斷。
通常,一個ISR不做實際的I/O處理以滿足一個IRP。相反,它從中斷處停止裝置,建立必要的狀態資訊,然後將驅動程式的DpcForIsr 或 CustomDpc排隊以便進行任何滿足當前導致裝置中斷請求的必要I/O處理。
考慮以下實現方針:
§ 為了獲得可能最短的間隔,一個ISR
必須執行於DIRQL
。
根據上述策略可以為機器中的所有裝置增加I/O流量,因為執行於DIRQL遮蔽了所有系統已經分配了一個較低或中等IRQL值的中斷。
驅動程式的中斷物件的SynchronizeIrql(在驅動程式呼叫IoConnectInterrupt時被指定)確定驅動程式的ISR與SynchCritSection例程在其上執行的DIRQL。
當一個驅動程式的StartIo例程用驅動程式的SynchCritSection例程呼叫KeSynchronizeExecution時,該呼叫者也傳送指向與ISR相連的中斷物件的指標。因此,來自裝置的中斷被遮蔽在處理器執行的SynchCritSection例程上。與此同時,KeSynchronizeExecution持有與中斷物件相連的中斷自旋鎖,以使ISR不能從另一個處理器訪問裝置暫存器或者裝置擴充套件中共享的狀態,直到驅動程式的SynchCritSection例程返回控制。
關於使用一箇中斷自旋鎖的KeSynchronizeExecution的呼叫者的更多資訊,見第16章的“使用自旋鎖”。
1.3 ISR
重疊I/O
操作功能
當Windows NT/Windows 2000 SMP機器內的ISR和DpcForIsr(或CustomDpc)可以並行執行時,只有代表DpcForIsr或CustomDpc例程的DPC物件的一個例項可以為在任何給定的時刻執行而被排隊。
如果相同DPC物件在DpcForIsr(或CustomDpc)例程執行之前通過一個ISR不止一次地排隊,相關DpcForIsr或CustomDpc例程僅被呼叫一次。如果DPC物件在DpcForIsr(或CustomDpc)執行時排隊,該例程的兩個例項可以在Windows NT/Windows 2000 SMP機器內並行執行。
因此,任何在其裝置上重疊I/O操作的驅動程式必須擁有DpcForIsr和/或CustomDpc例程,在這些例程被呼叫時它們可以完成多個IRP。對於驅動程式的ISR的基本要求與沒有重疊I/O操作的裝置驅動程式一樣。見“ISR基本功能”。
當然,如果一個驅動程式重疊I/O操作,其ISR必須為DpcForIsr或CustomDpc例程設定附加狀態。附加狀態包括一個關於DPC例程需要完成而沒有完成的請求的數量以及相關的環境資訊。此外,如果ISR在DPC執行之前被呼叫以處理另一箇中斷,ISR必須小心不要覆蓋為沒有完成的請求儲存的環境資訊。
因為驅動程式的DPC例程與 ISR共享該狀態,其DPC例程必須用一個系統提供的SynchCritSection例程呼叫KeSynchronizeExecution以訪問代表每個DPC例程的共享狀態。
關於這些例程的更多資訊,見第9章“DpcForIsr和CustomDpc例程”。關於ISR和為ISR排隊的DPC互動的更多資訊,見第16章的“使用自旋鎖”。
用DDK做的驅動中,中斷為什麼不能實現
我做的是PCI的驅動,用VC6 DDK來實現。板卡橋晶片用的是9052做的驅動中設定中斷可是沒有反應這是為什麼呢?將9052的 LINTi1接了個開關,模擬實現中斷的電平輸入。相關程式如下:
//獲取中斷資源
case CmResourceTypeInterrupt:
irql = (KIRQL) resource->u.Interrupt.Level;
vector = resource->u.Interrupt.Vector;
affinity = resource->u.Interrupt.Affinity;
mode = (resource->Flags == CM_RESOURCE_INTERRUPT_LATCHED)? Latched : LevelSensitive;
irqshare = resource->ShareDisposition == CmResourceShareShared;
//連線中斷
NTSTATUS status = IoConnectInterrupt(&pdx->InterruptObject, (PKSERVICE_ROUTINE) OnInterrupt,
(PVOID) pdx, NULL, vector, irql, irql, Latched, TRUE, affinity, FALSE);
//中斷服務例程
BOOLEAN OnInterrupt(PKINTERRUPT InterruptObject, PDEVICE_EXTENSION pdx)
{ // OnInterrupt
//關中斷
UCHAR HSR = READ_PORT_UCHAR(0x4c+pdx->portbase0);
KdPrint(("==============readHSRinterrupt!!!\n"));
HSR = HSR&0xFE;
WRITE_PORT_UCHAR(0x4c+pdx->portbase0,HSR);
KdPrint(("==============interrupt!!!\n"));
//恢復中斷訊號電平
//WRITE_REGISTER_UCHAR((PUCHAR)pdx->MemBar1+0x400000,0x10);
HSR = READ_PORT_UCHAR(0x4c+pdx->portbase0);
HSR = HSR|0x01;
WRITE_PORT_UCHAR(0x4c+pdx->portbase0,HSR);
IoRequestDpc(pdx->fdo, NULL, pdx);
return TRUE;
}
///WDMDeviceIOControl的事件響應
case IOCTL_ENABLE_INT:
{
//允許中斷
UCHAR HSR = READ_PORT_UCHAR((PUCHAR)(0x4c+pdx->portbase0));
HSR = HSR | 0x01;
WRITE_PORT_UCHAR((PUCHAR)(0x4c+pdx->portbase0),HSR);
}
break;
//應用程式呼叫
DeviceIoControl(handle, IOCTL_ENABLE_INT, NULL, 0, NULL, 0, &dwReturn, NULL);
中斷請求級(IRQL
)
為了將不同CPU體系中不同的處理硬體優先順序方法統一起來,NT
使用了抽象的CPU
優先順序方案。即中斷請求級。
IRQL
是一個數,定義了CPU
當前活動的重要性。
HIGHEST_LEVEL
機器檢查和匯流排錯誤(硬體)
POWER_LEVEL
電源失效中斷(硬體)
IPI_LEVEL
多處理器系統處理器之間的門鈴(硬體)
CLOCK2_LEVEL
內部時鐘2(硬體)
CLOCK1_LEVEL
內部時鐘1(硬體)
PROFILE_LEVEL
輪廓檔案定時器(硬體)
DIRQLs IO
裝置中斷的平臺相關的級數(硬體)
DISPATCH_LEVEL
執行緒排程器和延遲過程呼叫執行(軟體)
DPC
例程都是在IRQL=DISPATCH_LEVEL
執行的,相當於ISR
(中斷服務例程)的一個延續,
伴隨著ISR
一起註冊。
執行順序:
執行I/O和中斷------->ISR---------->DPC--------->I/O完成例程(IOCompleteRequest)--->APC(非同步過程呼叫)
DPC
物件從終端使用者角度有兩種:DpcForIsr
和CustomDPC
。前者是與裝置驅動物件(Device Object
)繫結的;後者則由驅動自行維護。但從實現上來說,只有一種DPC
物件存在,DpcForIsr
所涉及的維護函式,實際上都是對CustomDPC
的一個封裝而已。
DPC
是執行緒無關的,只有核心態的,這點不像APC
。
APC_LEVEL
非同步過程呼叫執行(軟體)
PASSIVE_LEVEL
一般的執行緒執行級(軟體)
Dispatch
例程的IRQL
是PASSIVE_LEVEL
級別
看看下面的程式碼有什麼問題?***_Read
是一個Read
例程。
NTSTATUS ***_Read(IN PDEVICE_OBJECT pFdo,IN PIRP Irp)
{
....
KIRQL oldirql;
…..
// pdx->ReadBufferLock是在裝置物件擴充套件裡面的KSPIN_LOCK變數。
KeAcquireSpinLock(&pdx->ReadBufferLock,&oldirql);
ulBytesInReadBuffer = pdx->BytesInReadBuffer;
KeReleaseSpinLock(&pdx->ReadBufferLock,oldirql);
…..
}
通常Dispatch_read
是IRP_MJ_READ
的處理例程,它執行在PASSIVE_LEVEL
級別。但是當呼叫了KeAcquireSpinLock
了之後,它就執行在DISPATCH_LEVEL
級別了,這時是不能訪問paged
記憶體的。所以這段程式碼有可能會導致Windows
藍屏。
再看看下面這段程式碼會不會有問題?有什麼問題?
NTSTATUS ***_DispatchClose(IN PDEVICE_OBJECT pFdo,IN PIRP Irp)
{
…..
KeAcquireSpinLock(&pdx->lkThread,&eOldIrqLevel);-
if (pdx->thread)
{
….
KeWaitForSingleObject(pdx->thread,Executive,KernelMode,FALSE,NULL);
….
}
KeReleaseSpinLock(&pdx->lkThread, eOldIrqLevel)
…..
}
通常DispatchClose
例程是執行IRQL
等於PASSIVE_LEVEL
,這是可以任意呼叫KeWaitForSingleObject
,但是當呼叫KeAcquireSpinLock
之後,IRQL
升級到DISPATCH_LEVEL
。這時再呼叫KeWaitForSingleObject
來無限期等待,會導致dead lock
,因為大家都是處在DISPATCH_LEVEL
例程,別的例程可能得不到機會來設定KeWaitForSingleObject
等待的事情,所以會導致dead lock
。
IoConnectInterrupt
IoConnectInterrupt
的目的是為裝置驅動程式註冊一個ISR
(中斷服務例程),使得它可以再裝置在指定的處理器上產生中斷的時候被呼叫。
NTSTATUS
IoConnectInterrupt(
OUT PKINTERRUPT *InterruptObject,//指向驅動程式提供的中斷物件儲存地址,該引數隨後要傳遞給KeSynchronizeExecution。
OUT PKSERVICE_ROUTINE ServiceRoutine,//中斷服務例程的入口
IN PVOID ServiceContext,//指向驅動指定的即將傳遞給ISR的引數,ServiceContext必須在常駐記憶體中,可以是驅動程式建立的裝置驅動的裝置擴充套件,也可以是驅動建立的控制物件的控制擴充,還可以是裝置驅動分配的非分頁記憶體。
IN PKSPIN_LOCK SpinLock OPTIONAL,//指向已經初始化的自旋所,驅動程式負責自旋所的儲存,並且該自旋所將用來同步被驅動程式其它例程共享的資料的訪問,該引數在ISR處理多箇中斷向量或者驅動程式包含不止一個ISR時需要設定,否則,驅動程式不需要為中斷自旋所分配儲存空間,引數設定為NULL。
IN ULONG Vector,//輸入獲取的中斷向量
IN KIRQL Irql,//輸入獲取的中斷優先順序DIRQL
IN KIRQL SynchronizeIrql,//指明ISR執行所在的DIRQL,當ISR需要處理多箇中斷向量或者驅動程式有多個ISR的時候,該值選擇全部中斷資源的u.Interrupt.Level中的最高值,否則和上面的Irql變數相等。
IN KINTERRUPT_MODE InterruptMode,//電平觸發或者邊沿觸發
IN BOOLEAN ShareVector,//指明中斷向量是否是可共享的。
IN KAFFINITY ProcessorEnableMask,//指定一個KAFFINITY值,用來說明裝置中斷可以在什麼樣的處理器平臺上發生。
IN BOOLEAN FloatingSave //指明是否需要儲存裝置中斷時的浮點堆疊,在X86平臺下,該值必須是FALSE。
相關文章
- 將windows應用程式註冊為windows服務Windows
- exe程式註冊成windows系統服務Windows
- C# 註冊Windows服務C#Windows
- 將IoTdb註冊為Windows服務Windows
- mysql註冊服務MySql
- 服務註冊-Eureka
- 按鍵中斷驅動程式
- nacos 服務註冊原理
- consul 服務註冊中心
- SpringCloud之服務註冊SpringGCCloud
- 【SpringBoot】服務對註冊中心的註冊時機Spring Boot
- 把一個程式註冊成系統服務
- 通過Eureka中已經註冊的服務名,呼叫服務
- lms框架服務註冊中心框架
- 使用nssm將.net core的woker service 註冊為windows服務SSMWindows
- 微服務架構之「 服務註冊 」微服務架構
- etcd套路(九)註冊服務中心程式碼封裝封裝
- go微服務系列(二) - 服務註冊/服務發現Go微服務
- 記一次中斷註冊
- pci匯流排驅動及pci裝置驅動註冊
- NodeJs服務註冊與服務發現實現NodeJS
- 服務治理->搭建服務註冊中心: Spring Cloud EurSpringCloud
- 服務治理平臺-註冊中心
- 自定義元件服務註冊配置元件
- nacos服務註冊與發現
- OpenFeign 服務註冊和呼叫原理
- Eureka服務註冊與發現
- 服務註冊中心之Eureka使用
- 微服務4:服務註冊與發現微服務
- consul服務註冊與服務發現的巨坑
- liunx驅動之字元裝置的註冊字元
- windows 服務執行啟動桌面程式Windows
- 【Spring註解驅動開發】在@Import註解中使用ImportBeanDefinitionRegistrar向容器中註冊beanSpringImportBean
- etcd與服務註冊及發現
- spring cloud alibaba系列(一) 服務註冊SpringCloud
- springcloud之服務註冊與發現SpringGCCloud
- apisix~整合服務發現註冊中心API
- Zookeeper實現服務註冊/發現