windows驅動註冊中斷服務程式

風靈使發表於2018-09-04

一個驅動程式的標準中斷服務例程的必要功能和建立一個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、目標裝置物件和儲存的環境資訊的指標呼叫IoRequestDpcIoRequestDpcDpcForIsr例程排隊以便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物件從終端使用者角度有兩種:DpcForIsrCustomDPC。前者是與裝置驅動物件(Device Object)繫結的;後者則由驅動自行維護。但從實現上來說,只有一種DPC物件存在,DpcForIsr所涉及的維護函式,實際上都是對CustomDPC的一個封裝而已。
DPC是執行緒無關的,只有核心態的,這點不像APC
APC_LEVEL非同步過程呼叫執行(軟體)
PASSIVE_LEVEL一般的執行緒執行級(軟體)
Dispatch例程的IRQLPASSIVE_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_readIRP_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。

相關文章