一个简单的操作端口的驱动------完整过程

 

这个驱动很简单,动态加载,无须更改注册表等。没有inf等东西,应该属于旧式的NT式驱动。

驱动:通过 _inp 和 _outp /  READ_PORT_UCHAR 和 WRITE_PORT_UCHAR  访问PC自带的speeker cmos的端口

应用程序:通过DeviceIoControl访问驱动

参考:
http://blog.csdn.net/bhw98/archive/2003/05/26/19665.aspx

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);

//这2个函数在编译的时候会不过,后来把它们去掉,发现也可以
 
// 函数原型预先说明
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]);  //_inp 可以使用READ_PORT_UCHAR代替
                    Irp->IoStatus.Information = 8;  // 输出长度为8
                    break;
                case IOCTL_MYPORT_WRITE_BYTE:       // 写字节到端口
                    _outp(pvIOBuffer[0], pvIOBuffer[1]);  //_outp可以使用WRITE_PORT_UCHAR代替

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: FX3U-USB-BD是三菱电机公司生产的一种USB通信接口板,用于与三菱PLC(可编程逻辑控制器)进行通信。该驱动是安装在计算机上的软件程序,与FX3U-USB-BD配合使用,实现计算机与PLC之间的数据交互。 该驱动的安装非常简单,只需要将驱动软件光盘插入计算机,然后按照安装向导的指示进行安装即可。安装完成后,需要将FX3U-USB-BD插入计算机的USB接口。接下来,在PLC和计算机之间进行连接,可以使用USB线缆将FX3U-USB-BD与PLC相连。连接完成后,驱动程序会自动识别FX3U-USB-BD,并建立计算机和PLC之间的通信通道。 通过FX3U-USB-BD驱动,我们可以通过计算机与PLC进行数据传输和程序下载。可以使用三菱电机公司提供的专业编程软件GX Works2或GX Developer与PLC进行编程,并且可以实时监控PLC的运行状态。此外,还可以通过驱动程序实现对PLC的参数配置以及数据的采集和存储。FX3U-USB-BD驱动提供了一个简便的通信接口,使得计算机与PLC之间的数据交互更加便捷。 总之,FX3U-USB-BD驱动是一种用于与三菱PLC进行通信的软件程序。通过驱动的安装和设置,我们能够实现计算机与PLC之间的数据交互和程序下载,便于对PLC进行编程和监控。 ### 回答2: FX3U-USB-BD驱动是用于连接Mitsubishi FX3U系列可编程控制器和计算机的USB接口板驱动程序。它充当了计算机和可编程控制器之间的桥梁,使得用户可以通过计算机对可编程控制器进行编程、监测和控制。 FX3U-USB-BD驱动的安装可以通过以下步骤实现: 1.首先,确定计算机上是否已安装了Mitsubishi的GX Developer软件。如果没有安装,需要先安装该软件。 2.将FX3U-USB-BD驱动程序从官方网站或光盘中下载到计算机中,并运行安装程序。 3.按照安装程序的指示,进行驱动程序的安装。安装过程中可能需要选择安装路径和接受许可协议等。 4.安装完成后,重新启动计算机以确保驱动程序的正常加载和生效。 5.连接FX3U系列可编程控制器和计算机,使用USB线连接FX3U-USB-BD接口板和计算机。 6.打开GX Developer软件,选择连接方式为USB连接,并选择正确的通讯端口。 7.在GX Developer软件中,可以编写、下载和监测可编程控制器的程序,以实现各种控制和监测功能。 总之,FX3U-USB-BD驱动是一种用于连接Mitsubishi FX3U系列可编程控制器和计算机的USB接口板驱动程序,用户可以通过该驱动程序实现计算机对可编程控制器的编程、监测和控制。安装驱动程序后,用户可以使用GX Developer软件来进行相关操作,并连接FX3U系列可编程控制器和计算机,实现各种控制和监测功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值