W2K信號(Signals)的設(shè)備驅(qū)動
Unix下的信號提供了一個簡單的IPC機制,也就是當進程收到一個信號后會異步(asynchronous) 地調(diào)用你的信號處理函數(shù)(也叫做句柄),不管你的代碼是否已經(jīng)處在執(zhí)行的過程之中。 而在Windows 2000(譯者注:版本高于W2k的Windows平臺)下就需要用到一個設(shè)備驅(qū)動,以便你能使用異步過程調(diào)用(asynchronous procedure calls , 簡稱APCs或者APC) 來達成同樣的效果.
By Panagiotis E.
August 01, 2001
URL:http://www.ddj.com/windows/184416344
翻譯:Lymons (lymons@gmail.com)
在Windows和基于Unix的操作系統(tǒng)之間的一個重要的不同就是對程序員自定義的信號處理函數(shù)的支持。盡管標準C庫已經(jīng)為信號處理[2]提供了基本的支持,但這些函數(shù)對于那些想主要依靠信號來實現(xiàn)進程間通信(IPC)的程序員來講還不夠。 實際上,在Windows的上下文中缺乏這樣的一個機制,導(dǎo)致了進程(線程)間的異步通信的實現(xiàn)困難,另外還需要運用一些特殊的數(shù)據(jù)結(jié)構(gòu),如事件,而且還需要創(chuàng)建一個進程,專門用于不斷地去輪巡一些條件[6],查看其狀態(tài)是否發(fā)生改變。 在本文中,我將向大家介紹SignalsLib庫,它被用于在Win32平臺下進行信號處理。這個庫的核心是一個設(shè)備驅(qū)動,該驅(qū)動提供了一個給目標進程發(fā)送信號并讓其異步執(zhí)行信號處理函數(shù)的機制,即使是目標進程并不處在消息等待的狀態(tài)。
跟大多文章一樣,本文為信號處理提供了一份概要性的說明,
1. 信號和基本機制
2. 庫和驅(qū)動的設(shè)計與實現(xiàn)
3. 該機制下的性能測量
4. 面向程序員的API接口
5. 以及相關(guān)的擴展和可能用法。
信號的概述
信號是把異步事件通知給進程的一個機制。它的基本思想是為每一個獨特的信號關(guān)聯(lián)上一個整數(shù)代碼,并且任何進程都能給任何一個信號注冊一個處理函數(shù)(回調(diào)函數(shù))(通過指定該信號的整數(shù)代碼). 當一個進程發(fā)送一個特定的信號給其他的進程(該進程已經(jīng)為這個信號注冊了信號處理函數(shù))的時候,這個目標進程當前正在做的操作將會被中斷,轉(zhuǎn)而去執(zhí)行這個被注冊的信號處理函數(shù)。
信號機制有點兒類似于,在一個信號中斷中進行一個中斷處理,不管目標進程是否正在執(zhí)行某一段代碼。跟中斷處理函數(shù)一樣,信號處理函數(shù)需要認真仔細地編碼 --- 普通的代碼一定不能訪問和信號處理函數(shù)中一樣的數(shù)據(jù),除非它們都是用同步原語(synchronization primitives)來避免相互之間的破壞。信號提供了簡單和方便的進程間通信。通常傳統(tǒng)的應(yīng)用在下面的情況:
1. 通知一個服務(wù)它將輪轉(zhuǎn)(rotate)它的日志文件;
2. 通知一個父進程子進程已經(jīng)完成初始化并已經(jīng)準備開始實行下面的工作;
3. 通知一個進程它將暫時暫停自己的操作;
4. 通知一個進程它將在關(guān)機時盡可能快地執(zhí)行清除操作
5. 等等。
每個信號都被關(guān)連上一個將被執(zhí)行的動作,通過內(nèi)核調(diào)度來使接收到這個信號的進程來執(zhí)行它的行為。對于大多數(shù)的信號,缺省的動作時終止進程的運行,盡管一個進程能被要求執(zhí)行一些來自系統(tǒng)選擇的動作。這些可能的可選動作無非就是:
1. 忽略這個信號。 在這種情況下,進程將不會接收到這個信號的通知。
2. 恢復(fù)缺省的信號動作。
3. 執(zhí)行一個指定的信號處理函數(shù)。這種場合下,當希望一個指定的信號到達時,想要某個進程去執(zhí)行一些定制動作,就可以去注冊這個定制函數(shù)。當關(guān)聯(lián)的信號發(fā)生時就可以異步地調(diào)用它了。在這個信號處理函數(shù)返回之后,將在被中斷的代碼的地方繼續(xù)執(zhí)行原來的操作。
Windows支持信號
Win32為信號支持提供了一個十分特殊的函數(shù) SetConsoleCtrlHandler(). 這個函數(shù)讓一個控制臺的程序能捕獲很多的系統(tǒng)自身的信號(如,用戶按下了Ctrl-C, 用戶注銷, 等)。 但它沒有提供任何的程序員自定義的信號,也沒有提供任何的進程間通信 — 這就很嚴格的意味著操作系統(tǒng)只能把很少的一些特定事件通知給一個進程。
Windwos提供的僅有的與信號機制類似的是異常處理這種機制。然而,標準C要求給著名的signal()/raise() 的Unix 函數(shù)以及一些受約束的信號[2]提供支持. signal()給指定的信號設(shè)定被調(diào)用的信號處理函數(shù)。raise() 是給當前的進程發(fā)送特定的信號,調(diào)用為這個信號注冊的處理函數(shù),或者與該信號關(guān)聯(lián)的缺省動作。 另外,這些信號是不可擴展的,并且它們只能在給定的進程內(nèi)部進行傳播。 (標準C并沒有為向其他進程發(fā)送信號而定義標準函數(shù))
作為信號的替代品,Windows支持異步過程調(diào)用,簡稱為APCs (Asynchronous Procedure Calls). 一個APC 就是一個內(nèi)核定義的控制對象,代表著一個過程/函數(shù)可以被異步的調(diào)用. APCs 有著下列的幾個特征[7]:
1. 一個 APC 總是運行在一個指定的線程的上下文中.
2. 一個 APC 運行在 OS 的預(yù)設(shè)時間內(nèi).
3. APCs 能夠搶占當前正在運行的線程.
4. APC 例程也能被他們自己搶占.
在內(nèi)核中APCs 有三個不同的類型[2,3]:
用戶模式(User-mode) APCs. 用戶模式 APCs 默認是被禁止的; 也就是對于用戶模式的線程它們雖然被放置到隊列進行排隊,但它們并不會被執(zhí)行,除了在程序中一些明確定義的點上。具體的就是,它們能夠在下面兩種情況被執(zhí)行:
1. 當一個應(yīng)用調(diào)用等待服務(wù)(wait service)并且觸發(fā)了告警發(fā)生機制的時候;
2. 或者調(diào)用告警測試服務(wù)(test-alert service)的時候.
常態(tài)內(nèi)核模式(Normal kernel-mode) APCs. 除了默認情況下是可以被執(zhí)行的之外,其他的它們更像用戶模式的APCs。也就是,當線程已經(jīng)開始執(zhí)行一個內(nèi)核模式的APC,或者駐留在一個臨界區(qū)代碼之中時,是不能被執(zhí)行的。除此之外,它們都是可以被執(zhí)行的。
特殊內(nèi)核模式(Special kernel-mode) APCs. 它們是不能被阻塞的, 除了線程運行在IRQL (interrupt request level)喚起的狀態(tài)下。 特殊內(nèi)核模式的APCs 在內(nèi)核態(tài)中能在IRQL的 APC_LEVEL級別下運行。它們被用來強制讓一個線程在它的上下文中去執(zhí)行一個過程。 特殊內(nèi)核模式的APC 能夠搶占常態(tài)內(nèi)核模式APC的執(zhí)行。
Win32 API [4] 提供了 QueueUserAPC()這個函數(shù), 它允許一個應(yīng)用把線程的APC對象放置到隊列中。正在排隊中的APC是讓一個指定的線程去調(diào)用APC函數(shù)的請求。當用戶模式的APC被放到隊列中時,線程則不能直接去調(diào)用這個APC函數(shù),除非它是處在一個告警使能的狀態(tài)。 不幸的是,一個線程只能使用下列的 Win32 API函數(shù)的之一才能使自己進入到告警使能的狀態(tài): SleepEx(), SignalObjectAndWait(), WaitForSingleObjectEx(), WaitForMultipleObjectEx(), 或 MsgWaitForMultipleObjectsEx().
在內(nèi)核態(tài)[1], 程序員可以使用KeInitializeApc()來初始化一個APC對象, 定義目標線程,以及一個內(nèi)核態(tài)和用戶態(tài)的回調(diào)函數(shù), 以及APC (內(nèi)核 或者 用戶)類型, 最后是傳遞給這兩個函數(shù)的參數(shù)。接著, 這個目標線程的 APC 被排隊(KeInsertQueueApc()) 到隊列中,并且在線程沒有進入到告警使能的狀態(tài)時也能夠被執(zhí)行。
SignalsLib 函數(shù)庫的接口
這個函數(shù)庫給支持信號處理提供了適宜的接口,必要的數(shù)據(jù)結(jié)構(gòu)和機制。在構(gòu)建過程的期間,用戶能夠給每個線程定義選項以及全局信號處理函數(shù)表的用途。
signals.h (Listing 1) 定義了可用的信號,信號的整形代碼是從零開始直到MAX_SIGNALS-1. 在這個頭文件中為信號函數(shù)庫中的兩個如下的函數(shù)聲明了接口。
1. SetSignalHandler() 給指定的信號設(shè)置一個函數(shù)(handler). 該函數(shù)如果執(zhí)行失敗則返回0, 成功則返回非零值。
2. SendSignalToThread() 給指定的線程發(fā)送一個信號. 你必須給想接收這個信號的線程指定該線程的句柄,以及想要發(fā)送的信號(整形代碼)。該函數(shù)成功則返回0,否則返回非零。
testapp.c (Listing 2) 描述了如何去使用這個信號函數(shù)庫。 這個應(yīng)用創(chuàng)建了一個設(shè)定信號和對應(yīng)的信號處理函數(shù)的線程,以及發(fā)送這個信號給子線程的主線程,最終導(dǎo)致這個已經(jīng)安裝的信號處理函數(shù)被執(zhí)行。當然,這個驅(qū)動必須事先被安裝到系統(tǒng)里并且已被裝載到內(nèi)存中,否則當函數(shù)庫的DLLMain()函數(shù)被執(zhí)行的時候,會輸出相應(yīng)的錯誤消息。
設(shè)計和實現(xiàn)
SignalsLib 庫由DLL文件和內(nèi)核態(tài)的設(shè)備驅(qū)動組成。這個 DLL 給應(yīng)用程序提供了一個用戶態(tài)的接口, 當我們想要讓目標線程排隊一個內(nèi)核模式的APC并去調(diào)用關(guān)聯(lián)的內(nèi)核態(tài)的函數(shù)的時候,就需要用到這個設(shè)備驅(qū)動。應(yīng)用程序僅僅是簡單的調(diào)用SetSignalHandler() 和SendSignalToThread()這兩個函數(shù)就可以, 然而 — 這個 DLL 隱藏了所有的與設(shè)備驅(qū)動進行通信的細節(jié)。
SetSignalHandler() 函數(shù)很簡單 — 它僅僅是存儲了在信號處理函數(shù)的全局數(shù)組中的相應(yīng)位置,也就是一個函數(shù)指針。 當一個信號確實是被觸發(fā)的時候,內(nèi)部函數(shù)SignalsDriverRoutine()將會被調(diào)用,并且訪問這個全局數(shù)組來決定調(diào)用哪個信號處理函數(shù)。這兩個函數(shù)在signals.c (Listing 3)有定義.
SendSignalToThread() 函數(shù)是DLL與設(shè)備驅(qū)動進行通信的地方。 DllMain() 在DLL第一次被載入的時候會獲得一個設(shè)備驅(qū)動的句柄, 并且在DLL被卸載的時候會釋放這個句柄。 SendSignalToThread() 函數(shù)在調(diào)用DeviceIoControl()函數(shù)(該函數(shù)是傳遞一個SIGINFO結(jié)構(gòu)體給這個設(shè)備驅(qū)動)的時候會使用這個句柄:
typedef struct _SIGINFO



{


HANDLE hThread; /**//* target thread */


ULONG SigNo; /**//* signal number */


ULONG SigFunc; /**//* address of DriverRoutine */

} SIGINFO, *PSIGINFO;

注意這個 SigFunc 不是一個單獨信號處理函數(shù)的地址,而是SignalsDriverRoutine()函數(shù)的地址,該函數(shù)的功能是查詢和調(diào)用DLL中的正確的信號處理函數(shù)。
當SendSignalToThread() 函數(shù)傳遞這個信息給DeviceIoControl()的時候, 它將導(dǎo)致這個驅(qū)動的中斷服務(wù)程序被調(diào)用。該驅(qū)動的主要源代碼都在sigdrv.c (Listing 4)文件里面. 然后驅(qū)動中斷服務(wù)例程會調(diào)用SigDriverSendTheSignal()函數(shù)來負責(zé)為目標線程來排隊一個相應(yīng)的內(nèi)核模式的APC. SigDriverSendTheSignal() 中有一個指向這個目標線程的ETHREAD 的數(shù)據(jù)結(jié)構(gòu)體 [2]的指針. 然后調(diào)用 KeInitializeApc() 函數(shù)去初始化一個內(nèi)核模式的APC并且調(diào)用KeInsertQueueApc() 來把目標線程的 APC 插入到隊列中。
這個被放入到隊列中的 APC 包含一個指向sigdrv.c (Listing 4)文件中的一個函數(shù)UserApcCallBack()的指針. 這個函數(shù)將會在用戶態(tài)中被調(diào)用并且傳遞SIGINFO 結(jié)構(gòu)體. UserApcCallBack() 使用 SIGINFO 中的信息來調(diào)用DLL 函數(shù) SignalsDriverRoutine(), 而它就是那個查詢且調(diào)用與指定信號關(guān)聯(lián)的信號處理函數(shù)的函數(shù)。
性能評估
編程時到底是選擇常態(tài)還是特殊內(nèi)核模式的APC,是跟你所期望的功能有關(guān),而跟性能無關(guān)。 如果你認為你的信號處理函數(shù)能被其他的被觸發(fā)的信號所搶占是一件重要的事情的話,那么你應(yīng)該選擇使用特殊內(nèi)核模式的APC而不是常態(tài)內(nèi)核模式APCs.
這個APC 機制執(zhí)行的相當棒;一旦目標線程被調(diào)度后這個信號處理函數(shù)就會很快的被調(diào)用,一般情況下就是幾微秒之間的事兒。其中值得注意的一個重要的事情就是,內(nèi)核模式APC能立刻完成信號的傳送,這與系統(tǒng)載入的(或者說在系統(tǒng)中運行的)線程數(shù)量無關(guān)。他是通過改變線程的優(yōu)先級來減少這個響應(yīng)時間。例如, SendSignalToThread() 函數(shù)能夠提升目標線程的優(yōu)先級。
結(jié)論
主要是為了在Win32應(yīng)用中能夠使能相同線程內(nèi)部或者不同線程間的異步通信,我實現(xiàn)了這個用戶自定義信號的基本機制。與Unix系統(tǒng)調(diào)用signal() 和kill() 相似的,最終的,通過DLL和設(shè)備驅(qū)動協(xié)同工作也能提供這兩個重要的信號處理的接口,并且也支持了相似的SIGUSR1 和SIGUSR2這兩個信號.
作為這個庫的擴展將來很可能會實現(xiàn)一些其他的Unix信號,如SIGSTOP, SIGCONT, 和 SIGTERM, 并且支持POSIX 標準。 也愿意把這個機制集成到標準C庫中去。這個庫也協(xié)助實現(xiàn)了POSIX函數(shù)pthread_kill() ,這就使得應(yīng)用程序在用戶模式下(或者是從內(nèi)核到用戶模式)的POSIX線程間需要通知機制時,就讓開發(fā)工作變的很容易, 僅僅是讓設(shè)備驅(qū)動知道驅(qū)動例程的函數(shù)地址即可。 盡管這個POSIX 標準為應(yīng)用程序的所有的線程定義了全局信號處理函數(shù),這個庫也能容易的為每個線程提供信號處理的支持,通過使用線程的局部存儲數(shù)據(jù);為了簡單,在當前這個版本中沒有這么做。
為了發(fā)表這篇文章我盡量讓代碼編寫的簡潔, 代碼的實現(xiàn)是假設(shè)所有參與進來的進程都共享同一個signals.dll的實例(也就是它們之間是父子進程的關(guān)系). 更明確的說, SendSignalToThread() 函數(shù)總是傳遞SignalsDriverRoutine() 的函數(shù)地址(調(diào)用進程的上下文中)給設(shè)備驅(qū)動,但是設(shè)備驅(qū)動則會嘗試使用目標進程(很可能是另外一個進程)上下文中的那個地址. 如果目標進程已經(jīng)把signals.dll 載入到了一個不同的地址而不是調(diào)用進程載入進來的地址,這將會導(dǎo)致一個災(zāi)難發(fā)生。如果一個特別的進程不能把signals.dll 載入到一個缺省的地址,你可以選擇另外一個地址,直到你找到與已經(jīng)載入的DLL地址沒有沖突的另外一個地址。 你能更優(yōu)雅地解決這個問題通過修訂這個接口以便讓設(shè)備驅(qū)動能夠有足夠的信息來定位指定線程中的正確的回調(diào)函數(shù)的地址。
最后,另外一個基于用戶模式APC的實現(xiàn)在這里沒有描述。不過SendSignalToThread() 可以調(diào)用 Win32的 QueueUserAPC() 函數(shù)然后通過內(nèi)核模式的設(shè)備驅(qū)動,來設(shè)置目標線程成告警使能狀態(tài)。 你可以對與ETHREAD數(shù)據(jù)結(jié)構(gòu)[2]的基地址相偏移0x4a 個字節(jié)的一個內(nèi)存地址進行設(shè)定來實現(xiàn)的。
References
[1] E. N. Dekker and J. M. Newcomer. Developing Windows NT Device Drivers: A Programmer’s Handbook (Addison-Wesley, 1999).
[2] Microsoft Corporation. Microsoft Developer Network Library, msdn.microsoft.com/library.
[3] D. A. Solomon. Inside Windows NT, Second Edition (Microsoft Press, 1998).
[4] J. Ritcher. Advanced Windows: The Professional Developer’s Guide to the Win32 API for Windows NT 4.0 and Windows 95 (Microsoft Press, 1995).
[5] www.cmkrnl.com/arc-userapc.html
[6] www.microsoft.com/msj/0799/nerd/nerd0709.html
[7] www.osr.com/insider/1998/apc.html
[8] Microsoft Visual Studio\VC98\CRT\SRC\WINSIG.C
Panagiotis Hadjidoukas is a postgraduate student at High Performance Information Systems Laboratory, Department of Computer Engineering & Informatics, at the University of Patras in Greece. You can reach him by email at peh@hpclab.ceid.upatras.gr or at the web page of his laboratory at http://www.hpclab.ceid.upatras.gr.
Listing 1: signals.h — Interface to signal library

/**//* Number of supported signals */

#define MAX_SIGNALS 4



/**//* Signal names */

#define SIGNAL0 0

#define SIGNAL1 1

#define SIGNAL2 2

#define SIGNAL3 3


#ifndef SIGNALSLIBAPI

#define SIGNALSLIBAPI __declspec(dllimport)

#endif



/**//* Set a signal handler */

SIGNALSLIBAPI DWORD SetSignalHandler(DWORD, PVOID);



/**//* Send a signal to a thread */

SIGNALSLIBAPI DWORD SendSignalToThread(HANDLE, DWORD);


/**//* End of File */


Listing 2: testapp.c — Source for signal demonstration program
#define STRICT 1

#include <windows.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>


#include "..\dll\signals.h"



/**//* Global variables to avoid any optimization by the compiler */

long sum1 = 0, sum2 = 0;


volatile unsigned long WaitFlag = 0;


VOID __stdcall fSignal0(VOID)



{

printf("Thread (%ld):Inside the handler \

of signal SIGNAL0!!!\n", GetCurrentThreadId());

return;

}


DWORD WINAPI ThreadRoutine(LPVOID Param)



{

long i;



/**//* Set a hanndler for SIGNAL0 */

SetSignalHandler(SIGNAL0, fSignal0);



/**//* Do some stuff
. */

for (i = 0; i < 10000000; i++) sum1++;



/**//* Wait until the main thread sets the flag */

while (WaitFlag == 0);

return 0;

}


HANDLE hThread;

DWORD ThreadId;


int main()



{

ULONG i = 0;


DWORD Data = 1; /**//* Not actually used */



/**//* Create the target thread */

hThread = CreateThread(NULL,0,ThreadRoutine,&Data,0,&ThreadId);



/**//* Let the target thread to run for a while */

Sleep(1000);



/**//* Send a signal to the target thread */

printf("Thread (%ld): Sends a signal to the \

target thread\n", GetCurrentThreadId());

SendSignalToThread(hThread, SIGNAL0);



/**//* Do some stuff and wait for a while */

for (i = 0; i < 10000000; i++) sum2++;

Sleep(1000);



/**//* Set the flag. The handler must have been executed by now */

WaitFlag = 1;



/**//* Wait for the thread's termination */

WaitForSingleObject(hThread, INFINITE);


return 0;

}


/**//* End of File */


Listing 3: signals.c — Source for signal DLL
#define STRICT 1

#include <windows.h>

#include <stdio.h>

#include <winioctl.h>

#include <string.h>

#include <stdlib.h>


#include "..\driver\sigdrv.h"

#define SIGNALSLIBAPI __declspec(dllexport)

#include "signals.h"



/**//* Global array of signal handler */

VOID (__stdcall *functable[MAX_SIGNALS])(VOID);



/**//* Handle to the device driver */

HANDLE hDevice;



/**//* Set a signal thread */

DWORD SetSignalHandler(DWORD SignalNumber, PVOID f)



{

if (SignalNumber < MAX_SIGNALS)


{

functable[SignalNumber] = f;

return 0;

}

return 1;

}



/**//* Call the appropriate signal handler */

DWORD WINAPI SignalsDriverRoutine(DWORD SignalNumber)



{

if (functable[SignalNumber] != NULL)

(functable[SignalNumber])();

else

printf("NULL signal function
!\n");


return 0;

}




/**//* this information is sent to the device driver */

typedef struct _SIGINFO



{


HANDLE hThread; /**//* target thread */


ULONG SigNo; /**//* signal number */


ULONG SigFunc; /**//* address of DriverRoutine */

} SIGINFO, *PSIGINFO;


SIGINFO siginfo;



/**//* Send a signal to the targer thread */

DWORD SendSignalToThread(HANDLE hThread, DWORD SignalNumber)



{

DWORD cbReturned;



/**//* Initialize a SIGINFO structure */

siginfo.hThread = hThread;

siginfo.SigNo = SignalNumber;

siginfo.SigFunc = (unsigned long)SignalsDriverRoutine;



/**//* Send the information to the driver */

if (!DeviceIoControl (hDevice,

(DWORD)IOCTL_SIGDRV_SEND_SIGNAL,

(PSIGINFO) &siginfo,

sizeof(SIGINFO),

NULL,

0,

&cbReturned,

0

) )


{

return 1;

}

return 0;

}


BOOL WINAPI DllMain(HINSTANCE hDllInst, DWORD fdwReason,

LPVOID lpvReserved)



{

switch (fdwReason)


{

case DLL_PROCESS_ATTACH:

if ((hDevice =

CreateFile(

"\\\\.\\Global\\SIGDRV",

GENERIC_READ | GENERIC_WRITE,

FILE_SHARE_READ | FILE_SHARE_WRITE,

NULL,

OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL,

NULL

)) == INVALID_HANDLE_VALUE)


{

printf ("Can't get a handle to the driver\n");

return FALSE;

}


break;

case DLL_PROCESS_DETACH:

CloseHandle(hDevice);

break;

case DLL_THREAD_ATTACH:

break;

case DLL_THREAD_DETACH:

break;

}

return TRUE;

}



/**//* End of File */


Listing 4: sigdrv.c — Kernel-mode driver for signal library
#define STRICT 1

#include "ntddk.h"

#include "string.h"

#include "sigdrv.h"


#define SIGDRV_DEVICE_NAME_U L"\\Device\\Sigdrv"

#define SIGDRV_DOS_DEVICE_NAME_U L"\\DosDevices\\SIGDRV"


// Debugging macros

#ifdef DBG

#define SigDrvKdPrint(_x_) \

DbgPrint("SigDrv.sys: ");\

DbgPrint _x_;

#else

#define SigDrvKdPrint(_x_)

#endif


NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject,

IN PUNICODE_STRING registryPath);


VOID SigDrvUnload(

IN PDRIVER_OBJECT DriverObject);


NTSTATUS SigDrvDispatch(

IN PDEVICE_OBJECT DeviceObject,

IN PIRP Irp);


NTSTATUS SigDrvSendTheSignal(

IN PDEVICE_OBJECT DeviceObject,

IN OUT PVOID ioBuffer,

IN ULONG inputBufferLength,

IN ULONG outputBufferLength);


void KeInitializeApc(

PKAPC Apc,

PKTHREAD Thread,


CCHAR ApcStateIndex,

PKKERNEL_ROUTINE KernelRoutine,

PKRUNDOWN_ROUTINE RundownRoutine,

PKNORMAL_ROUTINE NormalRoutine,

KPROCESSOR_MODE ApcMode,

PVOID NormalContext);



void KeInsertQueueApc(

PKAPC Apc,

PVOID SystemArgument1,

PVOID SystemArgument2,

UCHAR unknown);


// Information the driver receives from user mode

typedef struct _SIGINFO



{

HANDLE hThread; // handle of targer thread

ULONG SigNo; // which signal

ULONG SigFunc; // signals' driver-routine of the dll

} SIGINFO, *PSIGINFO;


void KernelApcCallBack(

PKAPC Apc,

PKNORMAL_ROUTINE NormalRoutine,

PVOID NormalContext,

PVOID SystemArgument1,

PVOID SystemArgument2)



{

ExFreePool(Apc); // just free the kernel memory

return;

}


void UserApcCallBack(PVOID arg1, PVOID arg2, PVOID arg3)



{

PSIGINFO psiginfo = (PSIGINFO) arg3;

ULONG (*SignalDriverRoutine)(ULONG);


// take the user mode address of the function

SignalDriverRoutine = (unsigned long (__stdcall *)

(unsigned long)) psiginfo->SigFunc;


// call the driver-routine

SignalDriverRoutine(psiginfo->SigNo);


return;

}


NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject,

IN PUNICODE_STRING RegistryPath)



{

PDEVICE_OBJECT deviceObject=NULL;

NTSTATUS ntStatus;

WCHAR deviceNameBuffer[]=SIGDRV_DEVICE_NAME_U;

UNICODE_STRING deviceNameUnicodeString;

WCHAR deviceLinkBuffer[]=SIGDRV_DOS_DEVICE_NAME_U;

UNICODE_STRING deviceLinkUnicodeString;


RtlInitUnicodeString (&deviceNameUnicodeString,

deviceNameBuffer);

ntStatus = IoCreateDevice (

DriverObject,0,&deviceNameUnicodeString,

FILE_DEVICE_SIGDRV,0,FALSE,&deviceObject);

if (!NT_SUCCESS(ntStatus))


{

SigDrvKdPrint(("IoCreateDevice failed:%x\n", ntStatus));

return ntStatus;

}


DriverObject->MajorFunction[IRP_MJ_CREATE] =

DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =

DriverObject->MajorFunction[IRP_MJ_CLOSE] = SigDrvDispatch;


DriverObject->DriverUnload = SigDrvUnload;


RtlInitUnicodeString (&deviceLinkUnicodeString,

deviceLinkBuffer);


ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,

&deviceNameUnicodeString);

if (!NT_SUCCESS(ntStatus))


{

SigDrvKdPrint (("IoCreateSymbolicLink failed\n"));

IoDeleteDevice (deviceObject);

}


return ntStatus;

}


NTSTATUS SigDrvDispatch(

IN PDEVICE_OBJECT DeviceObject,

IN PIRP Irp)



{

PIO_STACK_LOCATION irpStack;

PVOID ioBuffer;

ULONG inputBufferLength;

ULONG outputBufferLength;

ULONG ioControlCode;

NTSTATUS ntStatus;


Irp->IoStatus.Status = STATUS_SUCCESS;

Irp->IoStatus.Information = 0;


irpStack = IoGetCurrentIrpStackLocation(Irp);


ioBuffer = Irp->AssociatedIrp.SystemBuffer;

inputBufferLength =

irpStack->Parameters.DeviceIoControl.InputBufferLength;

outputBufferLength =

irpStack->Parameters.DeviceIoControl.OutputBufferLength;


switch (irpStack->MajorFunction)


{

case IRP_MJ_CREATE:

SigDrvKdPrint (("IRP_MJ_CREATE\n"));

break;


case IRP_MJ_CLOSE:

SigDrvKdPrint (("IRP_MJ_CLOSE\n"));

break;


case IRP_MJ_DEVICE_CONTROL:

ioControlCode =

irpStack->Parameters.DeviceIoControl.IoControlCode;


switch (ioControlCode)


{

case IOCTL_SIGDRV_SEND_SIGNAL:

Irp->IoStatus.Status = SigDrvSendTheSignal(

DeviceObject,

ioBuffer,

inputBufferLength,

outputBufferLength);


if (NT_SUCCESS(Irp->IoStatus.Status))


{

Irp->IoStatus.Information = sizeof(PVOID);

SigDrvKdPrint(("Signal was sent\n"));

}

else


{

Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;

SigDrvKdPrint(("Signal failed to be sent\n"));

}

break;

default:

SigDrvKdPrint (("unknown IRP_MJ_DEVICE_CONTROL\n"));

Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;

break;

}

break;

}


ntStatus = Irp->IoStatus.Status;

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return ntStatus;

}


VOID SigDrvUnload(IN PDRIVER_OBJECT DriverObject)



{

WCHAR deviceLinkBuffer[] = SIGDRV_DOS_DEVICE_NAME_U;

UNICODE_STRING deviceLinkUnicodeString;


RtlInitUnicodeString (&deviceLinkUnicodeString,

deviceLinkBuffer);

IoDeleteSymbolicLink (&deviceLinkUnicodeString);

IoDeleteDevice (DriverObject->DeviceObject);


return;

}


NTSTATUS SigDrvSendTheSignal(

IN PDEVICE_OBJECT DeviceObject,

IN OUT PVOID IoBuffer,

IN ULONG InputBufferLength,

IN ULONG OutputBufferLength)



{

NTSTATUS ntStatus = STATUS_SUCCESS;

PVOID virtualAddress;

SIGINFO *psiginfo = (PSIGINFO) IoBuffer;

PETHREAD uThread = NULL;

PKAPC kApc;


// take a pointer to the kernel thread structure

ntStatus = ObReferenceObjectByHandle(

psiginfo->hThread, THREAD_ALL_ACCESS,

NULL, KernelMode, &uThread, NULL);


if (NT_ERROR(ntStatus))
{

SigDrvKdPrint (("ObReferenceObjectByHandle Failed\n"));

return ntStatus;

}


// Allocate an KAPC structure from NonPagedPool

kApc = ExAllocatePool(NonPagedPool, sizeof(KAPC));


KeInitializeApc(kApc,

(PKTHREAD) uThread, 0,

(PKKERNEL_ROUTINE) &KernelApcCallBack, 0,

(PKNORMAL_ROUTINE) &UserApcCallBack,

KernelMode, (PVOID) 0);

KeInsertQueueApc (kApc, (PVOID) (ULONG) 10, (PVOID) psiginfo, 0);


ObDereferenceObject((PVOID) uThread);

return ntStatus;

}


/**//* End of File */

