虛擬裝置驅動程式(VxD)設計中的兩個關鍵問題 (轉)

worldblog發表於2007-12-06
虛擬裝置驅動程式(VxD)設計中的兩個關鍵問題 (轉)[@more@]

虛擬裝置(VxD)設計中的兩個關鍵問題

  陳國友

  在虛擬裝置驅動程式(VxD)的設計中,兩個尤為關鍵,且又令人困擾的問題是VxD的虛擬化和VxD與應用程式間的通訊機制。下面,對這兩個問題作一詳細的探討。

  一、VxD的虛擬化

  由於允許同時執行多個任務,所以出現多個程式試圖同時訪問同一物理裝置的情況時,如果多個應用程式透過同一個DLL驅動程式(注意和虛擬裝置驅動程式VxD的區別)訪問裝置,不需要對該裝置虛擬化,驅動程式使之順序訪問;如果是多個Windows應用程式對相同裝置同時訪問,由於都執行於System VM(虛擬機器),所以也不需要虛擬化,它們的訪問將由一個驅動程式(Windows DLL)進行檢測並使之化,而不是依靠VxD;如果多個VM試圖訪問同一裝置,由於DOS應用程式能夠直接操縱,所以必須對該裝置進行虛擬化,一個虛擬化裝置的VxD負責可靠地檢測多個VM試圖訪問同一裝置的情況,並採取仲裁的策略來解決這種衝突。這裡可能有以下幾種解決方案:

  1、允許一個VM訪問物理裝置,同時忽略其它的VM。這是最簡單的虛擬化形式。如VPD(Virtual Printer Device)。

  2、允許一個VM訪問物理裝置,同時為其它的VM虛擬化裝置。如VKD(Virtual Keyboard Device)分配給一個VM,並使之獲得物理鍵盤的訪問權(包括鍵盤中斷在內),對其它的VM而言,VKD只向它們提供一個空的鍵盤緩衝區。

  3、允許多個VM共享同一物理裝置。儘管存在假象,但從VM的觀點來看,這種方法與獨享訪問一樣。如VDD(Virtual Display Device),使每一個Windows環境下的DVM認為是直接寫入視訊記憶體,其實只是被VDD對映到了一個視窗的緩衝區。

  4、VxD獨立訪問物理裝置的同時,允許一個VM訪問虛擬裝置,這是最複雜的虛擬化形式。如(Virtual Device),VCD緩衝區接收序列資料並透過對映中斷透明地傳給相應的一個VM,VM在中斷處理過程中讀取串列埠資料暫存器,這些資料的實質是VCD緩衝區已經接收的資料。

  與物理裝置一樣,硬體中斷很多時候也必須虛擬化,這種情況更為複雜。虛擬化中斷實質上就是將硬體產生的中斷對映到需要它的每一個VM(不管該VM是否正在執行),替代VxD進行服務。在這裡我們給出一個虛擬化中斷的VxD例項的幾個重要回撥過程,並採用最簡單的仲裁策略來解決訪問衝突(見程式1)。

  typedef struct

  {

   IRQHANDLE IrqHandle;

   VMHANDLE VMOwner;

   Char DeviceName[8];

   BOOL bVMIsServicing;

  } DEVICE_CONTEXT;

  

  void _stdcall MaskChangHandler ( VMHANDLE hVM , IRQHANDLE hIRQ , BOOL bMasking )

   //當一個VM在中斷控制器中遮蔽或開啟中斷hIRQ時,VPICD該過程

  {

   if ( !bMasking ) //若為開啟中斷

   {

   if ( !device.VMOwner )

   {

   device.VMOwner = hVM; //若無任何VM佔有該中斷,則將該中斷的擁有權設為當前VM

   }

   else

   {

   if ( device.VMOwner != hVM )

   {

   device.VMOwner = _Resolve_Contention ( device.VMOwner , hVM , device.DeviceName );

   //若已有VM佔有該中斷,則可透過對話方塊在兩者間作出選擇

   }

   }

   VPICD_Physically_Unmask ( hIRQ ); //開啟該物理中斷

   }

   else

   {

   device.VMOwner = 0;

   VPICD_Physically_Mask ( hIRQ ); //遮蔽該物理中斷

   }

  }

  

  BOOL _stdcall HwIntHandler ( VMHANDLE hVM , IRQHANDLE hIRQ )

   //當中斷hIRQ發生,VPICD立即呼叫該過程

  {

   if ( device.VMOwner && !device.bVMIsServicing ) //若有VM佔有該中斷並且不在上一次的中斷處理中

   {

   VPICD_Set_Int_Request ( device.VMOwner , hIRQ ); //請見本例程後的討論

   }

   else

   {

   ......

   }

   return TRUE;

  }

  

  void _stdcall VirtIntHandler ( VMHANDLE hVM , IRQHANDLE hIRQ )

   //當VPICD每次向VM模擬中斷時,呼叫該過程

  {

   device.bVMIsServicing = TRUE; //設定中斷處理標誌

  }

  

  void _stdcall IRETHandler ( VMHANDLE hVM , IRQHANDLE hIRQ )

   //當從VM的中斷處理返回,該回撥

  {

   device.bVMIsServicing = FALSE; //清除中斷處理標誌

   }

  (程式1)

  由於中斷是非同步產生的,所以當VxD呼叫VPICD(虛擬可中斷控制器)服務VPICD_Set_Int_Request將該中斷對映到VM時,該VM應處於執行狀態。

  (1)在對映的第一步,VPICD透過呼叫VMM(虛擬機器管理器)服務Call_Priority_VM_Event強制排程所希望的VM,使用最高的優先權(Time_Critical_Boost);

  (2)VPICD提供一個該服務的回撥,所以當VM被排程執行時,VMM即可通知VPICD;

  (3)然後VPICD透過呼叫另一個VMM服務Simulate_Int來調整VM的執行環境。該服務將VM的CS、IP和標誌暫存器壓入VM的堆疊,從VM的中斷向量表IVT取出新的CS、IP和標誌暫存器,並且清除中斷標誌;

  (4)當VPICD從回撥返回,並且VMM變回V86時,VM便立即執行已向VPICD註冊的中斷處理過程。

  編寫虛擬化裝置的VxD與編寫非虛擬化裝置的VxD有很大的不同,主要是它要用到一組完全不同的VMM和VxD服務。實際上,現在很多為新裝置所編寫的VxD根本就不再虛擬化,因為並沒有DOS或Windows應用程式直接訪問這些硬體。

  二、VxD與應用程式間的通訊機制

  由於VxD並不僅僅處理硬體,所以在大多數情形下,VxD還向應用程式提供一個介面。透過該介面,應用程式就能夠做與硬體有關的事情了。

  Windows 9x具有VxD與應用程式雙向通訊的機制。下面敘述的應用程式均指應用程式。

  應用程式到VxD的通訊機制是:VxD並不象Win16應用程式介面那樣輸出一個特殊的過程(保護模式API過程或V86模式API過程)來支援應用程式,取而代之的是它的控制過程必須能夠處理一個特殊的訊息:W32_DEVICEIOCONTROL。VMM代替呼叫DeviceIoControl的應用程式向VxD傳送此訊息。訊息引數可確定VxD訊息響應函式、輸入輸出緩衝區指標及緩衝區大小,並繫結在DIOCPARAMETERS結構中。透過這一介面,不僅僅可以讀寫裝置,而且還能在應用程式和VxD之間互傳指標,從而達到特殊應用的目的。

  有時只需呼叫應用程式與VxD間的介面,便能及時獲得所需資訊和服務。但還有一些特殊情況,必須由VxD非同步通知應用程式,這就需要用到VxD到應用程式的通訊機制。

  VxD到應用程式的介面關係要比應用程式到VxD的介面關係複雜得多。其間有兩種呼叫方法:一種是使用PostMessage函式。透過呼叫這一由外殼VxD(SHELL VxD)提供的新服務,便可通知應用程式;另一種是使用特殊的Win32技術。這種技術的獨到之處在於Win32 API支援多執行緒。

  在Win32技術中,儘管採用的APC(Asynchronous Procedure Calls)非同步過程呼叫機制和Win32事件機制都依賴於喚醒一個Win32應用程式執行緒,但仍略有不同。VxD到應用程式最簡單的通訊機制就是透過APC,這種方法對應用程式和VxD相對要簡單一些。應用程式首先動態載入VxD(CreateFile),並用DeviceIoControl將回撥函式的地址傳給VxD,然後應用程式執行Win32呼叫SleepEx將其自身置為“掛起”(asleep yet alertable)狀態時。當應用程式處於“掛起”狀態,VxD能夠透過VWIN32 VxD提供的QueueUserApc服務呼叫應用程式的回撥函式。另一種更有效的方法是使用Win32事件機制。如果應用程式執行多個執行緒,當子執行緒等待著VxD來喚醒它的同時,主執行緒能夠繼續做自己的工作。例如,當一個子執行緒在等待VxD快取接收的資料時,主執行緒可同時使用者的輸入。一旦緩衝區達到門限,VxD將喚醒等待的子執行緒。對於執行緒間的通知,VxD使用執行緒事件,就象應用程式的多執行緒機制所做的那樣。在Windows 95下,VxD可訪問與多執行緒應用程式非常相同的一些Win32事件API(由VWIN32 VxD提供)。這些VWIN32事件服務包括:_VWIN32_ResetWin32Event、_VWIN32_SetWin32Event、_VWIN32_PulseWin32Event、_VWIN32_WaitSingle、_VWIN32_WaitMultipleObjects。利用這些服務,VxD可喚醒一個等待的Win32應用程式執行緒,或是等待被一個Win32應用程式執行緒喚醒。不幸的是VxD不只是透過簡單呼叫相應的事件服務,就能夠獲得Win32事件的控制程式碼。因此,為獲得Win32事件的控制程式碼要涉及到一個複雜的過程和一個未公佈的系統呼叫。事件通常是由應用程式產生(Win32 API CreateEvent),然後使用未公佈的Win32 API函式OpenVxDHandle將獲得的事件控制程式碼轉換為VxD事件控制程式碼,再透過DeviceIoControl將這一ring 0級事件控制程式碼傳給VxD,於是VxD便可將其作為VWIN32事件函式的引數來使用。

  因為Windows採用基於訊息的事件驅動機制,而VxD並不提供直接發往應用程式執行緒的訊息,所以PostMessage所發訊息與其它眾多的訊息都在主執行緒的訊息迴圈中處理。這樣,當執行一些介面操作時,大量的訊息佔據了訊息佇列,VxD所傳送的訊息就有可能得不到相應的處理。為解決這一問題,在實際設計中可採用的方法有兩種:第一種是仍採用PostMessage,但在應用程式和VxD中需設定標誌位,判斷訊息是否被處理並作了相應的工作(如重傳資料);第二種是使用Win32事件機制,將一個執行緒專用於等待響應Win32事件,而另一些執行緒用於其它處理。下面給出VxD利用Win32事件機制啟用Win32應用程式執行緒的部分例程(見程式2)。當生成或消除(destroy)一個VM,VxD便通知註冊的應用程式,並顯示出相應的資訊。

  在VxD中,

  D OnW32Deviceiocontrol ( PDIOCPARAMETERS p ) //VxD與Win32應用程式的介面函式

  {

   DWORD rc;

   swirch ( p->dwIoControlCode )

   {

   case DIOC_OPEN: //系統定義功能號:裝置開啟

   rc = 0;

   break;

   case DIOC_CLOSEHANDLE: //裝置關閉

   bClientRegistered = FALSE;

   rc = 0;

   break;

   case EVENTVXD_REGISTER: //自定義功能號

   hWin32Event = p->lpvInBuffer;

   *( (DWORD *) (p->lpvOutBuffer) ) = (DWORD) & GlobalVMInfo;

   *( (DWORD *) (p->lpcbBytesReturned) ) = sizeof (DWORD);

   bClientRegistered = TRUE;

   rc = 0;

   break;

   default:

   rc = 0xffffffff;

   }

   return rc; //若返回0表示成功

  }

  

  BOOL OnVmInit ( VMHANDLE hVM ) //一旦有VM被初始化便執行

  {

   if ( bClientRegistered )

   {

   GlobalVMInfo.hVM = hVM;

   GlobalVMInfo.bVmCreated = TRUE;

   Call_Priority_VM_Event (LOW_PRI_DEVICE_BOOST , Get_Sys_VM_Handle() ,

   PEF_WAIT_FOR_STI+PEF_WAIT_NOT_CRIT ,

   hWin32Event , PriorityEventThunk , 0 );

  //使System VM為當前執行狀態,將Ring0級事件控制程式碼作為回撥過程的引數

   }

   return TRUE;

  }

  

  VOID OnVmTenate ( VMHANDLE hVM ) //一旦有VM被終結便執行

  {

   if ( bClientRegistered )

   {

   GlobalVMInfo.hVM = hVM;

   GlobalVMInfo.bVmCreated = FALSE;

   Call_Priority_VM_Event (LOW_PRI_DEVICE_BOOST , Get_Sys_VM_Handle() ,

   PEF_WAIT_FOR_STI+PEF_WAIT_NOT_CRIT ,

   hWin32Event , PriorityEventThunk , 0 );

   }

  }

  

  VOID _stdcall PriorityEventHandler ( VMHANDLE hVM , PVOID Refdata , CRS * pRegs )

  {

   HANDLE hWin32Event = Refdata;

   _VWIN32_SetWin32Event ( hWin32Event ); //啟用事件

  }

  

  在Win32應用程式中;

  VOID main ( int ac , char *av[ ] )

  {

   hEventRing3 = CreateEvent ( 0 , FALSE , FALSE , NULL ); //生成Ring3級事件控制程式碼

   if ( !hEventRing3 )

   {

   printf ( "Cannot create Ring3 eventn" );

   exit ( 1 );

   }

  

   hKernel32Dll = LoadLibrary ( "kernel32.dll" );

   if ( !hKernel32Dll )

   {

   printf ( "Cannot load KERNEL32.DLLn" );

   exit ( 1 );

   }

  

   pfOpenVxDHandle = ( HANDLE ( WINAPI * ) ( HANDLE ) )

  GetProcAddress ( Kernel32Dll , "OpenVxDHandle" );

  If ( !pfOpenVxDHandle )

  {

   printf ( "Cannot get addr of OpenVxDHandlen" );

   exit ( 1 );

  }

  

   hEventRing0 = (*pfOpenVxDHandle ) ( hEventRing3 ); //將Ring3級事件控制程式碼轉換為Ring0級事件控制程式碼

   if ( !hEventRing0 )

   {

   printf ( "Cannot create Ring0 eventn" );

   exit ( 1 );

   }

  

   hDevice = CreateFile ( VxDName , 0 , 0 , 0 , CREATE_NEW , FILE_FLAG_DELETE_ON_CLOSE , 0 );

  //動態載入VxD

   if ( !hDevice )

   {

   printf ( "Cannot load VxD error = %xn" , GetLastError ( ) );

   exit ( 1 );

   }

  

   if ( !DeviceIoControl ( hDevice , EVENTVXD_REGISTER , hEventRing0 , sizeof ( hEventRing0 ) , &pVMInfo , sizeof ( pVMInfo ) , &cbBytestReturned , 0 ) )

   //Win32程式與VxD的介面函式,將Ring0級事件控制程式碼傳入VxD,從VxD傳出GlobalVMInfo結構的指標

   {

   printf ( "DeviceIoControl REGISTER failedn" );

   exit ( 1 );

   }

  

   CreateThread ( 0 , 0x1000 , SecondThread , hEventRing3 , 0 , &tid ); //建立執行緒

   printf ( "Press any key to exit..." );

   getch ( );

   CloseHandle ( hDevice );

  }

  

  DWORD WINAPI SecondThread ( PVOID hEventRing3 )

  {

   while ( TRUE )

   {

   WaitForSingleObject ( ( HANDLE ) hEventRing3 , INFINITE ); //等待相應事件

   printf ( "VM %081x was %x" , pVMInfo->hVM , pVMInfo->bCreated ? "created": "destroyed" );

   //顯示被created或destroyed的VM

   }

   return 0;

  }

  (程式2)

  三、結束語

  雖然VxD的設計還涉及到其它許多方面,但掌握解決以上關鍵問題的細節將對VxD的程式設計起到十分重要的作用。希望本文能給大家帶來一些幫助。

  (作者地址:南京通訊工程學院研究生二隊 210016 收稿日期:1999.2.2)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-988828/,如需轉載,請註明出處,否則將追究法律責任。

相關文章