分享
 
 
 

实战DeviceIoControl 之六:访问物理端口

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

P.bhw98

{

PADDING-RIGHT: 0px;

PADDING-LEFT: 0px;

FONT-SIZE: 9pt;

PADDING-BOTTOM: 0px;

MARGIN: 10px 0px 5px;

LINE-HEIGHT: normal;

PADDING-TOP: 0px;

FONT-FAMILY: Verdana, Arial

}

PRE.bhw98

{

FONT-SIZE: 9pt;

PADDING-RIGHT: 5px;

PADDING-LEFT: 5px;

PADDING-BOTTOM: 5px;

MARGIN: 5px 0px;

LINE-HEIGHT: normal;

PADDING-TOP: 5px;

BACKGROUND-COLOR: #f0f0f0

}

PRE.diag

{

FONT-SIZE: 9pt;

PADDING-RIGHT: 5px;

PADDING-LEFT: 5px;

PADDING-BOTTOM: 5px;

MARGIN: 5px 0px;

LINE-HEIGHT: normal;

PADDING-TOP: 5px;

}

CODE.bhw98

{

FONT-SIZE: 9pt;

COLOR: #000000

}

TABLE.bhw98

{

BORDER-RIGHT: #808080 1px solid;

BORDER-TOP: #808080 1px solid;

FONT-SIZE: 9pt;

MARGIN: 3px 0px 10px;

BORDER-LEFT: #808080 1px solid;

LINE-HEIGHT: normal;

BORDER-BOTTOM: #808080 1px solid;

FONT-FAMILY: Verdana, Arial

}

TD.bhw98

{

BORDER-RIGHT: darkgray 1px solid;

PADDING-RIGHT: 10px;

BORDER-TOP: darkgray 1px solid;

PADDING-LEFT: 5px;

FONT-SIZE: 9pt;

PADDING-BOTTOM: 0px;

MARGIN: 0px;

BORDER-LEFT: darkgray 1px solid;

LINE-HEIGHT: normal;

PADDING-TOP: 3px;

BORDER-BOTTOM: darkgray 1px solid;

FONT-FAMILY: Verdana, Arial;

BACKGROUND-COLOR: #f0f0f0

}

STRONG.bhw98

{

FONT-WEIGHT: bolder;

FONT-SIZE: 20pt;

COLOR: #228b22;

FONT-STYLE: italic;

FONT-FAMILY: Verdana, Arial

}

LI.bhw98

{

FONT-SIZE: 9pt;

MARGIN: 3px 0px 0px 3px;

LINE-HEIGHT: normal;

FONT-FAMILY: Verdana, Arial

}

H1.bhw98

{

MARGIN-TOP: 25px;

FONT-WEIGHT: bolder;

FONT-SIZE: 12pt;

MARGIN-BOTTOM: 5px;

LINE-HEIGHT: normal;

FONT-FAMILY: Verdana, Arial

}

H2.bhw98

{

MARGIN-TOP: 20px;

FONT-WEIGHT: bolder;

FONT-SIZE: 10.5pt;

MARGIN-BOTTOM: 5px;

LINE-HEIGHT: normal;

FONT-FAMILY: Verdana, Arial

}

H3.bhw98

{

MARGIN-TOP: 15px;

FONT-WEIGHT: bolder;

FONT-SIZE: 9pt;

MARGIN-BOTTOM: 5px;

LINE-HEIGHT: normal;

FONT-FAMILY: Verdana, Arial

}

SPAN.key

{

COLOR: #0000ff

}

SPAN.num

{

COLOR: #800000

}

SPAN.str

{

COLOR: #8b008b

}

SPAN.rem

{

COLOR: #008000

}

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三方面的源码,有兴趣的话可以深入研究一下。

[相关资源]

本文驱动程序源码:MyPort.zip (3KB, 编译环境: VC6+2000DDK)

本文应用程序源码:MyPortIo.zip (22KB, 文件MyPort.sys需复制到windows的system32\drivers目录中)

Yariv Kaplan的主页:http://www.internals.com

bhw98的专栏:http://www.csdn.net/develop/author/netauthor/bhw98/

首次发布: 2003-03-05

最后修订: 2003-05-20

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有