欢迎光临 C++Builder 研究! 本站主要面向软件开发者(Developer/Programmer), 提供 C++Builder, Delphi, C/C++, VC++ 等相关的资料。发布信息请致信给
编程文档
本站首页 www.ccrun.com | 编程文档 |   关键字:

文件系统驱动编程基础篇之3——Ioctl控制操作

关键字:文件系统驱动编程,Ioctl控制操作

作者:wskjuf    更新:2008-10-06 22:11:55    浏览:18719

文件系统驱动编程基础篇之三——Ioctl控制操作

文件系统驱动编程基础篇之三——Ioctl控制操作

一、前略

    本系列文章为业余编程爱好者而写,仅仅作为初学者的一个借鉴,真正的精华存在于参考资料*中。知识的积累将经历从薄到厚,再从厚到薄的反复过程,为了打下牢固的基础,请读者务必在阅读本文的基础上花费必要的时间完成参考资料。 参考资料*:
1.《Programming the Microsoft Windows driver model》第一版(当前阶段主要阅读资料,阅读第二章三小节、第九章三小节)
2.《Inside Microsoft Windows 2000 3rd》(中文版为《Windows2000内部揭密》)第三章二小节
3.《Named Device Objects》以及相关链接
4.《Defining I/O Control Codes
5.《Windows核心编程》第13章一小节
6.《Understanding and Using Execution Context in NT Drivers》(译文http://blog.donews.com/zwell/archive/2004/12/15/203221.aspx)

阅读基础:不限。

本章目的:了解上下文的概念、Ioctl的基本使用方式。


二、对象管理与命名空间(Namespace)

    内核空间中不同类型的对象都通过对象管理器统一管理,并通过命名空间这一逻辑上的概念来组织各个对象,类似于资源管理器。Device目录存放着通过IoCreateDevice创建的各种设备对象,包括文件系统驱动下创建的卷对象。FileSystem目录存放着文件系统驱动对象和文件系统识别器设备对象(这些内容将在进阶篇叙述)。更具体的描述请参看资料2。

    到目前为止,我们还未讨论过用户模式下的应用程序如何与驱动程序发生交互,请暂时忘记“中断门”、“陷阱门”这类“高深莫测”的术语(大肆宣扬这些术语反而有引入歧途的动机),这些包含在CPU硬件理论中的基础知识不会对我们学习驱动编程有直接的影响,相反,值得一提的却是CreateFile函数。文件是一个高度抽象的概念,既然内核中的对象可以被统一管理,外部的各种设备自然也不例外,它们都可以用文件来加以描述。从图中我们看到计算机中的串口COM1,它对应着设备对象Serial0,而C:盘,对应着是卷设备对象HarddiskVolume4,这是一种称为“符号链接”的映射,通过这个映射,用户模式下的程序才能看到内核中的设备对象,也才可以通过CreateFile打开它们。形象的说,符号链接类似于小名,如大狗一般就称为“旺财”,小狗就叫做“小白”。在内核中建立符号连接可使用IoCreateSymbolicLink,用户模式下可用DefineDosDevice。
    CreateFile的使用示例,注意“.”对应着命名空间里的“GLOBAL??”:

if ((hDevice = CreateFile( "\\\\.\\IoctlTest",
    GENERIC_READ | GENERIC_WRITE,
    0,
    NULL,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    NULL)) == INVALID_HANDLE_VALUE) {

    另一种途径就是Ioctl控制操作。

三、Ioctl控制码

    Ioctl控制码的结构类似于消息(如WM_XXX)或NTSTATUS的定义方式,它是一个驱动程序预定义的4字节整数,定义它的宏为:

#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)
                                        16-31     2-13     0-1     14-15

    通过提供设备类型、功能码(可看作函数的序号)、缓冲方式和存取权限,该宏就创建了一个Ioctl码。设备驱动可以定义多个Ioctl码(通过不同的功能码来区分不同的功能函数)以提供不同的控制功能。

四、Ioctl的同异步与缓冲区操作

    使用DeviceIoControl函数来实现用户模式下的Ioctl操作,它的定义如下:

BOOL DeviceIoControl( HANDLE hDevice,
    DWORD dwIoControlCode,
// 本文转自 C++Builder研究 - http://www.ccrun.com/article.asp?i=1056&d=j2d4m0
    LPVOID lpInBuffer,
    DWORD nInBufferSize,
    LPVOID lpOutBuffer,
    DWORD nOutBufferSize,
    LPDWORD lpBytesReturned,
    LPOVERLAPPED lpOverlapped,
);

    根据Ioctl码的不同,DeviceIoControl函数可以发出两种IRP:IRP_MJ_FILE_SYSTEM_CONTROL和IRP_MJ_DEVICE_CONTROL,前者代表了file system I/O control (FSCTL)请求,后者代表了设备的IOCTL请求。还有一种仅仅在内核模式下使用的IRP_MJ_INTERNAL_DEVICE_CONTROL,它用于内核不同组件间的通信。
    DeviceIoControl需要提供输入和输出缓冲区:

    该函数的使用请参看资料1的第九章三小节,最后一个参数的存在,使Ioctl可以以同步或异步(前提是hDevice以FILE_FLAG_OVERLAPPED方式打开)的方式来完成。同步方式下,函数必须等待内核中的操作完成才返回,否则将立即返回。
    Ioctl码的缓冲区类型可分为三类:METHOD_BUFFERED、METHOD_IN_DIRECT和METHOD_OUT_DIRECT、METHOD_NEITHER。不同类型的缓冲区体现了操作的效率上的不同:
?

    我们暂不理会驱动程序如何找到用户模式下的输入、输出缓冲区,以及如何定义拷贝缓冲区,先来关注一下这三种方式的不同之处。上两图清晰的表达了自身的特点:Buffered方式在输入、输出数据时都要产生用户缓冲区与内核中的拷贝缓冲区间的复制操作,效率上较低,而Direct方式从字面上理解即为“直接”,从上图也可看出,它的输出操作是通过MDL方式完成的,这是一种用户模式内存映射到系统(内核)内存的方法,避免了复制操作,效率上就提高了。
    在Neither方式下,I/O管理器直接将用户模式下的缓冲区地址传递给内核驱动程序,不做任何映射变换。驱动如果想直接使用这个地址,必须处于这个进程的上下文中,因为只有在同一个上下文中,用户进程和驱动例程使用的同一个地址值,才能被系统映射到同一个内存页面。
    也许大家会疑惑驱动例程究竟是由哪个线程执行的?事实上,不同例程的代码很可能由不同类型的线程来执行,有的属于用户模式下的线程,有的属于操作系统的线程,有的甚至根本不是线程对象。我们以“上下文”来描述驱动例程运行的线程环境,某时刻的它运行在三种上下文的一种中:

- 系统进程上下文System process context
- 特定用户线程(和进程)上下文A specific user thread (and process) context
- 任意用户线程(和进程)上下文Arbitrary user thread (and process) context

    我们不妨简单的理解为,在某种上下文中,CPU正执行着我们的驱动例程指令。“上下文”做为一个概念上的抽象,在具体实现上有着明确的数据结构,为了更好的理解上下文,请阅读资料6。随着实践的增加,读者对此将有更深入的理解。

五、Ioctl上的实践

    Ioctl有着广泛的应用,它使用简单,功能却很强大。笔者选择了一些示例,读者可以根据需要选读。

(一)WINDDK\3790\src\general\ioctl

    这个示例堪称Ioctl应用的标准样本。SioctlDeviceControl是实现自定义Ioctl码的函数,需重点研究。IO_STACK_LOCATION子域Parameters的操作规范,读者可以查阅Msdn上IRP_MJ_DEVICE_CONTROL节的说明。Io堆栈的重要性不亚于IRP,需要熟悉它的结构与和相关的函数。
    这个示例还演示了如何手动加载服务,这是一个三板斧的过程:

安装驱动程序流程:
1、调用OpenSCManager()打开服务控制管理器
2、调用CreateService()创建一个服务,服务类型为内核驱动
3、调用OpenService()取得服务句柄

启动服务:
4、调用StartService()启动服务

停止服务:
4、调用ControlService()停止服务

删除服务:
4、调用DeleteService()删除服务
5、调用CloseServiceHandle()关闭服务句柄

操作驱动程序流程:
1、调用CreateFile()取得设备句柄
2、调用DeviceIoControl()传递I/O控制代码
3、调用CloseHandle()关闭设备句柄

(二)《Rootkits——Windows内核的安全防护》第4章2小节的内核钩子
(三)《城里城外看SSDT》
(四)《被占用文件操作三法》
    示例henum需要在c文件首部添加#pragma comment(lib, "ntdll.lib"),并将DDK中的ntdll.lib复制到目录下,关键函数为NtQuerySystemInformation与NtQueryInformationFile。后两个示例和文件自删除技术一样,思路来自对内核对象的理解。
(五)实现了读取磁盘序列号等操作的diskid32源码

六、结语

    本篇内容难度不大,我们在理解Ioctl设计思路的同时也进一步熟悉了各种数据结构。除了示例一,其他示例仅要求以最大能力去理解,本文的参考完成时间为不超过两星期。

上篇文章:文件系统驱动编程基础篇之2——标准模型
下篇文章:浅析C++中内存分配的方式
相关搜索:
  中搜索“文件系统驱动编程基础篇之3——Ioctl控制操作 ”相关内容
  中搜索“文件系统驱动编程基础篇之3——Ioctl控制操作 ”相关内容
  中搜索“文件系统驱动编程基础篇之3——Ioctl控制操作 ”相关内容
  中搜索“文件系统驱动编程基础篇之3——Ioctl控制操作 ”相关内容
  中搜索“文件系统驱动编程基础篇之3——Ioctl控制操作 ”相关内容
  中搜索“文件系统驱动编程基础篇之3——Ioctl控制操作 ”相关内容
C++Builder 研究 - http://www.ccrun.com © 2001,2011  总访问量: 43859375  来访IP: 107.22.52.86  晋ICP备05000574号
Tags: Borland CodeGear Embarcadero C++Builder Delphi VC++ C/C++ RAD Studio BCB BDS Source Code VCL MFC COM SDK Components Controls Developer Programmer 编程学习资料 源代码 源程序 源码 编程文档 经验技巧 组件 控件 元件 开源 函数 软件开发 一切尽在C++Builder研究!