设备驱动通常采用什么方式进行访问,除了ept38u读写器驱动下载控制,

字符设备驱动程序框架介绍;本章部分内容参看了《linux设备驱动详解》;第一部分为以前学习linux设备驱动详解一书时记;linux驱动程序一般分为三类:字符设备,块设备;字符设备:;字符设备以字节流的方式被访问,也即对字符设备的读;常用的串口等设备的数据传输也是以字节为单位进行数;块设备:;在块设备上数据以块的方式被存放,比如flash,;close,re
字符设备驱动程序框架介绍
本章部分内容参看了《linux设备驱动详解》
第一部分为以前学习linux设备驱动详解一书时记录的一些字符设备常用函数及结构体,第二部分为以字符设备为模板写的LED驱动程序。
linux驱动程序一般分为三类:字符设备,块设备,网络设备
字符设备:
字符设备以字节流的方式被访问,也即对字符设备的读写操作是以字节为单位的,字符设备的操作函数一般用到open,close,read,write等系统调用的函数。
常用的串口等设备的数据传输也是以字节为单位进行数据的交互。
在块设备上数据以块的方式被存放,比如flash,SD卡等上的数据以页为单位进行读写。对SD卡,硬盘等块设备的读写,应用程序可以使用open,open,
close,read,write等系统调用函数对块设备以任意字节进行读写。
网络设备:
网络设备同时具有字符设备,块设备的特点,数据的读写有一定的格式,以socket包的方式进行数据的传输。
编写设备驱动程序的一般步骤:
1.查看原理图硬件连接,查看控制设备数据手册,了解kernel中设备的操作函数集
2.在kernel中找到相似的设备驱动程序仿写。一般情况芯片商会提供相应芯片的驱动程序模板
3.实现驱动程序的初始化,及设备注册到kernel,创建设备节点
4.实现设备控制的操作函数集,如,常用的系统调用函数open,close,read,write等。
5.将驱动程序编译进kernel
6.编写应用测试驱动程序。
一.字符设备的常用函数
1.驱动程序中设备的加载和卸载函数
module_init和module_exit,在写模块的时候这两个函数分别在insmod的时候和rmmod的时候调用。
调用module_init函数用来向kernel中注册驱动程序,调用module_exit下载驱动程序。
二..字符设备常用函数和结构体
1.描述字符设备的结构体cdev
struct cdev {
struct module *
const struct file_operations *
struct list_
cdev结构体的dev_t 成员定义了设备号,为32 位,其中高12 位为主设备号,低 20 位为次设备号。 使用下列宏可以从dev_t获得主设备号和次设备号。
MAJOR(dev_t dev)
MINOR(dev_t dev)
而使用下列宏则可以通过主设备号和设备号生成dev_t。
MKDEV(int major, int minor)
2.操作cdev结构体用到的函数
linux/fs/char_dev.c
此函数用于初始化cdev结构体的成员
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
此函数为cdev结构体申请一块内存
struct cdev *cdev_alloc(void)
此函数向内核注册cdev
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
此函数从内核删除cdev
void cdev_del(struct cdev *p)
3.设备号的申请与释放函数
为一个字符驱动一个或多个设备编号来使用
int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数:from:是你要分配的起始设备编号,常常取值为0
count:是你请求的连续设备编号的总数
是应当连接到这个编号范围的设备的名字; 它会出现在 /proc/devices 和 sysfs 中.
返回值:成功返回0,出错返回负数
用于设备号未知,向系统动态申请未被占用的设备号的情况
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name) 作用:该函数需要传递给它指定的第一个次设备号baseminor(一般为0)和要分配的设备数count,以及设备名,调用该函数后自动分配得到的设备号保存在dev中。
baseminor: 通常为0;
*dev:存放返回的设备号;
count:连续编号范围.
这个意思说假如major是248,count是2的话,249也就是相当于被使用的了
成功返回0,失败返回-1;
释放申请的设备号
void unregister_chrdev_region(dev_t from, unsigned count);
4.file_operations结构体
struct file_operations {
struct module *
// 拥有该结构的模块的指针,一般为THIS_MODULES
loff_t (*llseek) (struct file *, loff_t, int);
// 用来修改文件当前的读写位置
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
// 从设备中同步读取数据
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
// 向设备发送数据
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
// 初始化一个异步的读取操作
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); // 初始化一个异步的写入操作
int (*readdir) (struct file *, void *, filldir_t);
// 仅用于读取目录,对于设备文件,该字段为NULL
unsigned int (*poll) (struct file *, struct poll_table_struct *); // 轮询函数,判断目前是否可以进行非阻塞的读取或写入
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
// 执行设备I/O控制命令
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
// 不使用BLK文件系统,将使用此种函数指针代替ioctl
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
// 在64位系统上,32位的ioctl调用将使用此函数指针代替
int (*mmap) (struct file *, struct vm_area_struct *);
// 用于请求将设备内存映射到进程地址空间
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
// 刷新待处理的数据
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
// 异步fsync
int (*lock) (struct file *, int, struct file_lock *);
// 通知设备FASYNC标志发生变化
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这 个函数返回一个负值。
read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一 个负值。
write()函数向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被 实现,
当用户进行write()系统调用时,将得到-EINVAL返回值。
readdir()函数仅用于目录,设备节点不需要实现它。
ioctl()提供设备相关控制命令的实现(既不是读操作也不是写操作),当调用成功时,
返回给调用程序一个非负值。内核本身识别部分控制命令,而不必调用设备驱动中的
ioctl()。如果设备不提供ioctl()函数,对于内核不能识别的命令,用户进行ioctl()系统调
用时将获得-EINVAL返回值。
mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进
行mmap()系统调用时将获得-ENODEV返回值。这个函数对于帧缓冲等设备特别有意
poll()函数一般用于询问设备是否可被非阻塞地立即读写。当询问的条件未触发 时,用户空间进行select()和poll()系统调用将引起进程的阻塞
三亿文库包含各类专业文献、文学作品欣赏、各类资格考试、高等教育、中学教育、应用写作文书、4412开发板android入门篇_字符设备驱动框架17等内容。 
 二、实例剖析我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解 Linux 的设备驱动程序的工作原理.把下面的 C 代码输入机器,你就会获得...  字符设备驱动程序开发1_计算机软件及应用_IT/计算机_专业资料。Linux 操作系统将所有的设备都看成文件,以操作文件的方式访问设备,应用程序不能直接 操作硬件,而是使用...  支持以下设备:扫二维码下载 AndroidiPhoneiPad 扫描二维码下载 支持Android / ...关键词:字符设备驱动框架步骤Red Hat 1/2 相关文档推荐 Linux下的PCI设备开发以及...  linux 设备驱动程序之字符设备驱动一、linux 系统将设备分为 3 类:字符设备、块设备、网络设备 1、字符设备:是指只能一个字节一个字节读写的设备,不能随机读取...  4月9日 字符设备驱动基本编程 本文将使用内存来虚拟 4 个同类型字符设备 scull,并以该字符设备为例来进行 字符设备驱动基本编程的讲解。本文可素材和源代码(做...  d.注册设备驱动程序,使用 register_chrdev 注册字符型设备。函数原型为: intregister_chrdev(0, &test_name&, &test_file_operations) 函数返回主设备号,若注册...  《字符设备驱动程序设计》实验报告学号:
实验内容: LINUX 模块驱动...不要选自动 使用 make 编译第一个程序 在开发板上运行 helloworld 设计相关...  iTOP-4412开发板精英版和全能版的区别_电子/电路_工程科技_专业资料。迅为4412开发板精英版和全能版的区别 迅为4412 开发板精英版和全能版的区别基本参数 底板 ...  实验二:字符设备驱动实验一、实验目的通过本实验的学习, 了解 Linux 操作系统中的字符设备驱动程序结构,并能 编写简单的字符设备的驱动程序以及对所编写的设备驱动...下次自动登录
现在的位置:
& 综合 & 正文
实战DeviceIoControl 之一:通过API访问设备驱动程序
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 一些存储设备的名称是微软定义好的,不可能有什么变化。大体列出如下
软盘驱动器
硬盘逻辑分区
C:, D:, E:, ...
物理驱动器
PHYSICALDRIVEx
CD-ROM, DVD/ROM
其中,物理驱动器不包括软驱和光驱。逻辑驱动器可以是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 to the drive to be examined
// results flag
// discard results
hDevice = CreateFile("////.//PhysicalDrive0",
// drive to open
// no access to the drive
FILE_SHARE_READ | // share mode
FILE_SHARE_WRITE,
// default security attributes
OPEN_EXISTING,
// disposition
// file attributes
// 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
// no input buffer
pdg, sizeof(*pdg),
// output buffer
// # bytes returned
(LPOVERLAPPED) NULL);
// synchronous I/O
CloseHandle(hDevice);
return (bResult);
int main(int argc, char *argv[])
DISK_GEOMETRY
// disk drive geometry structure
// generic results flag
ULONGLONG DiskS
// 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.BytesPerS
printf("Disk size = %I64d (Bytes) = %I64d (Mb)/n", DiskSize,
DiskSize / (1024 * 1024));
printf("GetDriveGeometry failed. Error %ld./n", GetLastError());
return ((int)bResult);
Q 如果将设备名换成“A:”就可以取A盘参数,换成“CDROM0”就可以取CDROM参数,是这样吗?
A 这个问题暂不做回答。请动手试一下。
现在我们总结一下通过DeviceIoControl访问设备驱动程序的“三步曲”:首先用CreateFile取得设备句柄,然后用DeviceIoControl与设备进行I/O,最后别忘记用CloseHandle关闭设备句柄。
[相关资源]
bhw98的专栏:
首次发布:
最后修订:
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 一些存储设备的名称是微软定义好的,不可能有什么变化。大体列出如下
软盘驱动器
硬盘逻辑分区
C:, D:, E:, ...
物理驱动器
PHYSICALDRIVEx
CD-ROM, DVD/ROM
其中,物理驱动器不包括软驱和光驱。逻辑驱动器可以是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 to the drive to be examined
// results flag
// discard results
hDevice = CreateFile("////.//PhysicalDrive0",
// drive to open
// no access to the drive
FILE_SHARE_READ | // share mode
FILE_SHARE_WRITE,
// default security attributes
OPEN_EXISTING,
// disposition
// file attributes
// 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
// no input buffer
pdg, sizeof(*pdg),
// output buffer
// # bytes returned
(LPOVERLAPPED) NULL);
// synchronous I/O
CloseHandle(hDevice);
return (bResult);
int main(int argc, char *argv[])
DISK_GEOMETRY
// disk drive geometry structure
// generic results flag
ULONGLONG DiskS
// 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.BytesPerS
printf("Disk size = %I64d (Bytes) = %I64d (Mb)/n", DiskSize,
DiskSize / (1024 * 1024));
printf("GetDriveGeometry failed. Error %ld./n", GetLastError());
return ((int)bResult);
Q 如果将设备名换成“A:”就可以取A盘参数,换成“CDROM0”就可以取CDROM参数,是这样吗?
A 这个问题暂不做回答。请动手试一下。
现在我们总结一下通过DeviceIoControl访问设备驱动程序的“三步曲”:首先用CreateFile取得设备句柄,然后用DeviceIoControl与设备进行I/O,最后别忘记用CloseHandle关闭设备句柄。
[相关资源]
bhw98的专栏:
首次发布:
最后修订:
&&&&推荐文章:
【上篇】【下篇】博客访问: 1149682
博文数量: 290
博客积分: 1272
博客等级: 少尉
技术积分: 1825
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: WINDOWS
设备对象一共可以有三种读写方式,分别是缓冲区方式读写,直接方式读写,其他方式读写。这三种方式的Flags分别对应为DO_BUFFERED_IO,DO_DIRECT_IO和0。
在驱动程序创建设备对象的时候,需要考虑好该设备是采用何种读写方式。当IoCreateDevice创建完设备后,需要对设备对象的Flags子域进行设置。设置不同的Flags子域会导致以不同的读写方式操作设备。
&示例代码:
//创建设备
status = IoCreateDevice(pDriverObj, & sizeof(DEVICE_EXTENSION),
&(UNICODE_STRING)ustrDeviceName,
&&&&&&&&&&& FILE_DEVICE_UNKNOWN, 0, TRUE,
&pDeviceObj);
//判断是否创建成功
if (!NT_SUCCESS(status))
//设置读写方式,这里为缓冲区读写方式
pDeviceObj->Flags |= DO_BUFFERED_IO;
&1.&&&&&& 缓冲区方式读写设备
读写操作一般是由ReadFile和WriteFile函数引起的。例如,WriteFile要求用户提供一段带有数据的缓冲区,并且说明缓冲区的大小,然后WriteFile将这段内存的数据传入到驱动程序中。
对于缓冲区读写方式来说,操作系统会将用户应用程序提供的缓冲区中的数据复制到内核模式下的地址中。IRP的派遣函数将会对内核模式下的缓冲区进行操作,而不是操作用户模式下的缓冲区。对于ReadFile来说,当IRP请求结束时(一般是由IoCompleteRequest函数结束IRP),这段内存地址会被复制到ReadFile提供的缓冲区中,以此读出在内核中的数据。
这样做的优点是,比较简单的解决了将用户地址传入驱动的问题。缺点是需要在用户模式和内核模式之间复制数据,影响了运行效率。在少量内存操作时,可以使用该方法。
&以“缓冲区”方式读写设备时,操作系统会分配一段内核模式下的内存。这段内存大小等于ReadFile或者WriteFile指定的字节数。并且ReadFile或者WriteFile创建的IRP的AssociatedIrp.SystemBuffer子域会记录这段内存地址。
另外,在派遣函数中,我们还可以通过IO_STACK_LOCATION中的Parameters.Read.Length子域知道ReadFile请求多少字节。通过中的Parameters.Write.Length子域知道WriteFile写入多少字节。
然而,WriteFile和ReadFile指定对设备操作多少字节,并不意味着操作了这么多字节。在派遣函数中,应该设置IRP的子域<rmation。这个子域记录设备实际操作了多少字节。
而用户模式下的ReadFile和WriteFile分别通过各自的第四个参数得到真实操作了多少字节。
&示例代码:
NTSTATUS FuckRead(IN PDEVICE_OBJECT pDeviceObj, IN PIRP pIrp)
KdPrint(("进入IRP_MJ_READ派遣函数!\n"));
&PIO_STACK_LOCATION
pIrpStackLoc = IoGetCurrentIrpStackLocation(pIrp);
//readLength 和 ReadFile函数中的第三个参数数值相同
//是想要读取的字节数
ULONG readLength = pIrpStackLoc->Parameters.Read.L
&//pIrp->rmation的值就是ReadFile函数返回的第四个参数的值
//是实际读取的字节数
pIrp->rmation = readL
pIrp->IoStatus.Status = STATUS_SUCCESS;
&//填充内核模式下的缓冲区
RtlFillMemory(pIrp->AssociatedIrp.SystemBuffer, readLength, 'A');
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
&KdPrint(("离开IRP_MJ_READ派遣函数!\n"));
return STATUS_SUCCESS;
&应用程序:
&int main(void)
hDevice = CreateFile("\\\\.\\HelloDDK",GENERIC_READ | GENERIC_WRITE,
&&&&&&&&&& FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice == INVALID_HANDLE_VALUE)
&&&&&&&&& DWORD dwError = GetLastError();
&&&&&&&&&& printf("%d\n", dwError);
//多分配一个字节,使得printf可以读到'\0'结束
char readBuffer[11] = {0};
ReadFile(hDevice, readBuffer, 10, &ulLength, NULL);
printf("%s\n", readBuffer);
CloseHandle(hDevice);
getchar();
直接方式读写设备
这种方式需要在创建完设备对象后,在设置设备属性的时候,对Flags子域设置为DO_DIRECT_IO。
和缓冲区方式读写设备不同,直接方式读写设备,操作系统会将用户模式下的缓冲区锁住。然后操作系统将这段缓冲区在内核模式地址再次映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。
操作系统先将用户模式的地址锁住后,操作系统用内存描述符表(MDL数据结构)记录这段内存。用户模式的这段缓冲区在虚拟内存上是连续的,但是在物理内存上可能是离散的。如下图所示:
MDL记录这段虚拟内存,这段虚拟内存的大小存储在mdl->ByteCount里,这段虚拟内存的第一个页地址是mdl->StartVa,这段虚拟内存的首地址对于第一个页地址偏移量为mdl->ByteOffset。因此,这段虚拟内存的首地址应该是mdl->StartVa +
mdl->ByteOffest。DDK提供了几个宏,方便我们得到这几个数值:
#define& MmGetMdlByteCount(Mdl)&&&&&&&&& ((Mdl)->ByteCount)
#define&& MmGetMdlByteOffsetMdl) ((Mdl)->ByteOffset)
#define&& MmGetMdlVirtualAddress (Mdl)&&&&&&& ((PVOID) ((PCHAR) ((Mdl)->StartVa) + (Mdl)->ByteOffset))
我们通过IRP的pIrp->MdlAddress得到MDL数据结构,这个结构描述了被锁住的缓冲区内存。通过DDK的三个宏MmGetMdlByteCount,MmGetMdlVirtualAddress,MmGetMdlByteOffset可以得到锁住缓冲区的长度,虚拟内存地址,偏移量。
&示例代码:
NTSTATUS FuckRead(IN PDEVICE_OBJECT pDeviceObj, IN PIRP pIrp)
KdPrint(("进入IRP_MJ_READ派遣函数!\n"));
//得到当前IO堆栈
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
//获取指定的读字节数&&&&&&&
ULONG ulReadLen = stack->Parameters.Read.L
KdPrint(("ulReadLen:%d\n", ulReadLen));
//得到锁定缓冲区的长度&&&&&&&&
ULONG mdl_length = MmGetMdlByteCount(pIrp->MdlAddress);
//得到锁定缓冲区的偏移量&&&&&&&&
ULONG mdl_offset= MmGetMdlByteOffset(pIrp->MdlAddress);
//得到锁定缓冲区的首地址,用户模式下地址&&&&&&&&
PVOID mdl_address = MmGetMdlVirtualAddress(pIrp->MdlAddress);
KdPrint(("mdl_address:0x%08X\n", mdl_address));
KdPrint(("mdl_length:%d\n", mdl_length));
KdPrint(("mdl_offset:%d\n", mdl_offset));
//mdl的长度应该和要读取的长度相等,否则操作设为不成功。
&&&&&&& if (mdl_length != ulReadLen)&&&&&&&
{&&&&&&&&&&&&&&&&&
pIrp->rmation = 0;&&&&&&&&&&&&&&&&&&
status = STATUS_UNSUCCESSFUL;
else&&&&&&&&
{&&&&&&&&&&&&&&&&&&
//用MmGetSystemAddressForMdlSafe得到MDL在内核模式下的映射&&&&&&&&&&&&&&&&&
PVOID kernel_address = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,&&&&&NormalPagePriority);&&&&&&&&&&&
KdPrint(("kernel_address:0x%08X", kernel_address));&&&&&&&&&&&&&&&&&
//填充内存&&&&&&&&&&&&&&&&&
RtlFillMemory(kernel_address, mdl_length, 'B');&&&&&&&&&&&&&&&&&&
pIrp->rmation = mdl_&&&&&&&&
//设置完成状态
pIrp->IoStatus.Status =&&&&&&&&
//结束IRP请求&&&&
IoCompleteRequest(pIrp, IO_NO_INCREMENT);&&&&&&
KdPrint(("离开IRP_MJ_READ派遣函数!\n"));&&&&&
3.&&&&&& 其他方式读写设备
在调用IoCreateDevice创建设备后,对pDevObj->Flags即不设置DO_BUFFERED_IO,也不设置DO_DIRECT_IO,此时采用的读写方式就是其他读写方式。
在使用其他方式读写设备时,派遣函数直接读写应用程序提供的缓冲区地址。在驱动程序中,直接操作应用程序的缓冲区是很危险的。只有驱动程序与应用程序运行在相同线程上下文的情况下,才能使用这种方式。
用这种方式读写时,ReadFile和WriteFile提供的缓冲区内存地址,可以在派遣函数中通过pIrp->UserBuffer字段得到。需要读取的字节数可以从I/O堆栈中的stack->Parameters.Read.Length字段得到。
使用用户模式的内存时要格外小心,因为ReadFile有可能把空指针地址或者非法地址传递给驱动程序。因此,驱动程序使用用户模式地址前,需要探测这段内存是否可读写。探测可读写,可以使用ProbeForWrite函数和try块。
示例代码:
NTSTATUS FuckRead(IN PDEVICE_OBJECT pDeviceObj, IN PIRP pIrp)
KdPrint(("进入IRP_MJ_READ派遣函数!\n"));&&&&&
NTSTATUS status = STATUS_SUCCESS;&&&&&&&
//得到当前堆栈&&&&&&&&
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);&&&&&&
//得到要读取数据的长度&&&&&&
ULONG ulReadLength = stack->Parameters.Read.L&&&&&&&
//得到用户模式下数据的地址&&&&&&&&
PVOID user_address = pIrp->UserB&&&&&&&&
KdPrint(("user_address:0x%08X\n", user_address));&&&&&&&&&&&&
__try&&&&&&&&
{&&&&&&&&&&&&&&&&&&
KdPrint(("进入__try块!\n"));&&&&&&&&&&&&&&&&&&
//测试用户模式下的地址是否可写&&&&&&&&&&&&&&&
ProbeForWrite(user_address, ulReadLength, 4);&&&&&&&&&&&&&&&&&
RtlFillMemory(user_address, ulReadLength, 'C');&&&&&&&&&&&&&&&&&&
KdPrint(("离开__try块!\n"));&&&&&&&
__except(EXCEPTION_EXECUTE_HANDLER)&&&&&&
{&&&&&&&&&&&&&&&&&&
KdPrint(("进入__except块!\n"));&&&&&&&&&&&&&&&&&
status = STATUS_UNSUCCESSFUL;&&&&&&&&&&&&&&&&&&
ulReadLength = 0;&&&&&&&&
//设置完成状态&&&&&&
pIrp->IoStatus.Status =&&&&&&
//设置操作字节数&&&&&&
pIrp->rmation = ulReadL&&&&&&&&
//结束IRP请&&&&&&&&
IoCompleteRequest(pIrp, IO_NO_INCREMENT);&&&&&
KdPrint(("离开IRP_MJ_READ派遣函数!\n"));&&&&&&&
阅读(701) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。}

我要回帖

更多关于 驱动读写内存 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信