从 Windows 向 Linux 迁移设备控制应用程序

从 Windows 向 Linux 迁移设备控制应用程序 - 爱码网

从 Windows 向 Linux 迁移设备控制应用程序

通过认识 Windows 和 Linux 在设备控制方面的差异,克服迁移中的难题

Sun Ling 和 Yang Yi
2008 年 7 月 14 日发布

WeiboGoogle+用电子邮件发送本页面

0

如果读者开发过不同平台的设备控制应用程序,那么肯定了解 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。在代码中表示为 \\\\.\\DeviceNameDeviceName 应当与相应设备驱动程序定义的设备名称相同。

有些设备名称由 Microsoft 定义,因此不能修改(如表 1 所示)。

表 1. Windows 设备名称(x = 0,1,2 等)

设备路径名
软盘驱动器A: B:
硬盘逻辑子区C: D: E: . . .
物理驱动器PhysicalDrivex
CD-ROM、DVD/ROMCdRomx
磁带驱动器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. 设备控制函数的映射

WindowsLinux
CreateFileopen
CloseHandleclose
DeviceIoControlioctl
ReadFileread
WriteFilewrite

现在,我们深入探讨三个最常用的函数:createclose 和 devioctl

Windows 的设备打开和关闭

我们讨论 Windows 函数 CreateFile 和 CloseHandle。函数 CreateFile 用于打开设备。该函数返回句柄,用以访问清单 1 所示的对象。

清单 1. Windows 的 CreateFile 函数

1

2

3

4

5

6

7

8

9

10

11

12

13

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 函数

1

2

3

int open (const char *pathname,

       int flags,

       mode_t mode);

调用成功将返回文件描述符,它是进程尚未打开的序号最小的文件描述符。如果调用失败,将返回 -1。文件描述符用作设备句柄。

参数标志必须包含 O_RDONLYO_WRONLY 或 O_RDWR 的其中之一。其他标志可选。参数模式在新文件创立时说明文件访问权。

在 Linux 中,函数 close 关闭设备就像关闭文件一样:int close(int fd);

Windows 的 DeviceIoControl

设备控制(Windows 的 DeviceIoControl 和 Linux 的 ioctl)是最常用的设备控制函数,可以完成设备访问、信息获取、命令发送和数据交换等任务。清单 3 举例说明了 DeviceIoControl

清单 3. Windows 的 DeviceIoControl 函数

1

2

3

4

5

6

7

8

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 可以使用 ioctlint 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 头文件

WindowsLinux
#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. 命令详解

1

2

3

4

5

6

7

8

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 主硬盘驱动器

1

2

3

4

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 的源代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

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 的源代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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 版本和编译环境,可能需要另做修改。

从 Windows 向 Linux 迁移设备控制应用程序

通过认识 Windows 和 Linux 在设备控制方面的差异,克服迁移中的难题

Sun Ling 和 Yang Yi
2008 年 7 月 14 日发布

WeiboGoogle+用电子邮件发送本页面

0

如果读者开发过不同平台的设备控制应用程序,那么肯定了解 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。在代码中表示为 \\\\.\\DeviceNameDeviceName 应当与相应设备驱动程序定义的设备名称相同。

有些设备名称由 Microsoft 定义,因此不能修改(如表 1 所示)。

表 1. Windows 设备名称(x = 0,1,2 等)

设备路径名
软盘驱动器A: B:
硬盘逻辑子区C: D: E: . . .
物理驱动器PhysicalDrivex
CD-ROM、DVD/ROMCdRomx
磁带驱动器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. 设备控制函数的映射

WindowsLinux
CreateFileopen
CloseHandleclose
DeviceIoControlioctl
ReadFileread
WriteFilewrite

现在,我们深入探讨三个最常用的函数:createclose 和 devioctl

Windows 的设备打开和关闭

我们讨论 Windows 函数 CreateFile 和 CloseHandle。函数 CreateFile 用于打开设备。该函数返回句柄,用以访问清单 1 所示的对象。

清单 1. Windows 的 CreateFile 函数

1

2

3

4

5

6

7

8

9

10

11

12

13

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 函数

1

2

3

int open (const char *pathname,

       int flags,

       mode_t mode);

调用成功将返回文件描述符,它是进程尚未打开的序号最小的文件描述符。如果调用失败,将返回 -1。文件描述符用作设备句柄。

参数标志必须包含 O_RDONLYO_WRONLY 或 O_RDWR 的其中之一。其他标志可选。参数模式在新文件创立时说明文件访问权。

在 Linux 中,函数 close 关闭设备就像关闭文件一样:int close(int fd);

Windows 的 DeviceIoControl

设备控制(Windows 的 DeviceIoControl 和 Linux 的 ioctl)是最常用的设备控制函数,可以完成设备访问、信息获取、命令发送和数据交换等任务。清单 3 举例说明了 DeviceIoControl

清单 3. Windows 的 DeviceIoControl 函数

1

2

3

4

5

6

7

8

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 可以使用 ioctlint 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 头文件

WindowsLinux
#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. 命令详解

1

2

3

4

5

6

7

8

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 主硬盘驱动器

1

2

3

4

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 的源代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

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 的源代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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 版本和编译环境,可能需要另做修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值