- 浏览: 1238191 次
文章分类
最新评论
-
char1st:
2b 青年 mongodb
我们该如何设计数据库(一) -
什么向往:
二逼青年的做法的确让人有点觉得:靠,这都行!!
我们该如何设计数据库(一) -
cloudmail:
如果只用到name来查询的话,支持2b做法总之,2b做法是最容 ...
我们该如何设计数据库(一)
实战DeviceIoControl
实战DeviceIoControl
Aida整理。希望与大家多交流驱动开发经验。
QQ18918737
E-mail: aida@tom.com
2006.5.12
实战DeviceIoControl 之一
Q 在NT/2000/XP中,我想用VC编写应用程序访问硬件设备,如获取磁盘参数、读写绝对扇区数据、测试光驱实际速度等,该从哪里入手呢?
A 在NT/2000/XP中,应用程序可以通过API函数DeviceIoControl来实现对设备的访问—获取信息,发送命令,交换数据等。利用该接口函数向指定的设备驱动发送正确的控制码及数据,然后分析它的响应,就可以达到我们的目的。
DeviceIoControl的函数原型为
BOOL DeviceIoControl(
HANDLE hDevice, // 设备句柄
DWORD dwIoControlCode, // 控制码
LPVOID lpInBuffer, // 输入数据缓冲区指针
DWORD nInBufferSize, // 输入数据缓冲区长度
LPVOID lpOutBuffer, // 输出数据缓冲区指针
DWORD nOutBufferSize, // 输出数据缓冲区长度
LPDWORD lpBytesReturned, // 输出数据实际长度单元长度
LPOVERLAPPED lpOverlapped // 重叠操作结构指针
);
设备句柄用来标识你所访问的设备。
发送不同的控制码,可以调用设备驱动程序的不同类型的功能。在头文件winioctl.h中,预定义的标准设备控制码,都以IOCTL或FSCTL开头。例如,IOCTL_DISK_GET_DRIVE_GEOMETRY是对物理驱动器取结构参数(介质类型、柱面数、每柱面磁道数、每磁道扇区数等)的控制码,FSCTL_LOCK_VOLUME是对逻辑驱动器的卷加锁的控制码。
输入输出数据缓冲区是否需要,是何种结构,以及占多少字节空间,完全由不同设备的不同操作类型决定。在头文件winioctl.h中,已经为标准设备预定义了一些输入输出数据结构。重叠操作结构指针设置为NULL,DeviceIoControl将进行阻塞调用;否则,应在编程时按异步操作设计。
Q 设备句柄是从哪里获得的?
A 设备句柄可以用API函数CreateFile获得。它的原型为
HANDLE CreateFile(
LPCTSTR lpFileName, // 文件名/设备路径
DWORD dwDesiredAccess, // 访问方式
DWORD dwShareMode, // 共享方式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全描述符指针
DWORD dwCreationDisposition, // 创建方式
DWORD dwFlagsAndAttributes, // 文件属性及标志
HANDLE hTemplateFile // 模板文件的句柄
);
CreateFile这个函数用处很多,这里我们用它“打开”设备驱动程序,得到设备的句柄。操作完成后用CloseHandle关闭设备句柄。
与普通文件名有所不同,设备驱动的“文件名”(常称为“设备路径”)形式固定为“//./DeviceName”(注意在C程序中该字符串写法为“////.//DeviceName”),DeviceName必须与设备驱动程序内定义的设备名称一致。
一般地,调用CreateFile获得设备句柄时,访问方式参数设置为0或GENERIC_READ|GENERIC_WRITE,共享方式参数设置为FILE_SHARE_READ|FILE_SHARE_WRITE,创建方式参数设置为OPEN_EXISTING,其它参数设置为0或NULL。
Q 可是,我怎么知道设备名称是什么呢?
A 一些存储设备的名称是微软定义好的,不可能有什么变化。大体列出如下
软盘驱动器 |
A:, B: |
硬盘逻辑分区 |
C:, D:, E:, ... |
物理驱动器 |
PHYSICALDRIVEx |
CD-ROM, DVD/ROM |
CDROMx |
磁带机 |
TAPEx |
其中,物理驱动器不包括软驱和光驱。逻辑驱动器可以是IDE/SCSI/PCMCIA/USB接口的硬盘分区(卷)、光驱、MO、CF卡等,甚至是虚拟盘。x=0,1,2 ……
其它的设备名称需通过驱动接口的GUID调用设备管理函数族取得,这里暂不讨论。
Q 请举一个简单的例子说明如何通过DeviceIoControl访问设备驱动程序。
A 这里有一个从MSDN上摘抄来的demo程序,演示在NT/2000/XP中如何通过DeviceIoControl获取硬盘的基本参数。
/* The code of interest is in the subroutine GetDriveGeometry. The
code in main shows how to interpret the results of the IOCTL call. */
#include <windows.h>
#include <winioctl.h>
BOOL GetDriveGeometry(DISK_GEOMETRY *pdg)
{
HANDLE hDevice; // handle to the drive to be examined
BOOL bResult; // results flag
DWORD junk; // discard results
hDevice = CreateFile("////.//PhysicalDrive0", // drive to open
0, // no access to the drive
FILE_SHARE_READ | // share mode
FILE_SHARE_WRITE,
NULL, // default security attributes
OPEN_EXISTING, // disposition
0, // file attributes
NULL); // do not copy file attributes
if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive
{
return (FALSE);
}
bResult = DeviceIoControl(hDevice, // device to be queried
IOCTL_DISK_GET_DRIVE_GEOMETRY, // operation to perform
NULL, 0, // no input buffer
pdg, sizeof(*pdg), // output buffer
&junk, // # bytes returned
(LPOVERLAPPED) NULL); // synchronous I/O
CloseHandle(hDevice);
return (bResult);
}
int main(int argc, char *argv[])
{
DISK_GEOMETRY pdg; // disk drive geometry structure
BOOL bResult; // generic results flag
ULONGLONG DiskSize; // size of the drive, in bytes
bResult = GetDriveGeometry (&pdg);
if (bResult)
{
printf("Cylinders = %I64d/n", pdg.Cylinders);
printf("Tracks per cylinder = %ld/n", (ULONG) pdg.TracksPerCylinder);
printf("Sectors per track = %ld/n", (ULONG) pdg.SectorsPerTrack);
printf("Bytes per sector = %ld/n", (ULONG) pdg.BytesPerSector);
DiskSize = pdg.Cylinders.QuadPart * (ULONG)pdg.TracksPerCylinder *
(ULONG)pdg.SectorsPerTrack * (ULONG)pdg.BytesPerSector;
printf("Disk size = %I64d (Bytes) = %I64d (Mb)/n", DiskSize,
DiskSize / (1024 * 1024));
}
else
{
printf("GetDriveGeometry failed. Error %ld./n", GetLastError());
}
return ((int)bResult);
}
Q 如果将设备名换成“A:”就可以取A盘参数,换成“CDROM0”就可以取CDROM参数,是这样吗?
A 这个问题暂不做回答。请动手试一下。
现在我们总结一下通过DeviceIoControl访问设备驱动程序的“三步曲”:首先用CreateFile取得设备句柄,然后用DeviceIoControl与设备进行I/O,最后别忘记用CloseHandle关闭设备句柄。
实战DeviceIoControl 之二:获取软盘/硬盘/光盘的参数
Q 在MSDN的那个demo中,将设备名换成“A:”取A盘参数,先用资源管理器读一下盘,再运行这个程序可以成功,但换一张盘后就失败;换成“CDROM0”取CDROM参数,无论如何都不行。这个问题如何解决呢?
A 取软盘参数是从软盘上读取格式化后的信息,也就是必须执行读操作,这一点与硬盘不同。将CreateFile中的访问方式改为GENERIC_READ就行了。
IOCTL_DISK_GET_DRIVE_GEOMETRY这个I/O控制码,对软盘和硬盘有效,但对一些可移动媒介如CD/DVD-ROM、TAPE等就不管用了。要取CDROM参数,还得另辟蹊径。IOCTL_STORAGE_GET_MEDIA_TYPES_EX能够帮我们解决问题。
Q 使用这些I/O控制码,需要什么样的输入输出数据格式呢?
A DeviceIoControl使用这两个控制码时,都不需要输入数据。
IOCTL_DISK_GET_DRIVE_GEOMETRY直接输出一个DISK_GEOMETRY结构:
typedef struct _DISK_GEOMETRY {
LARGE_INTEGER Cylinders;
// 柱面数
MEDIA_TYPE MediaType;
// 介质类型
DWORD TracksPerCylinder;
// 每柱面的磁道数
DWORD SectorsPerTrack;
// 每磁道的扇区数
DWORD BytesPerSector;
// 每扇区的字节数
} DISK_GEOMETRY;
IOCTL_STORAGE_GET_MEDIA_TYPES_EX输出一个GET_MEDIA_TYPES结构:
typedef struct _GET_MEDIA_TYPES {
DWORD DeviceType;
// 设备类型
DWORD MediaInfoCount;
// 介质信息条数
DEVICE_MEDIA_INFO MediaInfo[
1];
// 介质信息
} GET_MEDIA_TYPES;
让我们来看一下DEVICE_MEDIA_INFO结构的定义:
typedef struct _DEVICE_MEDIA_INFO {
union {
struct {
LARGE_INTEGER Cylinders;
// 柱面数
STORAGE_MEDIA_TYPE MediaType;
// 介质类型
DWORD TracksPerCylinder;
// 每柱面的磁道数
DWORD SectorsPerTrack;
// 每磁道的扇区数
DWORD BytesPerSector;
// 每扇区的字节数
DWORD NumberMediaSides;
// 介质面数
DWORD MediaCharacteristics;
// 介质特性
} DiskInfo;
// 硬盘信息
struct {
LARGE_INTEGER Cylinders;
// 柱面数
STORAGE_MEDIA_TYPE MediaType;
// 介质类型
DWORD TracksPerCylinder;
// 每柱面的磁道数
DWORD SectorsPerTrack;
// 每磁道的扇区数
DWORD BytesPerSector;
// 每扇区的字节数
DWORD NumberMediaSides;
// 介质面数
DWORD MediaCharacteristics;
// 介质特性
} RemovableDiskInfo;
// “可移动盘”信息
struct {
STORAGE_MEDIA_TYPE MediaType;
// 介质类型
DWORD MediaCharacteristics;
// 介质特性
DWORD CurrentBlockSize;
// 块的大小
} TapeInfo;
// 磁带信息
} DeviceSpecific;
} DEVICE_MEDIA_INFO;
其中CD-ROM属于“可移动盘”的范围。请注意,GET_MEDIA_TYPES结构本身只定义了一条DEVICE_MEDIA_INFO,额外的DEVICE_MEDIA_INFO需要紧接此结构的另外的空间。
Q 调用方法我了解了,请用VC举个例子来实现我所期待已久的功能吧?
A 好,现在就演示一下如何取软盘/硬盘/光盘的参数。测试时,记得要有软盘/光盘插在驱动器里喔!
首先,用MFC AppWizard生成一个单文档的应用程序,取名为DiskGeometry,让它的View基于CEditView。
然后,添加以下的.h和.cpp文件。
//////////////////////////////////////////////////////////////////////////////
// GetDiskGeometry.h
//////////////////////////////////////////////////////////////////////////////
#if !
defined(GET_DISK_GEOMETRY_H__)
#define GET_DISK_GEOMETRY_H__
#if _MSC_VER >
1000
#pragma once
#endif
// _MSC_VER > 1000
#include
<winioctl.h>
BOOL GetDriveGeometry(
const char* filename, DISK_GEOMETRY *pdg);
#endif
// !defined(GET_DISK_GEOMETRY_H__)
//////////////////////////////////////////////////////////////////////////////
// GetDiskGeometry.cpp
//////////////////////////////////////////////////////////////////////////////
#include
"stdafx.h"
#include
"GetDiskGeometry.h"
// IOCTL_STORAGE_GET_MEDIA_TYPES_EX可能返回不止一条DEVICE_MEDIA_INFO,故定义足够的空间
#define MEDIA_INFO_SIZE
sizeof(GET_MEDIA_TYPES)+
15*
sizeof(DEVICE_MEDIA_INFO)
// filename -- 用于设备的文件名
// pdg -- 参数缓冲区指针
BOOL GetDriveGeometry(
const char* filename, DISK_GEOMETRY *pdg)
{
HANDLE hDevice;
// 设备句柄
BOOL bResult;
// DeviceIoControl的返回结果
GET_MEDIA_TYPES *pmt;
// 内部用的输出缓冲区
DWORD dwOutBytes;
// 输出数据长度
// 打开设备
hDevice = ::CreateFile(filename,
// 文件名
GENERIC_READ,
// 软驱需要读盘
FILE_SHARE_READ | FILE_SHARE_WRITE,
// 共享方式
NULL,
// 默认的安全描述符
OPEN_EXISTING,
// 创建方式
0,
// 不需设置文件属性
NULL);
// 不需参照模板文件
if (hDevice == INVALID_HANDLE_VALUE)
{
// 设备无法打开...
return FALSE;
}
// 用IOCTL_DISK_GET_DRIVE_GEOMETRY取磁盘参数
bResult = ::DeviceIoControl(hDevice,
// 设备句柄
IOCTL_DISK_GET_DRIVE_GEOMETRY,
// 取磁盘参数
NULL,
0,
// 不需要输入数据
pdg, sizeof(DISK_GEOMETRY),
// 输出数据缓冲区
&dwOutBytes,
// 输出数据长度
(LPOVERLAPPED)NULL);
// 用同步I/O
// 如果失败,再用IOCTL_STORAGE_GET_MEDIA_TYPES_EX取介质类型参数
if (!bResult)
{
pmt = (GET_MEDIA_TYPES *)
new BYTE[MEDIA_INFO_SIZE];
bResult = ::DeviceIoControl(hDevice,
// 设备句柄
IOCTL_STORAGE_GET_MEDIA_TYPES_EX,
// 取介质类型参数
NULL,
0,
// 不需要输入数据
pmt, MEDIA_INFO_SIZE,
// 输出数据缓冲区
&dwOutBytes,
// 输出数据长度
(LPOVERLAPPED)NULL);
// 用同步I/O
if (bResult)
{
// 注意到结构DEVICE_MEDIA_INFO是在结构DISK_GEOMETRY的基础上扩充的
// 为简化程序,用memcpy代替如下多条赋值语句:
// pdg->MediaType = (MEDIA_TYPE)pmt->MediaInfo[0].DeviceSpecific.DiskInfo.MediaType;
// pdg->Cylinders = pmt->MediaInfo[0].DeviceSpecific.DiskInfo.Cylinders;
// pdg->TracksPerCylinder = pmt->MediaInfo[0].DeviceSpecific.DiskInfo.TracksPerCylinder;
// ... ...
::memcpy(pdg, pmt->MediaInfo,
sizeof(DISK_GEOMETRY));
}
delete pmt;
}
// 关闭设备句柄
::CloseHandle(hDevice);
return (bResult);
}
然后,在Toolbar的IDR_MAINFRAME上添加一个按钮,ID为ID_GET_DISK_GEOMETRY。打开ClassWizard,在DiskGeometryView中
添加ID_GET_DISK_GEOMETRY的映射函数OnGetDiskGeometry。打开DiskGeometryView.cpp,包含头文件GetDiskGeometry.h。
在OnGetDiskGeometry中,添加以下代码
const char *szDevName[]=
{
"////.//A:",
"////.//B:",
"////.//PhysicalDrive0",
"////.//PhysicalDrive1",
"////.//PhysicalDrive2",
"////.//PhysicalDrive3",
"////.//Cdrom0",
"////.//Cdrom1",
};
DISK_GEOMETRY dg;
ULONGLONG DiskSize;
BOOL bResult;
CString strMsg;
CString strTmp;
for (
int i =
0; i <
sizeof(szDevName)/
sizeof(
char*); i++)
{
bResult = GetDriveGeometry(szDevName[i], &dg);
strTmp.Format(
"/r/n%s result = %s/r/n", szDevName[i], bResult ?
"success" :
"failure");
strMsg+=strTmp;
if (!bResult)
continue;
strTmp.Format(
" Media Type = %d/r/n", dg.MediaType);
strMsg+=strTmp;
strTmp.Format(
" Cylinders = %I64d/r/n", dg.Cylinders);
strMsg+=strTmp;
strTmp.Format(
" Tracks per cylinder = %ld/r/n", (ULONG) dg.TracksPerCylinder);
strMsg+=strTmp;
strTmp.Format(
" Sectors per track = %ld/r/n", (ULONG) dg.SectorsPerTrack);
strMsg+=strTmp;
strTmp.Format(
" Bytes per sector = %ld/r/n", (ULONG) dg.BytesPerSector);
strMsg+=strTmp;
DiskSize = dg.Cylinders.QuadPart * (ULONG)dg.TracksPerCylinder *
(ULONG)dg.SectorsPerTrack * (ULONG)dg.BytesPerSector;
strTmp.Format(
" Disk size = %I64d (Bytes) = %I64d (Mb)/r/n", DiskSize, DiskSize / (
1024 *
1024));
strMsg+=strTmp;
}
CEdit& Edit = GetEditCtrl();
Edit.SetWindowText(strMsg);
最后,最后干什么呢?编译,运行......
实战DeviceIoControl 之三:制作磁盘镜像文件
Q DOS命令DISKCOPY给我很深的印象,现在也有许多“克隆”软件,可以对磁盘进行全盘复制。我想,要制作磁盘镜像文件,DeviceIoControl应该很有用武之地吧?
A 是的。这里举一个制作软盘镜像文件,功能类似于“DISKCOPY”的例子。
本例实现其功能的核心代码如下:
// 打开磁盘
HANDLE OpenDisk(LPCTSTR filename)
{
HANDLE hDisk;
// 打开设备
hDisk = ::CreateFile(filename,
// 文件名
GENERIC_READ | GENERIC_WRITE,
// 读写方式
FILE_SHARE_READ | FILE_SHARE_WRITE,
// 共享方式
NULL,
// 默认的安全描述符
OPEN_EXISTING,
// 创建方式
0,
// 不需设置文件属性
NULL);
// 不需参照模板文件
return hDisk;
}
// 获取磁盘参数
BOOL GetDiskGeometry(HANDLE hDisk, PDISK_GEOMETRY lpGeometry)
{
DWORD dwOutBytes;
BOOL bResult;
// 用IOCTL_DISK_GET_DRIVE_GEOMETRY取磁盘参数
bResult = ::DeviceIoControl(hDisk,
// 设备句柄
IOCTL_DISK_GET_DRIVE_GEOMETRY,
// 取磁盘参数
NULL,
0,
// 不需要输入数据
lpGeometry,
sizeof(DISK_GEOMETRY),
// 输出数据缓冲区
&dwOutBytes,
// 输出数据长度
(LPOVERLAPPED)NULL);
// 用同步I/O
return bResult;
}
// 从指定磁道开始读磁盘
BOOL ReadTracks(HANDLE hDisk, PDISK_GEOMETRY lpGeometry, LPVOID pBuf, DWORD dwStartCylinder, DWORD dwCylinderNumber)
{
DWORD VirtBufSize;
DWORD BytesRead;
// 大小
VirtBufSize = lpGeometry->TracksPerCylinder * lpGeometry->SectorsPerTrack * lpGeometry->BytesPerSector;
// 偏移
::SetFilePointer(hDisk, VirtBufSize*dwStartCylinder, NULL, FILE_BEGIN);
return ::ReadFile(hDisk, pBuf, VirtBufSize*dwCylinderNumber, &BytesRead, NULL);
}
// 从指定磁道开始写磁盘
BOOL WriteTracks(HANDLE hDisk, PDISK_GEOMETRY lpGeometry, LPVOID pBuf, DWORD dwStartCylinder, DWORD dwCylinderNumber)
{
DWORD VirtBufSize;
DWORD BytesWritten;
// 大小
VirtBufSize = lpGeometry->TracksPerCylinder * lpGeometry->SectorsPerTrack * lpGeometry->BytesPerSector;
// 偏移
::SetFilePointer(hDisk, VirtBufSize*dwStartCylinder, NULL, FILE_BEGIN);
return ::WriteFile(hDisk, pBuf, VirtBufSize*dwCylinderNumber, &BytesWritten, NULL);
}
// 从指定磁道开始格式化磁盘
BOOL LowLevelFormatTracks(HANDLE hDisk, PDISK_GEOMETRY lpGeometry, DWORD dwStartCylinder, DWORD dwCylinderNumber)
{
FORMAT_PARAMETERS FormatParameters;
PBAD_TRACK_NUMBER lpBadTrack;
DWORD dwOutBytes;
DWORD dwBufSize;
BOOL bResult;
FormatParameters.MediaType = lpGeometry->MediaType;
FormatParameters.StartCylinderNumber = dwStartCylinder;
FormatParameters.EndCylinderNumber = dwStartCylinder + dwCylinderNumber -
1;
FormatParameters.StartHeadNumber =
0;
FormatParameters.EndHeadNumber = lpGeometry->TracksPerCylinder -
1;
dwBufSize = lpGeometry->TracksPerCylinder *
sizeof(BAD_TRACK_NUMBER);
lpBadTrack = (PBAD_TRACK_NUMBER) new BYTE[dwBufSize];
// 用IOCTL_DISK_FORMAT_TRACKS对连续磁道进行低级格式化
bResult = ::DeviceIoControl(hDisk,
// 设备句柄
IOCTL_DISK_FORMAT_TRACKS,
// 低级格式化
&FormatParameters,
sizeof(FormatParameters),
// 输入数据缓冲区
lpBadTrack, dwBufSize,
// 输出数据缓冲区
&dwOutBytes,
// 输出数据长度
(LPOVERLAPPED)NULL);
// 用同步I/O
delete lpBadTrack;
return bResult;
}
// 将卷锁定
BOOL LockVolume(HANDLE hDisk)
{
DWORD dwOutBytes;
BOOL bResult;
// 用FSCTL_LOCK_VOLUME锁卷
bResult = ::DeviceIoControl(hDisk,
// 设备句柄
FSCTL_LOCK_VOLUME,
// 锁卷
NULL,
0,
// 不需要输入数据
NULL,
0,
// 不需要输出数据
&dwOutBytes,
// 输出数据长度
(LPOVERLAPPED)NULL);
// 用同步I/O
return bResult;
}
// 将卷解锁
BOOL UnlockVolume(HANDLE hDisk)
{
DWORD dwOutBytes;
BOOL bResult;
// 用FSCTL_UNLOCK_VOLUME开卷锁
bResult = ::DeviceIoControl(hDisk,
// 设备句柄
FSCTL_UNLOCK_VOLUME,
// 开卷锁
NULL,
0,
// 不需要输入数据
NULL,
0,
// 不需要输出数据
&dwOutBytes,
// 输出数据长度
(LPOVERLAPPED)NULL);
// 用同步I/O
return bResult;
}
// 将卷卸下
// 该操作使系统重新辨识磁盘,等效于重新插盘
BOOL DismountVolume(HANDLE hDisk)
{
DWORD dwOutBytes;
BOOL bResult;
// 用FSCTL_DISMOUNT_VOLUME卸卷
bResult = ::DeviceIoControl(hDisk,
// 设备句柄
FSCTL_DISMOUNT_VOLUME,
// 卸卷
NULL,
0,
// 不需要输入数据
NULL,
0,
// 不需要输出数据
&dwOutBytes,
// 输出数据长度
(LPOVERLAPPED)NULL);
// 用同步I/O
return bResult;
}
将软盘保存成镜像文件的步骤简单描述为:
1、创建空的镜像文件。
2、调用OpenDisk打开软盘。成功转3,失败转8。
3、调用LockVolume将卷锁定。成功转4,失败转7。
4、调用GetDiskGeometry获取参数。成功转5,失败转6。
5、将磁盘参数写入镜像文件作为文件头。调用ReadTracks按柱面读出数据,保存在镜像文件中。循环次数等于柱面数。
6、调用UnlockVolume将卷解锁。
7、调用CloseDisk关闭软盘。
8、关闭镜像文件。
将镜像文件载入软盘的步骤简单描述为:
1、打开镜像文件。
2、调用OpenDisk打开软盘。成功转3,失败转11。
3、调用LockVolume将卷锁定。成功转4,失败转10。
4、调用GetDiskGeometry获取参数。成功转5,失败转9。
5、从镜像文件中读出文件头,判断两个磁盘参数是否一致。不一致转6,否则转7。
6、调用LowLevelFormatTracks按柱面格式化软盘。循环次数等于柱面数。成功转7,失败转8。
7、从镜像文件中读出数据,并调用WriteTracks按柱面写入磁盘。循环次数等于柱面数。
8、调用DismountVolume将卷卸下。
9、调用UnlockVolume将卷解锁。
10、调用CloseDisk关闭软盘。
11、关闭镜像文件。
Q 我注意到,磁盘读写和格式化是按柱面进行的,有什么道理吗?
A 没有特别的原因,只是因为在这个例子中可以方便地显示处理进度。
有一点需要特别提及,按绝对地址读写磁盘数据时,“最小单位”是扇区,地址一定要与扇区对齐,长度也要等于扇区长度的整数倍。比如,每扇区512字节,那么起始地址和数据长度都应能被512整除才行。
Q 我忽然产生了一个伟大的想法,用绝对地址读写的方式使用磁盘,包括U盘啦,MO啦,而不是用现成的文件系统,那不是可以将数据保密了吗?
A 当然,只要你喜欢。可千万别在你的系统盘上做试验,否则......可别怪bhw98没有提醒过你喔!
Q 我知道怎么测试光驱的传输速度了,就用上面的方法,读出一定长度数据,除以所需时间,应该可以吧?
A 可以。但取光盘参数时要用IOCTL_STORAGE_GET_MEDIA_TYPES_EX,我们已经探讨过的。
实战DeviceIoControl 之四:获取硬盘的详细信息
Q 用IOCTL_DISK_GET_DRIVE_GEOMETRY或IOCTL_STORAGE_GET_MEDIA_TYPES_EX只能得到很少的磁盘参数,我想获得包括硬盘序列号在内的更加详细的信息,有什么办法呀?
A 确实,用你所说的I/O控制码,只能得到最基本的磁盘参数。获取磁盘出厂信息的I/O控制码,微软在VC/MFC环境中没有开放,在DDK中可以发现一些线索。早先,Lynn McGuire写了一个很出名的获取IDE硬盘详细信息的程序DiskID32,下面的例子是在其基础上经过增删和改进而成的。
本例中,我们要用到ATA/APAPI的IDENTIFY DEVICE指令。ATA/APAPI是国际组织T13起草和发布的IDE/EIDE/UDMA硬盘及其它可移动存储设备与主机接口的标准,至今已经到了ATA/APAPI-7版本。该接口标准规定了ATA/ATAPI设备的输入输出寄存器和指令集。欲了解更详细的ATA/ATAPI技术资料,可访问T13的站点。
用到的常量及数据结构有以下一些:
// IOCTL控制码
// #define DFP_SEND_DRIVE_COMMAND 0x0007c084
#define DFP_SEND_DRIVE_COMMAND CTL_CODE(IOCTL_DISK_BASE,
0x0021, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
// #define DFP_RECEIVE_DRIVE_DATA 0x0007c088
#define DFP_RECEIVE_DRIVE_DATA CTL_CODE(IOCTL_DISK_BASE,
0x0022, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define FILE_DEVICE_SCSI
0x0000001B
#define IOCTL_SCSI_MINIPORT_IDENTIFY ((FILE_DEVICE_SCSI <<
16) +
0x0501)
#define IOCTL_SCSI_MINIPORT
0x0004D008
// see NTDDSCSI.H for definition
// ATA/ATAPI指令
#define IDE_ATA_IDENTIFY
0xEC
// ATA的ID指令(IDENTIFY DEVICE)
// IDE命令寄存器
typedef
struct _IDEREGS
{
BYTE bFeaturesReg;
// 特征寄存器(用于SMART命令)
BYTE bSectorCountReg;
// 扇区数目寄存器
BYTE bSectorNumberReg;
// 开始扇区寄存器
BYTE bCylLowReg;
// 开始柱面低字节寄存器
BYTE bCylHighReg;
// 开始柱面高字节寄存器
BYTE bDriveHeadReg;
// 驱动器/磁头寄存器
BYTE bCommandReg;
// 指令寄存器
BYTE bReserved;
// 保留
} IDEREGS, *PIDEREGS, *LPIDEREGS;
// 从驱动程序返回的状态
typedef
struct _DRIVERSTATUS
{
BYTE bDriverError;
// 错误码
BYTE bIDEStatus;
// IDE状态寄存器
BYTE bReserved[
2];
// 保留
DWORD dwReserved[
2];
// 保留
} DRIVERSTATUS, *PDRIVERSTATUS, *LPDRIVERSTATUS;
// IDE设备IOCTL输入数据结构
typedef
struct _SENDCMDINPARAMS
{
DWORD cBufferSize;
// 缓冲区字节数
IDEREGS irDriveRegs;
// IDE寄存器组
BYTE bDriveNumber;
// 驱动器号
BYTE bReserved[
3];
// 保留
DWORD dwReserved[
4];
// 保留
BYTE bBuffer[
1];
// 输入缓冲区(此处象征性地包含1字节)
} SENDCMDINPARAMS, *PSENDCMDINPARAMS, *LPSENDCMDINPARAMS;
// IDE设备IOCTL输出数据结构
typedef
struct _SENDCMDOUTPARAMS
{
DWORD cBufferSize;
// 缓冲区字节数
DRIVERSTATUS DriverStatus;
// 驱动程序返回状态
BYTE bBuffer[
1];
// 输入缓冲区(此处象征性地包含1字节)
} SENDCMDOUTPARAMS, *PSENDCMDOUTPARAMS, *LPSENDCMDOUTPARAMS;
// IDE的ID命令返回的数据
// 共512字节(256个WORD),这里仅定义了一些感兴趣的项(基本上依据ATA/ATAPI-4)
typedef
struct _IDINFO
{
USHORT wGenConfig;
// WORD 0: 基本信息字
USHORT wNumCyls;
// WORD 1: 柱面数
USHORT wReserved2;
// WORD 2: 保留
USHORT wNumHeads;
// WORD 3: 磁头数
USHORT wReserved4;
// WORD 4: 保留
USHORT wReserved5;
// WORD 5: 保留
USHORT wNumSectorsPerTrack;
// WORD 6: 每磁道扇区数
USHORT wVendorUnique[
3];
// WORD 7-9: 厂家设定值
CHAR sSerialNumber[
20];
// WORD 10-19:序列号
USHORT wBufferType;
// WORD 20: 缓冲类型
USHORT wBufferSize;
// WORD 21: 缓冲大小
USHORT wECCSize;
// WORD 22: ECC校验大小
CHAR sFirmwareRev[
8];
// WORD 23-26: 固件版本
CHAR sModelNumber[
40];
// WORD 27-46: 内部型号
USHORT wMoreVendorUnique;
// WORD 47: 厂家设定值
USHORT wReserved48;
// WORD 48: 保留
struct {
USHORT reserved1:
8;
USHORT DMA:
1;
// 1=支持DMA
USHORT LBA:
1;
// 1=支持LBA
USHORT DisIORDY:
1;
// 1=可不使用IORDY
USHORT IORDY:
1;
// 1=支持IORDY
USHORT SoftReset:
1;
// 1=需要ATA软启动
USHORT Overlap:
1;
// 1=支持重叠操作
USHORT Queue:
1;
// 1=支持命令队列
USHORT InlDMA:
1;
// 1=支持交叉存取DMA
} wCapabilities;
// WORD 49: 一般能力
USHORT wReserved1;
// WORD 50: 保留
USHORT wPIOTiming;
// WORD 51: PIO时序
USHORT wDMATiming;
// WORD 52: DMA时序
struct {
USHORT CHSNumber:
1;
// 1=WORD 54-58有效
USHORT CycleNumber:
1;
// 1=WORD 64-70有效
USHORT UnltraDMA:
1;
// 1=WORD 88有效
USHORT reserved:
13;
} wFieldValidity;
// WORD 53: 后续字段有效性标志
USHORT wNumCurCyls;
// WORD 54: CHS可寻址的柱面数
USHORT wNumCurHeads;
// WORD 55: CHS可寻址的磁头数
USHORT wNumCurSectorsPerTrack;
// WORD 56: CHS可寻址每磁道扇区数
USHORT wCurSectorsLow;
// WORD 57: CHS可寻址的扇区数低位字
USHORT wCurSectorsHigh;
// WORD 58: CHS可寻址的扇区数高位字
struct {
USHORT CurNumber:
8;
// 当前一次性可读写扇区数
USHORT Multi:
1;
// 1=已选择多扇区读写
USHORT reserved1:
7;
} wMultSectorStuff;
// WORD 59: 多扇区读写设定
ULONG dwTotalSectors;
// WORD 60-61: LBA可寻址的扇区数
USHORT wSingleWordDMA;
// WORD 62: 单字节DMA支持能力
struct {
USHORT Mode0:
1;
// 1=支持模式0 (4.17Mb/s)
USHORT Mode1:
1;
// 1=支持模式1 (13.3Mb/s)
USHORT Mode2:
1;
// 1=支持模式2 (16.7Mb/s)
USHORT Reserved1:
5;
USHORT Mode0Sel:
1;
// 1=已选择模式0
USHORT Mode1Sel:
1;
// 1=已选择模式1
USHORT Mode2Sel:
1;
// 1=已选择模式2
USHORT Reserved2:
5;
} wMultiWordDMA;
// WORD 63: 多字节DMA支持能力
struct {
USHORT AdvPOIModes:
8;
// 支持高级POI模式数
USHORT reserved:
8;
} wPIOCapacity;
// WORD 64: 高级PIO支持能力
USHORT wMinMultiWordDMACycle;
// WORD 65: 多字节DMA传输周期的最小值
USHORT wRecMultiWordDMACycle;
// WORD 66: 多字节DMA传输周期的建议值
USHORT wMinPIONoFlowCycle;
// WORD 67: 无流控制时PIO传输周期的最小值
USHORT wMinPOIFlowCycle;
// WORD 68: 有流控制时PIO传输周期的最小值
USHORT wReserved69[
11];
// WORD 69-79: 保留
struct {
USHORT Reserved1:
1;
USHORT ATA1:
1;
// 1=支持ATA-1
USHORT ATA2:
1;
// 1=支持ATA-2
USHORT ATA3:
1;
// 1=支持ATA-3
USHORT ATA4:
1;
// 1=支持ATA/ATAPI-4
USHORT ATA5:
1;
// 1=支持ATA/ATAPI-5
USHORT ATA6:
1;
// 1=支持ATA/ATAPI-6
USHORT ATA7:
1;
// 1=支持ATA/ATAPI-7
USHORT ATA8:
1;
// 1=支持ATA/ATAPI-8
USHORT ATA9:
1;
// 1=支持ATA/ATAPI-9
USHORT ATA10:
1;
// 1=支持ATA/ATAPI-10
USHORT ATA11:
1;
// 1=支持ATA/ATAPI-11
USHORT ATA12:
1;
// 1=支持ATA/ATAPI-12
USHORT ATA13:
1;
// 1=支持ATA/ATAPI-13
USHORT ATA14:
1;
// 1=支持ATA/ATAPI-14
USHORT Reserved2:
1;
} wMajorVersion;
// WORD 80: 主版本
USHORT wMinorVersion;
// WORD 81: 副版本
USHORT wReserved82[
6];
// WORD 82-87: 保留
struct {
USHORT Mode0:
1;
// 1=支持模式0 (16.7Mb/s)
USHORT Mode1:
1;
// 1=支持模式1 (25Mb/s)
USHORT Mode2:
1;
// 1=支持模式2 (33Mb/s)
USHORT Mode3:
1;
// 1=支持模式3 (44Mb/s)
USHORT Mode4:
1;
// 1=支持模式4 (66Mb/s)
USHORT Mode5:
1;
// 1=支持模式5 (100Mb/s)
USHORT Mode6:
1;
// 1=支持模式6 (133Mb/s)
USHORT Mode7:
1;
// 1=支持模式7 (166Mb/s) ???
USHORT Mode0Sel:
1;
// 1=已选择模式0
USHORT Mode1Sel:
1;
// 1=已选择模式1
USHORT Mode2Sel:
1;
// 1=已选择模式2
USHORT Mode3Sel:
1;
// 1=已选择模式3
USHORT Mode4Sel:
1;
// 1=已选择模式4
USHORT Mode5Sel:
1;
// 1=已选择模式5
USHORT Mode6Sel:
1;
// 1=已选择模式6
USHORT Mode7Sel:
1;
// 1=已选择模式7
} wUltraDMA;
// WORD 88: Ultra DMA支持能力
USHORT wReserved89[
167];
// WORD 89-255
} IDINFO, *PIDINFO;
// SCSI驱动所需的输入输出共用的结构
typedef
struct _SRB_IO_CONTROL
{
ULONG HeaderLength;
// 头长度
UCHAR Signature[
8];
// 特征名称
ULONG Timeout;
// 超时时间
ULONG ControlCode;
// 控制码
ULONG ReturnCode;
// 返回码
ULONG Length;
// 缓冲区长度
} SRB_IO_CONTROL, *PSRB_IO_CONTROL;
需要引起注意的是IDINFO第57-58 WORD (CHS可寻址的扇区数),因为不满足32位对齐的要求,不可定义为一个ULONG字段。Lynn McGuire的程序里正是由于定义为一个ULONG字段,导致该结构不可用。
以下是核心代码:
// 打开设备
// filename: 设备的“文件名”(设备路径)
HANDLE OpenDevice(LPCTSTR filename)
{
HANDLE hDevice;
// 打开设备
hDevice = ::CreateFile(filename,
// 文件名
GENERIC_READ | GENERIC_WRITE,
// 读写方式
FILE_SHARE_READ | FILE_SHARE_WRITE,
// 共享方式
NULL,
// 默认的安全描述符
OPEN_EXISTING,
// 创建方式
0,
// 不需设置文件属性
NULL);
// 不需参照模板文件
return hDevice;
}
// 向驱动发“IDENTIFY DEVICE”命令,获得设备信息
// hDevice: 设备句柄
// pIdInfo: 设备信息结构指针
BOOL IdentifyDevice(HANDLE hDevice, PIDINFO pIdInfo)
{
PSENDCMDINPARAMS pSCIP;
// 输入数据结构指针
PSENDCMDOUTPARAMS pSCOP;
// 输出数据结构指针
DWORD dwOutBytes;
// IOCTL输出数据长度
BOOL bResult;
// IOCTL返回值
// 申请输入/输出数据结构空间
pSCIP = (PSENDCMDINPARAMS)::GlobalAlloc(LMEM_ZEROINIT,
sizeof(SENDCMDINPARAMS) -
1);
pSCOP = (PSENDCMDOUTPARAMS)::GlobalAlloc(LMEM_ZEROINIT,
sizeof(SENDCMDOUTPARAMS) +
sizeof(IDINFO) -
1);
// 指定ATA/ATAPI命令的寄存器值
// pSCIP->irDriveRegs.bFeaturesReg = 0;
// pSCIP->irDriveRegs.bSectorCountReg = 0;
// pSCIP->irDriveRegs.bSectorNumberReg = 0;
// pSCIP->irDriveRegs.bCylLowReg = 0;
// pSCIP->irDriveRegs.bCylHighReg = 0;
// pSCIP->irDriveRegs.bDriveHeadReg = 0;
pSCIP->irDriveRegs.bCommandReg = IDE_ATA_IDENTIFY;
// 指定输入/输出数据缓冲区大小
pSCIP->cBufferSize =
0;
pSCOP->cBufferSize =
sizeof(IDINFO);
// IDENTIFY DEVICE
bResult = ::DeviceIoControl(hDevice,
// 设备句柄
DFP_RECEIVE_DRIVE_DATA,
// 指定IOCTL
pSCIP,
sizeof(SENDCMDINPARAMS) -
1,
// 输入数据缓冲区
pSCOP,
sizeof(SENDCMDOUTPARAMS) +
sizeof(IDINFO) -
1,
// 输出数据缓冲区
&dwOutBytes,
// 输出数据长度
(LPOVERLAPPED)NULL);
// 用同步I/O
// 复制设备参数结构
::memcpy(pIdInfo, pSCOP->bBuffer,
sizeof(IDINFO));
// 释放输入/输出数据空间
::GlobalFree(pSCOP);
::GlobalFree(pSCIP);
return bResult;
}
// 向SCSI MINI-PORT驱动发“IDENTIFY DEVICE”命令,获得设备信息
// hDevice: 设备句柄
// pIdInfo: 设备信息结构指针
BOOL IdentifyDeviceAsScsi(HANDLE hDevice,
int nDrive, PIDINFO pIdInfo)
{
PSENDCMDINPARAMS pSCIP;
// 输入数据结构指针
PSENDCMDOUTPARAMS pSCOP;
// 输出数据结构指针
PSRB_IO_CONTROL pSRBIO;
// SCSI输入输出数据结构指针
DWORD dwOutBytes;
// IOCTL输出数据长度
BOOL bResult;
// IOCTL返回值
// 申请输入/输出数据结构空间
pSRBIO = (PSRB_IO_CONTROL)::GlobalAlloc(LMEM_ZEROINIT,
sizeof(SRB_IO_CONTROL) +
sizeof(SENDCMDOUTPARAMS) +
sizeof(IDINFO) -
1);
pSCIP = (PSENDCMDINPARAMS)((
char *)pSRBIO +
sizeof(SRB_IO_CONTROL));
pSCOP = (PSENDCMDOUTPARAMS)((
char *)pSRBIO +
sizeof(SRB_IO_CONTROL));
// 填充输入/输出数据
pSRBIO->HeaderLength =
sizeof(SRB_IO_CONTROL);
pSRBIO->Timeout =
10000;
pSRBIO->Length =
sizeof(SENDCMDOUTPARAMS) +
sizeof(IDINFO) -
1;
pSRBIO->ControlCode = IOCTL_SCSI_MINIPORT_IDENTIFY;
::strncpy ((
char *)pSRBIO->Signature,
"SCSIDISK",
8);
// 指定ATA/ATAPI命令的寄存器值
// pSCIP->irDriveRegs.bFeaturesReg = 0;
// pSCIP->irDriveRegs.bSectorCountReg = 0;
// pSCIP->irDriveRegs.bSectorNumberReg = 0;
// pSCIP->irDriveRegs.bCylLowReg = 0;
// pSCIP->irDriveRegs.bCylHighReg = 0;
// pSCIP->irDriveRegs.bDriveHeadReg = 0;
pSCIP->irDriveRegs.bCommandReg = IDE_ATA_IDENTIFY;
pSCIP->bDriveNumber = nDrive;
// IDENTIFY DEVICE
bResult = ::DeviceIoControl(hDevice,
// 设备句柄
IOCTL_SCSI_MINIPORT,
// 指定IOCTL
pSRBIO,
sizeof(SRB_IO_CONTROL) +
sizeof(SENDCMDINPARAMS) -
1,
// 输入数据缓冲区
pSRBIO,
sizeof(SRB_IO_CONTROL) +
sizeof(SENDCMDOUTPARAMS) +
sizeof(IDINFO) -
1,
// 输出数据缓冲区
&dwOutBytes,
// 输出数据长度
(LPOVERLAPPED)NULL);
// 用同步I/O
// 复制设备参数结构
::memcpy(pIdInfo, pSCOP->bBuffer,
sizeof(IDINFO));
// 释放输入/输出数据空间
::GlobalFree(pSRBIO);
return bResult;
}
// 将串中的字符两两颠倒
// 原因是ATA/ATAPI中的WORD,与Windows采用的字节顺序相反
// 驱动程序中已经将收到的数据全部反过来,我们来个负负得正
void AdjustString(
char* str,
int len)
{
char ch;
int i;
// 两两颠倒
for (i =
0; i < len; i +=
2)
{
ch = str[i];
str[i] = str[i +
1];
str[i +
1] = ch;
}
// 若是右对齐的,调整为左对齐 (去掉左边的空格)
i =
0;
while ((i < len) && (str[i] ==
' ')) i++;
::memmove(str, &str[i], len - i);
// 去掉右边的空格
i = len -
1;
while ((i >=
0) && (str[i] ==
' '))
{
str[i] =
'/0';
i--;
}
}
// 读取IDE硬盘的设备信息,必须有足够权限
// nDrive: 驱动器号(0=第一个硬盘,1=0=第二个硬盘,......)
// pIdInfo: 设备信息结构指针
BOOL GetPhysicalDriveInfoInNT(
int nDrive, PIDINFO pIdInfo)
{
HANDLE hDevice;
// 设备句柄
BOOL bResult;
// 返回结果
char szFileName[
20];
// 文件名
::sprintf(szFileName,
"////.//PhysicalDrive%d", nDrive);
hDevice = ::OpenDevice(szFileName);
if (hDevice == INVALID_HANDLE_VALUE)
{
return FALSE;
}
// IDENTIFY DEVICE
bResult = ::IdentifyDevice(hDevice, pIdInfo);
if (bResult)
{
// 调整字符串
::AdjustString(pIdInfo->sSerialNumber,
20);
::AdjustString(pIdInfo->sModelNumber,
40);
::AdjustString(pIdInfo->sFirmwareRev,
8);
}
::CloseHandle (hDevice);
return bResult;
}
// 用SCSI驱动读取IDE硬盘的设备信息,不受权限制约
// nDrive: 驱动器号(0=Primary Master, 1=Promary Slave, 2=Secondary master, 3=Secondary slave)
// pIdInfo: 设备信息结构指针
BOOL GetIdeDriveAsScsiInfoInNT(
int nDrive, PIDINFO pIdInfo)
{
HANDLE hDevice;
// 设备句柄
BOOL bResult;
// 返回结果
char szFileName[
20];
// 文件名
::sprintf(szFileName,
"////.//Scsi%d:", nDrive/
2);
hDevice = ::OpenDevice(szFileName);
if (hDevice == INVALID_HANDLE_VALUE)
{
return FALSE;
}
// IDENTIFY DEVICE
bResult = ::IdentifyDeviceAsScsi(hDevice, nDrive%
2, pIdInfo);
// 检查是不是空串
if (pIdInfo->sModelNumber[
0] ==
'/0')
{
bResult = FALSE;
}
if (bResult)
{
// 调整字符串
::AdjustString(pIdInfo->sSerialNumber,
20);
::AdjustString(pIdInfo->sModelNumber,
40);
::AdjustString(pIdInfo->sFirmwareRev,
8);
}
return bResult;
}
Q 我注意到ATA/ATAPI里,以及DiskID32里,有一个“IDENTIFY PACKET DEVICE”指令,与“IDENTIFY DEVICE”有什么区别?
A IDENTIFY DEVICE专门用于固定硬盘,而IDENTIFY PACKET DEVICE用于可移动存储设备如CDROM、CF、MO、ZIP、TAPE等。因为驱动程序的原因,实际上用本例的方法,不管是IDENTIFY DEVICE也好,IDENTIFY PACKET DEVICE也好,获取可移动存储设备的详细信息,一般是做不到的。而且除了IDE硬盘,对SCSI、USB等接口的硬盘也不起作用。除非厂商提供的驱动支持这样的功能。
Q ATA/ATAPI有很多指令,如READ SECTORS, WRITE SECTORS, SECURITY, SLEEP, STANDBY等,利用上述方法,是否可进行相应操作?
A 应该没问题。但切记,要慎重慎重再慎重!
Q 关于权限问题,请解释一下好吗?
A 在NT/2000/XP下,administrator可以管理设备,上述两种访问驱动的方法都行。但在user身份下,或者登录到域后,用户无法访问PhysicalDrive驱动的核心层,但SCSI MINI-PORT驱动却可以。目前是可以,不知道Windows以后的版本是否支持。因为这肯定是一个安全隐患。
另外,我们着重讨论NT/2000/XP中DeviceIoControl的应用,如果需要在98/ME中得到包括硬盘序列号在内的更加详细的信息,请参考DiskID32。
实战DeviceIoControl 之五:列举已安装的存储设备
Q 前几次我们讨论的都是设备名比较清楚的情况,有了设备名(路径),就可以直接调用CreateFile打开设备,进行它所支持的I/O操作了。如果事先并不能确切知道设备名,如何去访问设备呢?
A 访问设备必须用设备句柄,而得到设备句柄必须知道设备路径,这个套路以你我之力是改变不了的。每个设备都有它所属类型的GUID,我们顺着这个GUID就能获得设备路径。
GUID是同类或同种设备的全球唯一识别码,它是一个128 bit(16字节)的整形数,真实面目为
typedef
struct _GUID
{
unsigned
long Data1;
unsigned
short Data2;
unsigned
short Data3;
unsigned
char Data4[
8];
} GUID, *PGUID;
例如,Disk类的GUID为“53f56307-b6bf-11d0-94f2-00a0c91efb8b”,在我们的程序里可以定义为
const GUID DiskClassGuid = {0x53f56307L,
0xb6bf,
0x11d0, {
0x94,
0xf2,
0x00,
0xa0,
0xc9,
0x1e,
0xfb,
0x8b)};
或者用一个宏来定义
DEFINE_GUID(DiskClassGuid, 0x53f56307L,
0xb6bf,
0x11d0,
0x94,
0xf2,
0x00,
0xa0,
0xc9,
0x1e,
0xfb,
0x8b);
通过GUID找出设备路径,需要用到一组设备管理的API函数
SetupDiGetClassDevs, SetupDiEnumDeviceInterfaces, SetupDiGetInterfaceDeviceDetail, SetupDiDestroyDeviceInfoList,
以及结构SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA。
有关信息请查阅MSDN,这里就不详细介绍了。
实现GUID到设备路径的代码如下:
// SetupDiGetInterfaceDeviceDetail所需要的输出长度,定义足够大
#define INTERFACE_DETAIL_SIZE (
1024)
// 根据GUID获得设备路径
// lpGuid: GUID指针
// pszDevicePath: 设备路径指针的指针
// 返回: 成功得到的设备路径个数,可能不止1个
int GetDevicePath(LPGUID lpGuid, LPTSTR* pszDevicePath)
{
HDEVINFO hDevInfoSet;
SP_DEVICE_INTERFACE_DATA ifdata;
PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail;
int nCount;
BOOL bResult;
// 取得一个该GUID相关的设备信息集句柄
hDevInfoSet = ::SetupDiGetClassDevs(lpGuid,
// class GUID
NULL,
// 无关键字
NULL,
// 不指定父窗口句柄
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
// 目前存在的设备
// 失败...
if (hDevInfoSet == INVALID_HANDLE_VALUE)
{
return
0;
}
// 申请设备接口数据空间
pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)::GlobalAlloc(LMEM_ZEROINIT, INTERFACE_DETAIL_SIZE);
pDetail->cbSize =
sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
nCount =
0;
bResult = TRUE;
// 设备序号=0,1,2... 逐一测试设备接口,到失败为止
while (bResult)
{
ifdata.cbSize =
sizeof(ifdata);
// 枚举符合该GUID的设备接口
bResult = ::SetupDiEnumDeviceInterfaces(
hDevInfoSet,
// 设备信息集句柄
NULL,
// 不需额外的设备描述
lpGuid,
// GUID
(ULONG)nCount,
// 设备信息集里的设备序号
&ifdata);
// 设备接口信息
if (bResult)
{
// 取得该设备接口的细节(设备路径)
bResult = SetupDiGetInterfaceDeviceDetail(
hDevInfoSet,
// 设备信息集句柄
&ifdata,
// 设备接口信息
pDetail,
// 设备接口细节(设备路径)
INTERFACE_DETAIL_SIZE,
// 输出缓冲区大小
NULL,
// 不需计算输出缓冲区大小(直接用设定值)
NULL);
// 不需额外的设备描述
if (bResult)
{
// 复制设备路径到输出缓冲区
::strcpy(pszDevicePath[nCount], pDetail->DevicePath);
// 调整计数值
nCount++;
}
}
}
// 释放设备接口数据空间
::GlobalFree(pDetail);
// 关闭设备信息集句柄
::SetupDiDestroyDeviceInfoList(hDevInfoSet);
return nCount;
}
调用GetDevicePath函数时要注意,pszDevicePath是个指向字符串指针的指针,例如可以这样
int i;
char* szDevicePath[MAX_DEVICE];
// 设备路径
// 分配需要的空间
for (i =
0; i < MAX_DEVICE; i++)
{
szDevicePath[i] =
new
char[
256];
}
// 取设备路径
nDevice = ::GetDevicePath((LPGUID)&DiskClassGuid, szDevicePath);
// 逐一获取设备信息
for (i =
0; i < nDevice; i++)
{
// 打开设备
hDevice = ::OpenDevice(szDevicePath[i]);
if (hDevice != INVALID_HANDLE_VALUE)
{
... ...
// I/O操作
::CloseHandle(hDevice);
}
}
// 释放空间
for (i =
0; i & lt; MAX_DEVICE; i++)
{
delete []szDevicePath[i];
}
本例的Project中除了要包含winioctl.h外,还要包含initguid.h,setupapi.h,以及连接setupapi.lib。
Q 得到设备路径后,就可以到下一步,用CreateFile打开设备,然后用DeviceIoControl进行读写了吧?
A 是的。尽管该设备路径与以前我们接触的那些不太一样。本是“//./PhysicalDrive0”,现在鸟枪换炮,变成了类似这样的一副尊容:
“//?/ide#diskmaxtor_2f040j0__________________________vam51jj0#3146563447534558202020202020202020202020#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}”。
其实这个设备名在注册表的某处可以找到,例如在Win2000中这个名字可以位于
HKEY_LOCAL_MACHINE/System/CurrentControlSet/Services/Disk/Enum/0,
只不过“#”换成了“/”。分析一下这样的设备路径,你会发现很有趣的东西,它们是由接口类型、产品型号、固件版本、序列号、计算机名、GUID等信息组合而成的。当然,它是没有规范的,不能指望从这里面得到你希望知道的东西。
用CreateFile打开设备后,对于存储设备,IOCTL_DISK_GET_DRIVE_GEOMETRY,IOCTL_STORAGE_GET_MEDIA_TYPES_EX等I/O控制码照常使用。
今天我们讨论一个新的控制码:IOCTL_STORAGE_QUERY_PROPERTY,获取设备属性信息,希望得到系统中所安装的各种固定的和可移动的硬盘、优盘和CD/DVD-ROM/R/W的接口类型、序列号、产品ID等信息。
// IOCTL控制码
#define IOCTL_STORAGE_QUERY_PROPERTY CTL_CODE(IOCTL_STORAGE_BASE,
0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS)
// 存储设备的总线类型
typedef
enum _STORAGE_BUS_TYPE {
BusTypeUnknown =
0x00,
BusTypeScsi,
BusTypeAtapi,
BusTypeAta,
BusType1394,
BusTypeSsa,
BusTypeFibre,
BusTypeUsb,
BusTypeRAID,
BusTypeMaxReserved =
0x7F
} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
// 查询存储设备属性的类型
typedef
enum _STORAGE_QUERY_TYPE {
PropertyStandardQuery =
0,
// 读取描述
PropertyExistsQuery,
// 测试是否支持
PropertyMaskQuery,
// 读取指定的描述
PropertyQueryMaxDefined
// 验证数据
} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE;
// 查询存储设备还是适配器属性
typedef
enum _STORAGE_PROPERTY_ID {
StorageDeviceProperty =
0,
// 查询设备属性
StorageAdapterProperty
// 查询适配器属性
} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID;
// 查询属性输入的数据结构
typedef
struct _STORAGE_PROPERTY_QUERY {
STORAGE_PROPERTY_ID PropertyId;
// 设备/适配器
STORAGE_QUERY_TYPE QueryType;
// 查询类型
UCHAR AdditionalParameters[
1];
// 额外的数据(仅定义了象征性的1个字节)
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
// 查询属性输出的数据结构
typedef
struct _STORAGE_DEVICE_DESCRIPTOR {
ULONG Version;
// 版本
ULONG Size;
// 结构大小
UCHAR DeviceType;
// 设备类型
UCHAR DeviceTypeModifier;
// SCSI-2额外的设备类型
BOOLEAN RemovableMedia;
// 是否可移动
BOOLEAN CommandQueueing;
// 是否支持命令队列
ULONG VendorIdOffset;
// 厂家设定值的偏移
ULONG ProductIdOffset;
// 产品ID的偏移
ULONG ProductRevisionOffset;
// 产品版本的偏移
ULONG SerialNumberOffset;
// 序列号的偏移
STORAGE_BUS_TYPE BusType;
// 总线类型
ULONG RawPropertiesLength;
// 额外的属性数据长度
UCHAR RawDeviceProperties[
1];
// 额外的属性数据(仅定义了象征性的1个字节)
} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;
// 取设备属性信息
// hDevice -- 设备句柄
// pDevDesc -- 输出的设备描述和属性信息缓冲区指针(包含连接在一起的两部分)
BOOL GetDriveProperty(HANDLE hDevice, PSTORAGE_DEVICE_DESCRIPTOR pDevDesc)
{
STORAGE_PROPERTY_QUERY Query;
// 查询输入参数
DWORD dwOutBytes;
// IOCTL输出数据长度
BOOL bResult;
// IOCTL返回值
// 指定查询方式
Query.PropertyId = StorageDeviceProperty;
Query.QueryType = PropertyStandardQuery;
// 用IOCTL_STORAGE_QUERY_PROPERTY取设备属性信息
bResult = ::DeviceIoControl(hDevice,
// 设备句柄
IOCTL_STORAGE_QUERY_PROPERTY,
// 取设备属性信息
&Query,
sizeof(STORAGE_PROPERTY_QUERY),
// 输入数据缓冲区
pDevDesc, pDevDesc->Size,
// 输出数据缓冲区
&dwOutBytes,
// 输出数据长度
(LPOVERLAPPED)NULL);
// 用同步I/O
return bResult;
}
Q 我用这个方法从IOCTL_STORAGE_QUERY_PROPERTY返回的数据中,没有得到CDROM和USB接口的外置硬盘的序列号、产品ID等信息。但从设备路径上看,明明是有这些信息的,为什么它没有填充到STORAGE_DEVICE_DESCRIPTOR中呢?再就是为什么硬盘序列号本是“D22P7KHE ”,为什么它填充的是“3146563447534558202020202020202020202020”这种形式呢?
A 对这两个问题我也是心存疑惑,但又不敢妄加猜测,正琢磨着向微软请教呢。
实战DeviceIoControl 之六:访问物理端口
Q 在NT/2000/XP中,如何读取CMOS数据?
Q 在NT/2000/XP中,如何控制speaker发声?
Q 在NT/2000/XP中,如何直接访问物理端口?
A 看似小小问题,难倒多少好汉!
NT/2000/XP从安全性、可靠性、稳定性上考虑,应用程序和操作系统是分开的,操作系统代码运行在核心态,有权访问系统数据和硬件,能执行特权指令;应用程序运行在用户态,能够使用的接口和访问系统数据的权限都受到严格限制。当用户程序调用系统服务时,处理器捕获该调用,然后把调用的线程切换到核心态。当系统服务完成后,操作系统将线程描述表切换回用户态,调用者继续运行。
想在用户态应用程序中实现I/O读写,直接存取硬件,可以通过编写驱动程序,实现CreateFile、CloseHandle、 DeviceIOControl、ReadFile、WriteFile等功能。从Windows 2000开始,引入WDM核心态驱动程序的概念。
下面是本人写的一个非常简单的驱动程序,可实现字节型端口I/O。
#include
<ntddk.h>
#include
"MyPort.h"
// 设备类型定义
// 0-32767被Microsoft占用,用户自定义可用32768-65535
#define FILE_DEVICE_MYPORT
0x0000f000
// I/O控制码定义
// 0-2047被Microsoft占用,用户自定义可用2048-4095
#define MYPORT_IOCTL_BASE
0xf00
#define IOCTL_MYPORT_READ_BYTE CTL_CODE(FILE_DEVICE_MYPORT, MYPORT_IOCTL_BASE, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_MYPORT_WRITE_BYTE CTL_CODE(FILE_DEVICE_MYPORT, MYPORT_IOCTL_BASE+1, METHOD_BUFFERED, FILE_ANY_ACCESS)
// IOPM是65536个端口的位屏蔽矩阵,包含8192字节(8192 x 8 = 65536)
// 0 bit: 允许应用程序访问对应端口
// 1 bit: 禁止应用程序访问对应端口
#define IOPM_SIZE
8192
typedef UCHAR IOPM[IOPM_SIZE];
IOPM *pIOPM = NULL;
// 设备名(要求以UNICODE表示)
const WCHAR NameBuffer[] =
L"//Device//MyPort";
const WCHAR DOSNameBuffer[] =
L"//DosDevices//MyPort";
// 这是两个在ntoskrnl.exe中的未见文档的服务例程
// 没有现成的已经说明它们原型的头文件,我们自己声明
void Ke386SetIoAccessMap(
int, IOPM *);
void Ke386IoSetAccessProcess(PEPROCESS,
int);
// 函数原型预先说明
NTSTATUS MyPortDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
void MyPortUnload(IN PDRIVER_OBJECT DriverObject);
// 驱动程序入口,由系统自动调用,就像WIN32应用程序的WinMain
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
PDEVICE_OBJECT deviceObject;
NTSTATUS status;
UNICODE_STRING uniNameString, uniDOSString;
// 为IOPM分配内存
pIOPM = MmAllocateNonCachedMemory(
sizeof(IOPM));
if (pIOPM ==
0)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
// IOPM全部初始化为0(允许访问所有端口)
RtlZeroMemory(pIOPM,
sizeof(IOPM));
// 将IOPM加载到当前进程
Ke386IoSetAccessProcess(PsGetCurrentProcess(),
1);
Ke386SetIoAccessMap(
1, pIOPM);
// 指定驱动名字
RtlInitUnicodeString(&uniNameString, NameBuffer);
RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);
// 创建设备
status = IoCreateDevice(DriverObject,
0,
&uniNameString,
FILE_DEVICE_MYPORT,
0, FALSE, &deviceObject);
if (!NT_SUCCESS(status))
{
return status;
}
// 创建WIN32应用程序需要的符号连接
status = IoCreateSymbolicLink (&uniDOSString, &uniNameString);
if (!NT_SUCCESS(status))
{
return status;
}
// 指定驱动程序有关操作的模块入口(函数指针)
// 涉及以下两个模块:MyPortDispatch和MyPortUnload
DriverObject->MajorFunction[IRP_MJ_CREATE] =
DriverObject->MajorFunction[IRP_MJ_CLOSE] =
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyPortDispatch;
DriverObject->DriverUnload = MyPortUnload;
return STATUS_SUCCESS;
}
// IRP处理模块
NTSTATUS MyPortDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION IrpStack;
ULONG dwInputBufferLength;
ULONG dwOutputBufferLength;
ULONG dwIoControlCode;
PULONG pvIOBuffer;
NTSTATUS ntStatus;
// 填充几个默认值
Irp->IoStatus.Status = STATUS_SUCCESS;
// 返回状态
Irp->IoStatus.Information =
0;
// 输出长度
IrpStack = IoGetCurrentIrpStackLocation(Irp);
// Get the pointer to the input/output buffer and it's length
// 输入输出共用的缓冲区
// 因为我们在IOCTL中指定了METHOD_BUFFERED,
pvIOBuffer = Irp->AssociatedIrp.SystemBuffer;
switch (IrpStack->MajorFunction)
{
case IRP_MJ_CREATE:
// 与WIN32应用程序中的CreateFile对应
break;
case IRP_MJ_CLOSE:
// 与WIN32应用程序中的CloseHandle对应
break;
case IRP_MJ_DEVICE_CONTROL:
// 与WIN32应用程序中的DeviceIoControl对应
dwIoControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
switch (dwIoControlCode)
{
// 我们约定,缓冲区共两个DWORD,第一个DWORD为端口,第二个DWORD为数据
// 一般做法是专门定义一个结构,此处简单化处理了
case IOCTL_MYPORT_READ_BYTE:
// 从端口读字节
pvIOBuffer[
1] = _inp(pvIOBuffer[
0]);
Irp->IoStatus.Information =
8;
// 输出长度为8
break;
case IOCTL_MYPORT_WRITE_BYTE:
// 写字节到端口
_outp(pvIOBuffer[
0], pvIOBuffer[
1]);
break;
default:
// 不支持的IOCTL
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
}
}
ntStatus = Irp->IoStatus.Status;
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return ntStatus;
}
// 删除驱动
void MyPortUnload(IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING uniDOSString;
if(pIOPM)
{
// 释放IOPM占用的空间
MmFreeNonCachedMemory(pIOPM,
sizeof(IOPM));
}
RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);
// 删除符号连接和设备
IoDeleteSymbolicLink (&uniDOSString);
IoDeleteDevice(DriverObject->DeviceObject);
}
下面给出实现设备驱动程序的动态加载的源码。动态加载的好处是,你不用做任何添加新硬件的操作,也不用编辑注册表,更不用重新启动计算机。
// 安装驱动并启动服务
// lpszDriverPath: 驱动程序路径
// lpszServiceName: 服务名
BOOL StartDriver(LPCTSTR lpszDriverPath, LPCTSTR lpszServiceName)
{
SC_HANDLE hSCManager;
// 服务控制管理器句柄
SC_HANDLE hService;
// 服务句柄
DWORD dwLastError;
// 错误码
BOOL bResult = FALSE;
// 返回值
// 打开服务控制管理器
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCManager)
{
// 创建服务
hService = CreateService(hSCManager,
lpszServiceName,
lpszServiceName,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
lpszDriverPath,
NULL,
NULL,
NULL,
NULL,
NULL);
if (hService == NULL)
{
if (::GetLastError() == ERROR_SERVICE_EXISTS)
{
hService = ::OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS);
}
}
if (hService)
{
// 启动服务
bResult = StartService(hService,
0, NULL);
// 关闭服务句柄
CloseServiceHandle(hService);
}
// 关闭服务控制管理器句柄
CloseServiceHandle(hSCManager);
}
return bResult;
}
// 停止服务并卸下驱动
// lpszServiceName: 服务名
BOOL StopDriver(LPCTSTR lpszServiceName)
{
SC_HANDLE hSCManager;
// 服务控制管理器句柄
SC_HANDLE hService;
// 服务句柄
BOOL bResult;
// 返回值
SERVICE_STATUS ServiceStatus;
bResult = FALSE;
// 打开服务控制管理器
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCManager)
{
// 打开服务
hService = OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS);
if (hService)
{
// 停止服务
bResult = ControlService(hService, SERVICE_CONTROL_STOP, &ServiceStatus);
// 删除服务
bResult = bResult && DeleteService(hService);
// 关闭服务句柄
CloseServiceHandle(hService);
}
// 关闭服务控制管理器句柄
CloseServiceHandle(hSCManager);
}
return bResult;
}
应用程序实现端口I/O的接口如下:
// 全局的设备句柄
HANDLE hMyPort;
// 打开设备
// lpszDevicePath: 设备的路径
HANDLE OpenDevice(LPCTSTR lpszDevicePath)
{
HANDLE hDevice;
// 打开设备
hDevice = ::CreateFile(lpszDevicePath,
// 设备路径
GENERIC_READ | GENERIC_WRITE,
// 读写方式
FILE_SHARE_READ | FILE_SHARE_WRITE,
// 共享方式
NULL,
// 默认的安全描述符
OPEN_EXISTING,
// 创建方式
0,
// 不需设置文件属性
NULL);
// 不需参照模板文件
return hDevice;
}
// 打开端口驱动
BOOL OpenMyPort()
{
BOOL bResult;
// 设备名为"MyPort",驱动程序位于Windows的"system32/drivers"目录中
bResult = StartDriver(
"system32//drivers//MyPort.sys",
"MyPort");
// 设备路径为"//./MyPort"
if (bResult)
{
hMyPort = OpenDevice(
"////.//MyPort");
}
return (bResult && (hMyPort != INVALID_HANDLE_VALUE));
}
// 关闭端口驱动
BOOL CloseMyPort()
{
return (CloseHandle(hMyPort) && StopDriver(
"MyPort"));
}
// 从指定端口读一个字节
// port: 端口
BYTE ReadPortByte(WORD port)
{
DWORD buf[
2];
// 输入输出缓冲区
DWORD dwOutBytes;
// IOCTL输出数据长度
buf[
0] = port;
// 第一个DWORD是端口
// buf[1] = 0; // 第二个DWORD是数据
// 用IOCTL_MYPORT_READ_BYTE读端口
::DeviceIoControl(hMyPort,
// 设备句柄
IOCTL_MYPORT_READ_BYTE,
// 取设备属性信息
buf,
sizeof(buf),
// 输入数据缓冲区
buf,
sizeof(buf),
// 输出数据缓冲区
&dwOutBytes,
// 输出数据长度
(LPOVERLAPPED)NULL);
// 用同步I/O
return (BYTE)buf[
1];
}
// 将一个字节写到指定端口
// port: 端口
// data: 字节数据
void WritePortByte(WORD port, BYTE data)
{
DWORD buf[
2];
// 输入输出缓冲区
DWORD dwOutBytes;
// IOCTL输出数据长度
buf[
0] = port;
// 第一个DWORD是端口
buf[
1] = data;
// 第二个DWORD是数据
// 用IOCTL_MYPORT_WRITE_BYTE写端口
::DeviceIoControl(hMyPort,
// 设备句柄
IOCTL_MYPORT_WRITE_BYTE,
// 取设备属性信息
buf,
sizeof(buf),
// 输入数据缓冲区
buf,
sizeof(buf),
// 输出数据缓冲区
&dwOutBytes,
// 输出数据长度
(LPOVERLAPPED)NULL);
// 用同步I/O
}
有了ReadPortByte和WritePortByte这两个函数,我们就能很容易地操纵CMOS和speaker了(关于CMOS值的含义以及定时器寄存器定义,请参考相应的硬件资料):
// 0x70是CMOS索引端口(只写)
// 0x71是CMOS数据端口
BYTE ReadCmos(BYTE index)
{
BYTE data;
::WritePortByte(
0x70, index);
data = ::ReadPortByte(
0x71);
return data;
}
// 0x61是speaker控制端口
// 0x43是8253/8254定时器控制端口
// 0x42是8253/8254定时器通道2的端口
void Sound(DWORD freq)
{
BYTE data;
if ((freq >=
20) && (freq <=
20000))
{
freq =
1193181 / freq;
data = ::ReadPortByte(
0x61);
if ((data &
3) ==
0)
{
::WritePortByte(
0x61, data |
3);
::WritePortByte(
0x43,
0xb6);
}
::WritePortByte(
0x42, (BYTE)(freq %
256));
::WritePortByte(
0x42, (BYTE)(freq /
256));
}
}
void NoSound(
void)
{
BYTE data;
data = ::ReadPortByte(
0x61);
::WritePortByte(
0x61, data &
0xfc);
}
// 以下读出CMOS 128个字节
for (
int i =
0; i <
128; i++)
{
BYTE data = ::ReadCmos(i);
... ...
}
// 以下用C调演奏“多-来-米”
// 1 = 262 Hz
::Sound(
262);
::Sleep(
200);
::NoSound();
// 2 = 288 Hz
::Sound(
288);
::Sleep(
200);
::NoSound();
// 3 = 320 Hz
::Sound(
320);
::Sleep(
200);
::NoSound();
Q 就是个简单的端口I/O,这么麻烦才能实现,搞得俺头脑稀昏,有没有简洁明了的办法啊?
A 上面的例子,之所以从编写驱动程序,到安装驱动,到启动服务,到打开设备,到访问设备,一直到读写端口,这样一路下来,是为了揭示在NT/2000/XP中硬件访问技术的本质。假如将所有过程封装起来,只提供OpenMyPort, CloseMyPort, ReadPortByte, WritePortByte甚至更高层的ReadCmos、WriteCmos、Sound、NoSound给你调用,是不是会感觉清爽许多?
实际上,我们平常做的基于一定硬件的二次开发,一般会先安装驱动程序(DRV)和用户接口的运行库(DLL),然后在此基础上开发出我们的应用程序(APP)。DRV、DLL、APP三者分别运行在核心态、核心态/用户态联络带、用户态。比如买了一块图象采集卡,要先安装核心驱动,它的“Development Tool Kit”,提供类似于PCV_Initialize, PCV_Capture等的API,就是扮演核心态和用户态联络员的角色。我们根本不需要CreateFile、CloseHandle、 DeviceIOControl、ReadFile、WriteFile等较低层次的直接调用。
Yariv Kaplan写过一个WinIO的例子,能实现对物理端口和内存的访问,提供了DRV、DLL、APP三方面的源码,有兴趣的话可以深入研究一下。
实战DeviceIoControl 之七:在Windows 9X中读写磁盘扇
在Windows NT/2K/XP中,直接用CreateFile打开名称类似于"//./A:"的”文件”,就可以与设备驱动打交道,通过ReadFile/WriteFile以绝对地址方式访问磁盘了。但Windows 9X不支持这样的简单方法。本文介绍一种在Windows 9X中实现磁盘直接访问的方法:利用系统的vwin32.vxd,通过DeviceIoControl调用DOS INT21 7305H与440DH功能来完成。该调用支持FAT12、FAT16和FAT32,适用于Windows 95 SR2以及更高版本。
先来了解一下DOS INT21 7305H功能的入口参数:
AX -- 功能号7305H
DS:BX -- 读写扇区的信息结构
CX -- 必须为-1
DL -- 驱动器号: 1=A:, 2=B:, 3=C:, ...
SI -- 读写标志: 最低位0=读, 1=写
若调用成功,清除C标志;否则设置C标志。
DS:BX指向一个结构,此结构定义如下:
DISKIO STRUC
dwStartSector dd ? ; 起始扇区
wSector dw ? ; 扇区数
lpBuffer dd ? ; 数据缓冲区地址
DISKIO ENDS
在写操作下,需要“锁定”驱动器。DOS INT21 440DH的4AH/6AH功能可实现逻辑驱动器的加锁/解锁。其入口参数为:
AX -- 功能号440DH
BH -- 锁的级别,0-3级,直接写扇区用1
BL -- 驱动器号: 1=A:, 2=B:, 3=C:, ...
CH -- 0x08
CL -- 0x4A
DX -- 0
AX -- 功能号440DH
BL -- 驱动器号: 1=A:, 2=B:, 3=C:, ...
CH -- 0x08
CL -- 0x6A
以上两个调用,若调用成功,清除C标志;否则设置C标志。
通过IOCTL码VWIN32_DIOC_DOS_DRIVEINFO等调用上述中断。实现绝对磁盘读写的关键代码如下:
// INT21的IOCTL码
#define VWIN32_DIOC_DOS_IOCTL 1
#define VWIN32_DIOC_DOS_DRIVEINFO 6
// 寄存器组
typedef struct _DIOC_REGISTERS {
DWORD reg_EBX;
DWORD reg_EDX;
DWORD reg_ECX;
DWORD reg_EAX;
DWORD reg_EDI;
DWORD reg_ESI;
DWORD reg_Flags;
} DIOC_REGISTERS, *PDIOC_REGISTERS;
// IO参数(注意字节对齐方式)
#pragma pack(1)
typedef struct _DISKIO {
DWORD dwStartSector; // 起始扇区
WORD wSectors; // 扇区数
void* pBuffer; // 缓冲区指针
} DISKIO, *PDISKIO;
#pragma pack()
BOOL AbsDiskRead(
BYTE nDiskNumber, // 盘号, 1=A:, 2=B:, 3= C:, ...
DWORD dwStartSector, // 起始扇区
WORD wSectors, // 扇区数
void* pBuffer) // 数据缓冲区指针
{
HANDLE hDevice;
DIOC_REGISTERS regs;
DISKIO dio;
DWORD dwOutBytes;
BOOL bResult;
// 打开设备,获得VxD句柄
hDevice = CreateFile("////.//vwin32", // 设备路径
GENERIC_READ | GENERIC_WRITE, // 读写方式
FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享方式
NULL, // 默认的安全描述符
OPEN_EXISTING, // 创建方式
FILE_ATTRIBUTE_NORMAL, // 文件属性
NULL); // 不需参照模板文件
if(hDevice == INVALID_HANDLE_VALUE)
{
return FALSE;
}
// 填充DISKIO参数结构
dio.dwStartSector = dwStartSector;
dio.wSectors = wSectors;
dio.pBuffer = pBuffer;
// 填充寄存器组--中断入口参数
memset(®s, 0, sizeof(DIOC_REGISTERS));
regs.reg_EAX = 0x7305; // AX=0x7305
regs.reg_EBX = (DWORD)&dio; // EBX=DS:BX=参数指针
regs.reg_ECX = 0xffff; // CX=-1
regs.reg_EDX = nDiskNumber; // DL=盘号
regs.reg_ESI = 0; // SI=0 -- 读操作
// 用VWIN32_DIOC_DOS_DRIVEINFO读磁盘
dwOutBytes = 0;
bResult = DeviceIoControl(hDevice, // 设备句柄
VWIN32_DIOC_DOS_DRIVEINFO, // INT21
®s, sizeof(regs), // 输出数据缓冲区与长度
®s, sizeof(regs), // 输出数据缓冲区与长度
&dwOutBytes, // 输出数据长度
NULL); // 用同步I/O
// 确定DeviceIoControl与INT21都无错误
bResult = bResult && !(regs.reg_Flags & 1);
CloseHandle(hDevice);
return bResult;
}
BOOL AbsDiskWrite(
BYTE nDiskNumber, // 盘号, 1=A:, 2=B:, 3= C:, ...
DWORD dwStartSector, // 起始扇区
WORD wSectors, // 扇区数
void* pBuffer) // 数据缓冲区指针
{
HANDLE hDevice;
DIOC_REGISTERS regs;
DISKIO dio;
DWORD dwOutBytes;
BOOL bResult;
// 打开设备,获得VxD句柄
hDevice = CreateFile("////.//vwin32", // 设备路径
GENERIC_READ | GENERIC_WRITE, // 读写方式
FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享方式
NULL, // 默认的安全描述符
OPEN_EXISTING, // 创建方式
FILE_ATTRIBUTE_NORMAL, // 文件属性
NULL); // 不需参照模板文件
if(hDevice == INVALID_HANDLE_VALUE)
{
return FALSE;
}
// 填充DISKIO参数结构
dio.dwStartSector = dwStartSector;
dio.wSectors = wSectors;
dio.pBuffer = pBuffer;
// 填充寄存器组--中断入口参数
memset(®s, 0, sizeof(DIOC_REGISTERS));
regs.reg_EAX = 0x7305; // AX=0x7305
regs.reg_EBX = (DWORD)&dio; // EBX=DS:BX=参数指针
regs.reg_ECX = 0xffff; // CX=-1
regs.reg_EDX = nDiskNumber; // DL=盘号
regs.reg_ESI = 0x6001; // SI=0x6001 -- 普通写操作
// 用VWIN32_DIOC_DOS_DRIVEINFO写磁盘
dwOutBytes = 0;
bResult = DeviceIoControl(hDevice, // 设备句柄
VWIN32_DIOC_DOS_DRIVEINFO, // INT21
®s, sizeof(regs), // 输出数据缓冲区与长度
®s, sizeof(regs), // 输出数据缓冲区与长度
&dwOutBytes, // 输出数据长度
NULL); // 用同步I/O
// 确定DeviceIoControl与INT21都无错误
bResult = bResult && !(regs.reg_Flags & 1);
CloseHandle(hDevice);
return bResult;
}
BOOL LockVolume(
BYTE nDiskNumber) // 盘号, 1=A:, 2=B:, 3=C:, ...
{
HANDLE hDevice;
DIOC_REGISTERS regs;
DWORD dwOutBytes;
BOOL bResult;
// 打开设备,获得VxD句柄
hDevice = CreateFile("////.//vwin32", // 设备路径
GENERIC_READ | GENERIC_WRITE, // 读写方式
FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享方式
NULL, // 默认的安全描述符
OPEN_EXISTING, // 创建方式
FILE_ATTRIBUTE_NORMAL, // 文件属性
NULL); // 不需参照模板文件
if(hDevice == INVALID_HANDLE_VALUE)
{
return FALSE;
}
// 填充寄存器组--中断入口参数
memset(®s, 0, sizeof(DIOC_REGISTERS));
regs.reg_EAX = 0x440D; // AX=0x440D
regs.reg_EBX = 0x0100 | nDiskNumber; // BH=锁的级别,BL=盘号
regs.reg_ECX = 0x084A;
regs.reg_EDX = 0;
// 用VWIN32_DIOC_DOS_DRIVEINFO读磁盘
dwOutBytes = 0;
bResult = DeviceIoControl(hDevice, // 设备句柄
VWIN32_DIOC_DOS_IOCTL, // INT21
®s, sizeof(regs), // 输入数据缓冲区与长度
®s, sizeof(regs), // 输出数据缓冲区与长度
&dwOutBytes, // 输出数据长度
NULL); // 用同步I/O
// 确定DeviceIoControl与INT21都无错误
bResult = bResult && !(regs.reg_Flags & 1);
CloseHandle(hDevice);
return bResult;
}
BOOL UnlockVolume(
BYTE nDiskNumber) // 盘号, 1=A:, 2=B:, 3=C:, ...
{
HANDLE hDevice;
DIOC_REGISTERS regs;
DWORD dwOutBytes;
BOOL bResult;
// 打开设备,获得VxD句柄
hDevice = CreateFile("////.//vwin32", // 设备路径
GENERIC_READ | GENERIC_WRITE, // 读写方式
FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享方式
NULL, // 默认的安全描述符
OPEN_EXISTING, // 创建方式
FILE_ATTRIBUTE_NORMAL, // 文件属性
NULL); // 不需参照模板文件
if(hDevice == INVALID_HANDLE_VALUE)
{
return FALSE;
}
// 填充寄存器组--中断入口参数
memset(®s, 0, sizeof(DIOC_REGISTERS));
regs.reg_EAX = 0x440D; // AX=0x440D
regs.reg_EBX = nDiskNumber; // BL=盘号
regs.reg_ECX = 0x086A;
// 用VWIN32_DIOC_DOS_DRIVEINFO读磁盘
dwOutBytes = 0;
bResult = DeviceIoControl(hDevice, // 设备句柄
VWIN32_DIOC_DOS_IOCTL, // INT21
®s, sizeof(regs), // 输入数据缓冲区与长度
®s, sizeof(regs), // 输出数据缓冲区与长度
&dwOutBytes, // 输出数据长度
NULL); // 用同步I/O
// 确定DeviceIoControl与INT21都无错误
bResult = bResult && !(regs.reg_Flags & 1);
CloseHandle(hDevice);
return bResult;
}
下面的例子,从A盘的0扇区开始,读取10个扇区的数据,并保存在文件中:
unsigned char buf[512 * 10];
if (AbsDiskRead(1, 0, 10, buf))
{
FILE* fp = fopen("a.dat", "w+b");
fwrite(buf, 512, 10, fp);
fclose(fp);
}
下面的例子,读取D驱动器的第8888扇区,然后写回去:
unsigned char buf[512];
LockVolume(4);
if (AbsDiskRead(4, 8888, 1, buf))
{
... ...
if (AbsDiskWrite(4, 8888, 1, buf))
{
... ...
}
}
UnlockVolume(4);
在写方式下,SI寄存器的位0设置为1,位15-13在磁盘的不同区域需要有不同的值:
Bit 15 |
Bit 14 |
Bit 13 |
Description |
0 |
0 |
0 |
Other/Unknown. |
0 |
0 |
1 |
FAT data. |
0 |
1 |
0 |
Directory data. |
0 |
1 |
1 |
Normal file data. |
1 |
0 |
0 |
Reserved. |
如果不按照上述值操作,尽管能够写成功,但系统无法自动完成相关功能,可能会导致FAT数据备份、驱动器数据压缩等方面的问题。
相关推荐
实战DeviceIoControl。 Aida整理。希望与大家多交流驱动开发经验。
驱动程序函数DEVICEIOCONTROL的详细说明
实战DeviceIoControl:通过API访问设备驱动程序
实战DeviceIoControl:通过API访问设备驱动程序
网上有很多版本的《实战DeviceIoControl》,我这里做了一下整理,希望能对大家有所帮助。
DeviceIOControl实战演示及源代码,是应用程序与设备通信很好的示例教程
硬件读写操作函数DeviceIoControl具体说明和用法,含样例代码
可以获取硬盘、光盘、软盘等设备的具体信息
1.3.6 实战调试first 14 练习题 16 第2章 内核编程环境及其特殊性 17 2.1 内核编程的环境 18 2.1.1 隔离的应用程序 18 2.1.2 共享的内核空间 19 2.1.3 无处不在的内核模块 20 2.2 数据类型 21 2.2.1 基本数据类型 21...
1.3.6 实战调试first 14 练习题 16 第2章 内核编程环境及其特殊性 17 2.1 内核编程的环境 18 2.1.1 隔离的应用程序 18 2.1.2 共享的内核空间 19 2.1.3 无处不在的内核模块 20 2.2 数据类型 21 2.2.1 基本数据类型 21...
1.3.6 实战调试first 14 练习题 16 第2章 内核编程环境及其特殊性 17 2.1 内核编程的环境 18 2.1.1 隔离的应用程序 18 2.1.2 共享的内核空间 19 2.1.3 无处不在的内核模块 20 2.2 数据类型 21 2.2.1 基本数据类型 21...
1.3.6 实战调试first 14 练习题 16 第2章 内核编程环境及其特殊性 17 2.1 内核编程的环境 18 2.1.1 隔离的应用程序 18 2.1.2 共享的内核空间 19 2.1.3 无处不在的内核模块 20 2.2 数据类型 21 2.2.1 基本数据类型 21...
1.3.6 实战调试first 14 练习题 16 第2章 内核编程环境及其特殊性 17 2.1 内核编程的环境 18 2.1.1 隔离的应用程序 18 2.1.2 共享的内核空间 19 2.1.3 无处不在的内核模块 20 2.2 数据类型 21 2.2.1 基本数据类型 21...
1.3.6 实战调试first 14 练习题 16 第2章 内核编程环境及其特殊性 17 2.1 内核编程的环境 18 2.1.1 隔离的应用程序 18 2.1.2 共享的内核空间 19 2.1.3 无处不在的内核模块 20 2.2 数据类型 21 2.2.1 基本数据类型 21...
1.3.6 实战调试first 14 练习题 16 第2章 内核编程环境及其特殊性 17 2.1 内核编程的环境 18 2.1.1 隔离的应用程序 18 2.1.2 共享的内核空间 19 2.1.3 无处不在的内核模块 20 2.2 数据类型 21 2.2.1 基本数据类型 21...