歡迎您光臨本站 註冊首頁

從 Windows 向 Linux 遷移設備控制應用程序

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
通過認識 Microsoft Windows® 和 Linux® 操作系統設備控制的工作原理,本文將簡化從 Microsoft Windows® 向 Linux® 遷移設備控制應用程序。作者分析二者的差別,並給出 C/C++ 示例。

如果讀者開發過不同平台的設備控制應用程序,那麼肯定了解 Windows 和 Linux 的設備控制方式的差別,從一個平台向另一個平台遷移應用程序相當複雜。本文分析兩種操作系統的設備控制原理,探究從架構到系統調用的各個方面,重點比較二者差別。本文還給出一個遷移示例(用 C/C++ 編寫),詳細演示遷移過程。

工作條件:
根據本文的寫作目的,“Windows” 是指 Windows 2000 或其後續版本,且安裝有 Microsoft Visual C++® 6.0 或其後續版本。Linux應當基於 2.6 版內核,且安裝有 GNU GCC。

比較設備控制的架構

Windows 和 Linux 設備控制的方式是不同的。

Windows 設備控制架構

Windows 的 I/O 子系統將用戶應用程序和設備驅動程序聯繫起來,並定義基礎結構支持設備驅動程序。設備驅動程序為具體設備提供 I/O 介面(參見圖 1)。


圖 1:Windows 設備控制架構

在設備控制過程中,I/O 操作封裝為 IRP(I/O 請求數據包)。I/O 管理器創建 IRP,並將它發送到堆棧頂部。然後,設備驅動程序獲取 IRP 的堆棧地址。IRP 包含著 I/O 請求的參數。根據 IRP 包含的請求(比如 創建、 讀取、寫入、設備 I/O 控制、清除 或 關閉),各驅動程序通過硬體介面工作。

Linux 設備控制架構

Linux 的設備控制架構有所不同。主要區別是,Linux 的普通文件、目錄、設備和 socket 都是文件 —Linux 的所有東西都是文件。為了訪問設備,Linux 內核將設備操作調用通過文件系統映射到設備驅動程序。Linux 沒有 I/O 管理器。所有 I/O 請求從開始就進入文件系統(參見圖 2)。


圖 2. Linux 設備控制架構





比較設備文件名和路徑名

從開發的角度來看,獲取設備句柄是設備控制的先決條件。但是,由於設備控制架構的差異,獲取設備句柄會根據所用平台不同(Windows 還是 Linux)而有不同的過程。

一般而言,設備句柄由具體設備驅動程序的名稱決定。

Windows 設備驅動程序的文件名不同於普通文件,通常稱為設備路徑名。它具有固定格式,形如 \.DeviceName。在 C/C++ 編程中,這個字元串應當是 \\.\DeviceName。在代碼中表示為 \\\\.\\DeviceName。DeviceName 應當與相應設備驅動程序定義的設備名稱相同。

有些設備名稱由 Microsoft 定義,因此不能修改(如表 1 所示)。


表 1. Windows 設備名稱(x = 0,1,2 等)
設備 路徑名
軟盤驅動器 A: B:
硬碟邏輯子區 C: D: E: . . .
物理驅動器 PhysicalDrivex
CD-ROM、DVD/ROM CdRomx
磁帶驅動器 Tapex
COM 埠 COMx

例如,我們在 C/C++ 編程中使用設備路徑名,比如 \\\\.\\PhysicalDrive1、\\\\.\\CdRom0 和 \\\\.\\Tape0。 關於這個列表未收錄的其他設備的詳細情況,請查看本文後面的 參考資料 小節。

因為 Linux 將設備描述為文件,所以可以在目錄 ./dev 中找到所有設備文件。這個目錄的設備驅動程序包括:

  • IDE(Integrated Drive Electronics)硬碟驅動器,比如 /dev/hda 和 /dev/hdb
  • CD-ROM 驅動器,有些是 IDE;也有些是模擬 SCSI(Small Computer Systems Interface)設備的 CD-RW(CD 讀/寫)驅動器,比如 /dev/scd0
  • 串列口,例如 /dev/ttyS0 表示 COM1,/dev/ttyS1 表示 COM2,依此類推
  • 定位設備,包括 /dev/input/mice 等
  • 印表機,比如 /dev/lp0

常見設備文件大多可以按照上述描述找到。有關其他設備文件名和設備的詳細信息,請使用命令 dmesg。





比較主系統調用

設備控制的主系統調用包括下列操作:打開、關閉、I/O 控制、讀/寫等。參見表 2 所示的 Windows/Linux 映射。


表 2. 設備控制函數的映射
Windows Linux
CreateFile open
CloseHandle close
DeviceIoControl ioctl
ReadFile read
WriteFile write

現在,我們深入探討三個最常用的函數:create、close 和 devioctl。

Windows 的設備打開和關閉

我們討論 Windows 函數 CreateFile 和 CloseHandle。函數 CreateFile 用於打開設備。該函數返回句柄,用以訪問清單 1 所示的對象。


清單 1. Windows 的 CreateFile 函數
                  HANDLE CreateFile (LPCTSTR lpFileName,          //File name of the device                                                     (Device Pathname)     DWORD dwDesiredAccess,                       //Access mode to the object (read, write,                                                     or both)     DWORD dwShareMode,                           //Sharing mode of the object (read,                                                     write, both or none)     LPSECURITY_ATTRIBUTES lpSecurityAttributes,  //Security attribute determining whether                                                     the returned handle can be inherited by                                                     child processes     DWORD dwCreationDisposition,                 //Action taken on files that exist and                                                     do not exist     DWORD dwFlagsAndAttributes,                  //File attributes and flags     HANDLE hTemplateFile);                       //A handle to a template file  

參數 lpFileName 是前面講過的設備路徑名。通常,打開設備需要將 dwDesiredAccess 設置為 0 或 GENERIC_READ|GENERIC_WRITE,將 dwShareMode 設置為 FILE_SHARE_READ|FILE_SHARE_WRITE,將 dwCreationDisposition 設置為 OPEN_EXISTING,以及將 dwFlagsAndAttributes 和 hTemplateFile 設置為 0 或 NULL。返回句柄將用於後續設備控制操作。

關閉設備使用函數 CloseHandle。將參數 hObject 設置為設備打開時返回的句柄:BOOL WINAPI CloseHandle (HANDLE hObject);。

Linux 的設備打開和關閉

在 Linux 中,我們討論的是函數 open 和 close。 如前所述,打開設備就像打開普通文件一樣。清單 2 顯示如何使用 open 獲取設備句柄。


清單 2. Linux 的 open 函數
                  int open (const char *pathname,         int flags,          mode_t mode);  

調用成功將返迴文件描述符,它是進程尚未打開的序號最小的文件描述符。如果調用失敗,將返回 -1。文件描述符用作設備句柄。

參數標誌必須包含 O_RDONLY、O_WRONLY 或 O_RDWR 的其中之一。其他標誌可選。參數模式在新文件創立時說明文件訪問權。

在 Linux 中,函數 close 關閉設備就像關閉文件一樣:int close(int fd);。

Windows 的 DeviceIoControl

設備控制(Windows 的 DeviceIoControl 和 Linux 的 ioctl)是最常用的設備控制函數,可以完成設備訪問、信息獲取、命令發送和數據交換等任務。清單 3 舉例說明了 DeviceIoControl:


清單 3. Windows 的 DeviceIoControl 函數
                  BOOL DeviceIoControl (HANDLE hDevice,        DWORD dwIoControlCode,        LPVOID lpInBuffer,        DWORD nInBufferSize,        LPVOID lpOutBuffer,        DWORD nOutBufferSize,        LPDWORD lpBytesReturned,        LPOVERLAPPED lpOverlapped);   

這個系統調用向指定設備發送控制代碼和其他數據。相應設備驅動程序按照控制代碼 dwIoControlCode 的指示工作。例如,使用IOCTL_DISK_GET_DRIVE_GEOMETRY 可以從物理驅動器獲取結構參數(介質類型、柱面數、每柱面磁軌數、每磁軌扇區數等)。可以在 MSDN 網站上找到所有控制代碼定義、頭文件和其他詳細內容(參見 參考資料 獲得相關鏈接)。

是否需要輸入/輸出緩衝,以及它們結構和大小怎樣,都取決於實際 ioctl 過程涉及的設備和操作,並由該調用指定的 dwIoControlCode 確定。

如果重疊操作的指針設為 NULL,那麼 DeviceIoControl 將以阻塞(同步)方式工作。否則,它以非同步方式工作。

Linux 函數 ioctl

Linux 可以使用 ioctl — int ioctl(int fildes, int request, /* arg */ ...); — 向指定設備發送控制信息。第一個參數 fildes 是函數 open() 返回的文件描述符,用於指稱具體設備。

與對應的系統調用 DeviceIOControl 不同,ioctl 的輸入參數列表並不固定。它取決於 ioctl 進行何種請求,以及請求參數有何說明,正如 Windows 函數 DeviceIOControl 的參數 dwIoControlCode 一樣。但是,遷移期間需要注意何時選擇正確的請求參數,因為 DeviceIOControl 的 dwIoControlCode 和 ioctl 的 request 具有不同的取值。而且 dwIoControlCode 與 request 之間沒有顯式映射列表。通常可以在相關頭文件中查找請求參數值的定義來選擇參數值。所有控制代碼的定義在 /usr/include/{asm,linux}/*.h 文件中。

參數 arg 為具體設備的運轉提供詳細的命令信息。arg 的數據類型取決於特定控制請求。這個參數可以用於發送詳細命令和接收返回數據。





遷移示例

我們查看一個從 Windows 向 Linux 遷移的過程的示例。這個示例涉及從個人電腦主 IDE 硬碟驅動器讀取 SMART 日誌。

步驟 1. 識別設備類型

如前所述,Linux 的各個設備被當作文件。首先要描述設備在 Linux 上的文件名。只有使用這個文件名,才能獲取設備控制需要的設備句柄。

在這個示例中,對象是 IDE 硬碟驅動器。Linux 將其描述為 /dev/hda、/dev/hdb 等。本例將要遷移的硬碟設備路徑名是 \\\\.\\PhysicalDrive0。/dev/hda 是該設備對應的 Linux 文件名。

步驟 2. 改變包含頭文件

必須將 #include 頭文件改為 Linux 形式(參見表 3):


表 3. #include 頭文件
Windows Linux
#include <windows.h> #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <devioctl.h> #include <sys/ioctl.h>
#include <ntddscsi.h> #include <linux/hdreg.h>

windows.h 包含打開和關閉設備的函數(CreateFile 和 CloseHandle)。相應地,在 Linux 中用於 open() 和 close() 的函數應當包含頭文件 sys/types.h、sys/stat.h 和 fcntl.h。

Windows 的 devioctl.h 用於函數 DeviceIoControl,我們將其改為 sys/ioctl.h 以確保該函數 ioctl 能夠工作。

ntddscsi.h(它是來自 DDK 的頭文件)定義了一組用於設備控制的控制代碼。因為本例只處理 IDE 硬碟驅動器,所以只需將 linux/hdreg.h 添加到 Linux 程序。

對於其他情況,應當確保包含所有頭文件(它們帶有所需的控制代碼的定義)。例如,如果訪問 CD-ROM 而非硬碟驅動器,那麼應當包含 linux/cdrom.h。

步驟 3. 改正函數和參數

現在我們詳細查看代碼。清單 4 顯示命令的詳細信息。


清單 4. 命令詳解
                  unsigned char cmdBuff[7];  cmdBuff[0] = SMART_READ_LOG;  // Used for specifying SMART "commands"  cmdBuff[1] = 1;               // IDE sector count register  cmdBuff[2] = 1;               // IDE sector number register  cmdBuff[3] = SMART_CYL_LOW;   // IDE low order cylinder value  cmdBuff[4] = SMART_CYL_HI;    // IDE high order cylinder value  cmdBuff[5] = 0xA0 | (((Dev->Id-1) & 1) * 16); // IDE drive/head register  cmdBuff[6] = SMART_CMD;       // Actual IDE command  

命令信息來自 ATA 命令說明書。因為將此代碼移植到 Linux 不需要修改,所以沒有必要進一步分析。

清單 5 所示代碼打開 Windows 主硬碟驅動器。


清單 5. 打開 Windows 主硬碟驅動器
                  HANDLE devHandle = CreateFile("\\\\.\\PhysicalDrive0",           //pathname                               GENERIC_WRITE|GENERIC_READ,         //Access Mode                               FILE_SHARE_READ|FILE_SHARE_WRITE,   //Sharing Mode                               NULL,OPEN_EXISTING,0,NULL);  

從有關設備打開和關閉的講解可知,我們需要兩個參數(文件路徑名和設備訪問模式)來打開 Linux 設備。根據前面的原始代碼,第一個參數應當是 /dev/hda,第二個是 O_RDONLY|O_NONBLOCK。修改過的代碼如下所示:HANDLE devHandle = open("/dev/hda", O_RDONLY | O_NONBLOCK);。相應將 CloseHandle(devHandle); 更改為 close(devHandle);。

移植的主要部分是如何使用 ioctl 訪問特定設備和獲取需要的信息。原始 Windows 代碼如清單 6 所示:


清單 6. Windows 上 DeviceIoControl 的源代碼
                  typedef struct _Buffer{         UCHAR   req[8];              // Detailed command information other than                                          control code         ULONG   DataBufferSize;      // Size of Data Buffer, here is 512         UCHAR   DataBuffer[512];     // Data Buffer  } Buffer;    Buffer regBuffer;  memcpy(regBuffer.req, cmdBuff, 7);  //req[7] is reserved for future use. Must be zero.  regBuffer.DataBufferSize = 512;  unsigned int size = 512+12;         // Size of regBuffer                                      // 8 for req, 4 for DataBufferSize, 512 for data  DWORD bytesRet = 0;                 // Number of bytes returned  int retval;                         // Returned value    retval = DeviceIoControl(devHandle,                           IOCTL_IDE_PASS_THROUGH,  //Control code                           regBuffer, // Input Buffer, including detailed command                           size,                            regBuffer, // Output Buffer, use the same buffer here                           size,                            &bytesRet, NULL);  if (!retval)  	cout<<"DeviceIoControl failed."<<endl;  else  memcpy(data, retBuffer.DataBuffer, 512);  

DeviceIoControl 比 ioctl 需要更多的參數。設備句柄在兩個平台上都是第一個參數,它從 CreateFile 和 Linux 的 open() 返回。但是 Windows 的控制代碼和 Linux 的請求在定義上差別很大,以致沒有固定規則能夠找出這兩個參數的映射關係,如前文所述。 IOCTL_IDE_PASS_THROUGH 在頭文件 ntddscsi.h 中定義為 CTL_CODE (IOCTL_SCSI_BASE, 0x040a, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)。通過在頭文件 /usr/include/linux/hdreg.h 中查找定義,可以選用相應 Linux 控制代碼 HDIO_DRIVE_CMD。

另外,設備要完成具體任務需要詳細的命令信息。該命令存放在緩存中,與返回數據的內存空間在進程中交換數據。我們使用同一緩存來發送命令和獲取所需日誌信息。Linux 的緩存大小可以改變;不一定用完八個位元組。本例只用了命令的四個位元組。

對應的 Linux 代碼(清單 7)看起來簡單很多,因為它的結構和函數參數比 Windows 簡單。


清單 7. Linux 函數 ioctl 的源代碼
                  int retval;  unsigned char req[4+512]; // Enough for returned data and the 4 byte detailed                                command information  req[0]= cmdBuff[6];       // Consider the requirement in this sample, only 4 bytes                                are used  req[1]= cmdBuff[2];  req[2]= cmdBuff[0];  req[3]= cmdBuff[1];    retval = ioctl(devHandle, HDIO_DRIVE_CMD, &req);  if(ret)  	cout<<"ioctl failed."<<endl;  else   memcpy(data, &req[4], 512);  

步驟 4. Linux 環境下的測試

在改正頭文件、函數和參數之後,該程序準備在 Linux 上運行。現在的任務是在 Linux 平台上編譯該程序並糾正剩餘的語法錯誤。根據 Linux 版本和編譯環境,可能需要另做修改。

(責任編輯:A6)



[火星人 ] 從 Windows 向 Linux 遷移設備控制應用程序已經有1247次圍觀

http://coctec.com/docs/linux/show-post-69160.html