用户层和eset内核通信错误层之间通信有哪些方式 module

深入解析Linux内核及其相关架构的依赖关系
作者:佚名
字体:[ ] 来源:开源中文社区 时间:12-10 11:47:22
这篇文章主要介绍了Linux内核及其相关架构的依赖关系,分析了kernel和模块之间的调用等内容,需要的朋友可以参考下
Linux kernel 成功的两个原因:
灵活的架构设计使得大量的志愿开发者能够很容易加入到开发过程中;每个子系统(尤其是那些需要改进的)都具备良好的可扩展性。正是这两个原因使得Linux kernel可以不断进化和改进。
一、Linux内核在整个计算机系统中的位置
分层结构的原则:
the dependencies between subsystems are from the top down: layers pictured near the top depend on lower layers, but subsystems nearer the bottom do not depend on higher layers.
这种子系统之间的依赖性只能是从上到下,也就是图中顶部的子系统依赖底部的子系统,反之则不行。
二、内核的作用虚拟化(抽象),将计算机硬件抽象为一台虚拟机,供用户进程process使用;进程运行时完全不需要知道硬件是如何工作的,只要调用 Linux kernel 提供的虚拟接口virtual interface即可。多任务处理,实际上是多个任务在并行使用计算机硬件资源,内核的任务是仲裁对资源的使用,制造每个进程都以为自己是独占系统的错觉。PS:进程上下文切换就是要换掉程序状态字、换掉页表基地址寄存器的内容、换掉 current 指向的 task_struct 实例、换掉 PC &&&也就换掉了进程打开的文件(通过 task_struct 的 files 可以找到)、换掉了进程内存的执行空间(通过 task_struct 的 mem 可以找到);
三、Linux内核的整体架构
中心系统是进程调度器Process Scheduler,SCHED:所有其余的子系统都依赖于进程调度器,因为其余子系统都需要阻塞和恢复进程。当一个进程需要等待一个硬件动作完成时,相应子系统会阻塞这个进程;当这个硬件动作完成时,子系统会将这个进程恢复:这个阻塞和恢复动作都要依赖于进程调度器完成。
上图中的每一个依赖箭头都有原因:
进程调度器依赖内存管理器Memory manager:进程恢复执行时,需要依靠内存管理器分配供它运行的内存。IPC 子系统依赖于内存管理器:共享内存机制是进程间通信的一种方法,运行两个进程利用同一块共享的内存空间进行信息传递。VFS 依赖于网络接口Network Interface:支持 NFS 网络文件系统;VFS 依赖于内存管理器:支持 ramdisk 设备内存管理器依赖于 VFS,因为要支持交换swapping,可以将暂时不运行的进程换出到磁盘上的交换分区swap,进入挂起状态。
四、高度模块化设计的系统,利于分工合作。只有极少数的程序员需要横跨多个模块开展工作,这种情况确实会发生,仅发生在当前系统需要依赖另一个子系统时;硬件设备驱动hardware device drivers、文件系统模块logical filesystem modules、网络设备驱动network device drivers和网络协议模块network protocol modules这四个模块的可扩展性最高。
五、系统中的数据结构任务列表Task List进程调度器针对每个进程维护一个数据结构 task_struct;所有的进程用链表管理,形成 task list;进程调度器还维护一个 current 指针指向当前正在占用 CPU 的进程。内存映射Memory Map内存管理器存储每个进程的虚拟地址到物理地址的映射;并且也提供了如何换出特定的页,或者是如何进行缺页处理。这些信息存放在数据结构 mm_struct 中。每个进程都有一个 mm_struct 结构,在进程的 task_struct 结构中有一个指针 mm 指向次进程的 mm_struct 结构。在 mm_struct 中有一个指针 pgd,指向该进程的页目录表(即存放页目录首地址)&&&当该进程被调度时,此指针被换成物理地址,写入控制寄存器 CR3(x86体系结构下的页基址寄存器)I-nodesVFS 通过 inodes 节点表示磁盘上的文件镜像,inodes 用于记录文件的物理属性。每个进程都有一个 files_struct 结构,用于表示该进程打开的文件,在 task_struct 中有个 files 指针。使用 inodes 节点可以实现文件共享。文件共享有两种方式:(1)通过同一个系统打开文件 file 指向同一个 inodes 节点,这种情况发生于父子进程间;(2)通过不同系统打开文件指向同一个 inode 节点,举例有硬链接;或者是两个不相关的指针打开同一个文件。数据连接Data Connection内核中所有的数据结构的根都在进程调度器维护的任务列表链表中。系统中每个进程的的数据结构 task_struct 中有一个指针 mm 指向它的内存映射信息;也有一个指针 files 指向它打开的文件(用户打开文件表);还有一个指针指向该进程打开的网络套接字。
六、子系统架构1. 进程调度器Process Scheduler 架构(1)目标进程调度器是 Linux kernel 中最重要的子系统。系统通过它来控制对 CPU 的访问&&不仅仅是用户进程对 CPU 的访问,也包括其余子系统对 CPU 的访问。
调度策略模块scheduling policy module:决定哪个进程获得对 CPU 的访问权;调度策略应该让所有进程尽可能公平得共享 CPU。
体系结构相关模块architecture-specific module设计一组统一的抽象接口来屏蔽特定体系接口芯片的硬件细节。这个模块与 CPU 交互以阻塞和恢复进程。这些操作包括获取每个进程需要保存的寄存器和状态信息、执行汇编代码来完成阻塞或者恢复操作。体系结构无关模块architecture-independent module与调度策略模块交互将决定下一个执行的进程,然后调用体系结构相关的代码去恢复那个进程的执行。不仅如此,这个模块还会调用内存管理器的接口来确保被阻塞的进程的内存映射信息被正确得保存起来。系统调用接口模块system call interface允许用户进程访问 Linux Kernel 明确暴露给用户进程的资源。通过一组定义合适的基本上不变的接口(POSIX 标准),将用户应用程序和 Linux 内核解耦,使得用户进程不会受到内核变化的影响。(3)数据表示调度器维护一个数据结构&&task list,其中的元素时每个活动的进程 task_struct 实例;这个数据结构不仅仅包含用来阻塞和恢复进程的信息,也包含额外的计数和状态信息。这个数据结构在整个 kernel 层都可以公共访问。
(4)依赖关系、数据流、控制流正如前面提到过的,调度器需要调用内存管理器提供的功能,去为需要恢复执行的进程选择合适的物理地址,正因为如此,所以进程调度器子系统依赖于内存管理子系统。当其他内核子系统需要等待硬件请求完成时,它们都依赖于进程调度子系统进行进程的阻塞和恢复。这种依赖性通过函数调用和访问共享的 task list 数据结构来体现。所有的内核子系统都要读或者写代表当前正在运行进程的数据结构,因此形成了贯穿整个系统的双向数据流。
除了内核层的数据流和控制流,OS 服务层还给用户进程提供注册定时器的接口。这形成了由调度器对用户进程的控制流。通常唤醒睡眠进程的用例不在正常的控制流范围,因为用户进程无法预知何时被唤醒。最后,调度器与 CPU 交互来阻塞和恢复进程,这又形成它们之间的数据流和控制流&&CPU 负责打断当前正在运行的进程,并允许内核调度其他的进程运行。
2. 内存管理器Memory Manager 架构(1)目标内存管理模块负责控制进程如何访问物理内存资源。通过硬件内存管理系统(MMU)管理进程虚拟内存和机器物理内存之间的映射。每一个进程都有自己独立的虚拟内存空间,所以两个进程可能有相同的虚拟地址,但是它们实际上在不同的物理内存区域运行。MMU 提供内存保护,让两个进程的物理内存空间不互相干扰。内存管理模块还支持交换&&将暂时不用的内存页换出到磁盘上的交换分区,这种技术让进程的虚拟地址空间大于物理内存的大小。虚拟地址空间的大小由机器字长决定。
architecture specific module提供访问物理内存的虚拟接口;
架构无关模块architecture independent module负责每个进程的地址映射以及虚拟内存交换。当发生缺页错误时,由该模块负责决定哪个内存页应该被换出内存&&因为这个内存页换出选择算法几乎不需要改动,所以这里没有建立一个独立的策略模块。
系统调用接口system call interface为用户进程提供严格的访问接口(malloc 和 free;mmap 和 ummap)。这个模块允许用进程分配和释放内存、执行内存映射文件操作。
(3)数据表示内存管理存放每个进程的虚拟内存到物理内存的映射信息。这种映射信息存放在 mm_struct 结构实例中,这个实例的指针又存放在每个进程的 task_struct 中。除了存放映射信息,数据块中还应该存放关于内存管理器如何获取和存储页的信息。例如:可执行代码能够将可执行镜像作为备份存储;但是动态申请的数据则必须备份到系统页中。(这个没看懂,请高手解惑?)
最后,内存管理模块还应该存放访问和技术信息,以保证系统的安全。
(4)依赖关系、数据流和控制流内存管理器控制物理内存,当页面失败 page fault 发生时,接受硬件的通知(缺页中断)&& 这意味着在内存管理模块和内存管理硬件之间存在双向的数据流和控制流。内存管理也依赖文件系统来支持交换和内存映射 I/O&&这种需求意味着内存管理器需要调用对文件系统提供的函数接口procedure calls,往磁盘中存放内存页和从磁盘中取内存页。因为文件系统请求非常慢,所以在等待内存页被换入之前,内存管理器要让进程需要进入休眠&&这种需求让内存管理器调用进程调度器的接口。由于每个进程的内存映射存放在进程调度器的数据结构中,所以在内存管理器和进程调度器之间也有双向的数据流和控制流。用户进程可以建立新的进程地址空间,并且能够感知缺页错误&&这里需要来自内存管理器的控制流。一般来说没有用户进程到内存管理器的数据流,但是用户进程却可以通过 select 系统调用,从内存管理器获取一些信息。
3. 虚拟文件系统Virtual File System 架构(1)目标虚拟文件系统为存储在硬件设备上数据提供统一的访问接口。可以兼容不同的文件系统(ext2,ext4,ntf等等)。计算机中几乎所有的硬件设备都被表示为一个通用的设备驱动接口。逻辑文件系统促进与其他操作系统标准的兼容性,并且允许开发者以不同的策略实现文件系统。虚拟文件系统更进一步,允许系统管理员在任何设备上挂载任何逻辑文件系统。虚拟文件系统封装物理设备和逻辑文件系统的细节,并且允许用户进程使用统一的接口访问文件。
除了传统的文件系统目标,VFS 也负责装载新的可执行文件。这个任务由逻辑文件系统模块完成,使得 Linux 可以支持多种可执行文件。
device driver module设备独立接口模块Device Independent Interface:提供所有设备的同一视图逻辑文件系统logical file system:针对每种支持的文件系统系统独立接口system independent interface提供硬件资源和逻辑文件系统都无关的接口,这个模块通过块设备节点或者字符设备节点提供所有的资源。系统调用模块system call interface提供用户进程对文件系统的统一控制访问。虚拟文件系统为用户进程屏蔽了所有特殊的特性。(3)数据表示所有文件使用 inode 表示。每个 inode 都记录一个文件在硬件设备上的位置信息。不仅如此,inode 还存放着指向逻辑文件系统模块和设备驱动的的函数指针,这些指针能够执行具体的读写操作。通过按照这种形式(就是面向对象中的虚函数的思想)存放函数指针,具体的逻辑文件系统和设备驱动可以向内核注册自己而不需要内核依赖具体的模块特性。
(4)依赖关系、数据流和控制流一个特殊的设备驱动是 ramdisk,这个设备在主存中开辟一片区域,并把它当成持久性存储设备使用。这个设备驱动使用内存管理模块完成任务,所以在 VFS 与对内存管理模块存在依赖关系(图中的依赖关系反了,应该是 VFS 依赖于内存管理模块)、数据流和控制流。
逻辑文件系统支持网络文件系统。这个文件系统像访问本地文件一样,从另一台机器上访问文件。为了实现这个功能,一种逻辑文件系统通过网络子系统完成它的任务&&这引入了 VFS 对网络子系统的一个依赖关系以及它们之间的控制流和数据流。
正如前面提到的,内存管理器使用 VFS 完成内存交换功能和内存映射 I/O。另外,当 VFS 等待硬件请求完成时,VFS 需要使用进程调度器阻塞进程;当请求完成时,VFS 需要通过进程调度器唤醒进程。最后,系统调用接口允许用户进程调用来存取数据。不像前面的子系统,VFS 没有提供给用户注册不明确调用的机制,所以没有从VFS到用户进程的控制流。
4. 网络接口Network Interface 架构(1)目标网络子系统让 Linux 系统能够通过网络与其他系统相连。这个子系统支持很多硬件设备,也支持很多网络协议。网络子系统将硬件和协议的实现细节都屏蔽掉,并抽象出简单易用的接口供用户进程和其他子系统使用&&用户进程和其余子系统不需要知道硬件设备和协议的细节。
网络设备驱动模块network device drivers设备独立接口模块device independent interface module提供所有硬件设备的一致访问接口,使得高层子系统不需要知道硬件的细节信息。网络协议模块network protocol modules负责实现每一个网络传输协议,例如:TCP,UDP,IP,HTTP,ARP等等~协议无关模块protocol independent interface提供独立于具体协议和具体硬件设备的一致性接口。这使得其余内核子系统无需依赖特定的协议或者设备就能访问网络。系统调用接口模块system calls interface规定了用户进程可以访问的网络编程API(3)数据表示每个网络对象都被表示为一个套接字socket。套接字与进程关联的方法和 inode 节点相同。通过两个 task_struct 指向同一个套接字,套接字可以被多个进程共享。
(4)数据流,控制流和依赖关系当网络子系统需要等待硬件请求完成时,它需要通过进程调度系统将进程阻塞和唤醒&&这形成了网络子系统和进程调度子系统之间的控制流和数据流。不仅如此,虚拟文件系统通过网络子系统实现网络文件系统(NFS)&&这形成了 VFS 和网络子系统指甲的数据流和控制流。
七、结论1、Linux 内核是整个 Linux 系统中的一层。内核从概念上由五个主要的子系统构成:进程调度器模块、内存管理模块、虚拟文件系统、网络接口模块和进程间通信模块。这些模块之间通过函数调用和共享数据结构进行数据交互。
2、Linux 内核架构促进了他的成功,这种架构使得大量的志愿开发人员可以合适得分工合作,并且使得各个特定的模块便于扩展。
可扩展性一:Linux 架构通过一项数据抽象技术使得这些子系统成为可扩展的&&每个具体的硬件设备驱动都实现为单独的模块,该模块支持内核提供的统一的接口。通过这种方式,个人开发者只需要和其他内核开发者做最少的交互,就可以为 Linux 内核添加新的设备驱动。可扩展性二:Linux 内核支持多种不同的体系结构。在每个子系统中,都将体系结构相关的代码分割出来,形成单独的模块。通过这种方法,一些厂家在推出他们自己的芯片时,他们的内核开发小组只需要重新实现内核中机器相关的代码,就可以讲内核移植到新的芯片上运行。
大家感兴趣的内容
12345678910
最近更新的内容 上传我的文档
 下载
 收藏
该文档贡献者很忙,什么也没留下。
 下载此文档
正在努力加载中...
uevent内核
下载积分:30
内容提示:uevent内核
文档格式:PDF|
浏览次数:204|
上传日期: 01:25:20|
文档星级:
该用户还上传了这些文档
uevent内核
官方公共微信应用层和内核层通信
&span style=&font-family: Arial, Helvetica, sans- background-color: rgb(255, 255, 255);&&最近在看Windows内核安全与驱动开发,总结了一些东西,主要介绍一些自旋锁,事件,链表相关的东西。&/span&
LIST_ENTRY一个双向链表,在Windows中经常使用。在使用时只需在结构体中插入一个LIST_ENTRY结构,位置无所谓(最好在前面)。比如:
typedef struct {
LIST_ENTRY list_
UNICODE_STRING file_
}MY_LIST_TEST;
在使用之前需要调用InitializeListHead来初始化:
#define MEM_TAG
'test'
LIST_ENTRY my_list_
//链表头(注意不是自己定义的链表结构体)
InitializeListHead(&my_list_head); //初始化
MY_LIST_TEST *my_first_list = ExAllocatePoolWithTag(NonPagedPool, sizeof(CWK_STR_NODE), MEM_TAG); //申请空间
my_first_list-&file_name = ..... //填写数据成员
InsertHeadList(&my_list_head,(PLIST_ENTRY)&my_first_list);
//添加节点
这样做非常不安全,因为多线程的情况下操作同一个链表会破坏这个链表,这个时候就要引进自旋锁的概念。使用自旋锁首先要初始化一个自旋锁:
KSPIN_LOCK g_cwk_
KeInitializeSpinLock(&g_cwk_lock); //初始化一个自旋锁
ExInterlockedInsertHeadList(&my_list_head, (PLIST_ENTRY)&my_first_list, &g_cwk_lock); //添加节点 并进行加锁
my_first_list = ExInterlockedRemoveHeadList(&my_list_first, &g_cwk_lock); //移除一个节点 并返回到my_first_list中
下面说一下同步事件,当一个地方需要等待另一个事件完成后才能继续进行时需要设置同步事件,同步事件数据结构是KEVENT
KEVENT //定义一个事件
KeInitializeEvent(&event,SynchronizationEvent,TRUE); //事件初始化
//这里设置事件等待,如果这个事件没有被设置,那么会一直在这里等待
KeWaitForSingleObject(&_event,Executive,KernelMode,0,0);
//这理设置事件,只有这里执行完了以后,上面的等待才能放行,去执行后续流程
KeSetEvent(&_event, 0, TRUE);
要实现用户层和内核层的通信,首先要生成一个设备对象,在驱动开发中设备对象和分发函数构成了整个内核体系的基本框架。生成设备可以使用函数IoCreateDevice函数,
NTSTATUS IoCreateDevice
IN PDRIVER_OBJECT DriverObject,
//从DriverEntry的参数获得
IN ULONG DeviceExtensionSize,
//设备扩展大小
IN PUNICODE_STRING DeviceName OPTIONAL, //设备名
IN DEVICE_TYPE DeviceType,
//设备类型
IN ULONG DeviceCharacteristics,
//设备属性
IN BOOLEAN Exclusive,
//表示是否是一个独占设备
OUT PDEVICE_OBJECT *DeviceObject //返回成成的设备对象指针
但是这个生成的设备具有默认的安全属性,必须用管理员权限的进程才能打开,当用户是普通用户就没办法打开,所以使用IoCreateDeviceSecure函数创建设备
NTSTATUS IoCreateDeviceSecure
IN PDRIVER_OBJECT DriverObject,
//从DriverEntry的参数获得
IN ULONG DeviceExtensionSize,
//设备扩展大小
IN PUNICODE_STRING DeviceName OPTIONAL, //设备名
IN DEVICE_TYPE DeviceType,
//设备类型
IN ULONG DeviceCharacteristics,
//设备属性
IN BOOLEAN Exclusive,
//表示是否是一个独占设备
IN PUCNICODE_STRING DefaultSDDLString, //设置设备安全
IN LPCGUID DeviceClassGuid,
//微软提供CoCreateGuid生成,第一次生成就一直使用,不要每次启动都生成一个新的
OUT PDEVICE_OBJECT *DeviceObject //返回成成的设备对象指针
);可以发现只有两个地方不一样,其中DefaultSDDLString这个参数传入L&D:P(A;;GA;;;WD)&即表示允许任何用户访问该设备的万能安全设置字符串,使用如下:
//全局参数
PDEVICE_OBJECT g_cdo = NULL;
const GUID
CWK_GUID_CLASS_MYCDO = {0x17a0d1e0L, 0xe1, {0x92,0x16, 0x45, 0x1a, 0x21, 0x30, 0x29, 0x06}};//GUID
NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path){
UNICODE_STRING sddl = RTL_CONSTANT_STRING(L&D:P(A;;GA;;;WD)&);
UNICODE_STRING cdo_name = RTL_CONSTANT_STRING(L&\\Device\\cwk_3948d33e&); //设备名
IoCreateDeviceSecure(
&cdo_name,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
(LPCGUID)&CWK_GUID_CLASS_MYCDO,//guid
但是应用层无法直接通过设备的名字来打开设备,必须要建立一个暴露给应用层的符号链接,符号链接就是记录一个字符串对应到另一个字符串的一种简单结构,通过函数IoCreateSymbolicLink生成,一般都会成功,除非符号链接名冲突。
IoCreateSymbolicLink(
IN PUNICODE_STRING
SymbolicLinkName, //符号链接名
IN PUNICODE_STRING
DeviceName
);设备大部分都位于\Device\这个路径下,符号链接位于\??\这个路径下(的确是两个问号)
#define CWK_CDO_SYB_NAME
L&\\??\\slbkcdo_3948d33e&
UNICODE_STRING cdo_syb = RTL_CONSTANT_STRING(CWK_CDO_SYB_NAME);
IoCreateSymbolicLink(&cdo_syb,&cdo_name);在卸载驱动的时候应该删除它们,否则符号链接一直存在,使用如下代码:
IoDeleteSymbolicLink(&cdo_syb);
IoDeleteDevice(g_cdo);之后要介绍的是分发函数,所为分发函数就是用来处理发送给设备对象的请求的函数,使用如下结构:
&pre name=&code& class=&cpp&&NTSTATUS cwkDispatch(IN PDEVICE_OBJECT dev,IN PIRP irp){
.......//处理请求
NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path){
for(i=0;i&IRP_MJ_MAXIMUM_FUNCTION;i++)
driver-&MajorFunction[i] = cwkD
其中&MajorFunction是一个函数指针,保存着所有分发函数的指针,将所有分发函数都设置成同一个函数处理。在准备处理请求的时候第一步是获取请求的当前栈空间,我们发现cwkDispatch的参数有irp,查看irp可以发现它是一个大的结构体,结构如下:
&pre name=&code& class=&cpp&&typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _IRP {
MdlAddress(PMDL)域指向一个内存描述符表(MDL),该表描述了一个与该请求关联的用户模式缓冲区。
/guanlaiy/archive//2673375.html
这个博客讲解的比较详细
ULONG F //包含一些对驱动程序只读的标志。
struct _IRP *MasterI
__volatile LONG IrpC
PVOID SystemB //指针指向一个数据缓冲区,该缓冲区位于内核模式的非分页内存中
} AssociatedI
// 用来将 IRP挂入某个线程的 IrpList队列
LIST_ENTRY ThreadListE
// 是一个仅包含两个域的结构,驱动程序在最终完成请求时设置这个结构
//IoStatus.Status域将收到一个NTSTATUS代码
//rmation的类型为ULONG_PTR,其中保存的信息要取决于具体的IRP类型和请求完成的状态。
IO_STATUS_BLOCK IoS
//指定原始I/O请求的来源。驱动程序有时需要查看这个值来决定是否要信任某些参数
KPROCESSOR_MODE RequestorM
//如果为TRUE,则表明处理该IRP的最低级派遣例程返回了STATUS_PENDING。
BOOLEAN PendingR
其实数据结构 IRP 只是&I/O 请求包&IRP的头部,在 IRP 数据结构的后面还有一个IO_STACK_LOCATION 数据结
构的数组,数组的大小则取决于 IRP 数据结构中的StackCount,其数值来自堆叠中顶层设备对象的 StackSize
字段。这样,就在 IRP 中为目标设备对象堆叠中的每一层即每个模块都准备好了一个 IO_STACK_LOCATION 数
据结构。而CurrentLocation,则是用于该数组的下标,说明目前是在堆叠中的哪一层,因而正在使用哪一个
IO_STACK_LOCATION 数据结构。
分层驱动中使用CurrentLocation来记录IRP到达了哪一层,在不同的层有对应的处理函数
(通过IO_STACK_LOCATION关联),对IRP进行特定的处理。
CHAR StackC
CHAR CurrentL
//如果为TRUE,则表明IoCancelIrp已被调用,否则表明没有调用IoCancelIrp函数
//是一个IRQL值,当你在取消例程中释放自旋锁时应参考这个域
KIRQL CancelI
CCHAR ApcE
UCHAR AllocationF
PIO_STATUS_BLOCK UserI
PKEVENT UserE
PIO_APC_ROUTINE UserApcR
PVOID IssuingP
PVOID UserApcC
} AsynchronousP
LARGE_INTEGER AllocationS
__volatile PDRIVER_CANCEL CancelR
PVOID UserB
KDEVICE_QUEUE_ENTRY DeviceQueueE
PVOID DriverContext[4];
PETHREAD T
PCHAR AuxiliaryB
LIST_ENTRY ListE
// 当前栈的位置 - 包含一个在IRP栈中指向当前IO_STACK_LOCATION结构,
struct _IO_STACK_LOCATION *CurrentStackL
ULONG PacketT
PFILE_OBJECT OriginalFileO
PVOID CompletionK
typedef IRP *PIRP;
//http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=69879&id=2680607 这个博客讲的很不错
任何内核模式程序在创建一个IRP时,同时还创建了一个与之关联的 IO_STACK_LOCATION 结构数组:数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序,堆栈单元中包含该IRP的类型代码和参数信息以及完成函数的地址。查看IO_STACK_LOCATION结构
typedef struct _IO_STACK_LOCATION {
UCHAR MajorF //指示驱动程序应该使用哪个函数来处理IO请求。
UCHAR MinorF //进一步指出该IRP属于哪个主功能类
//表明IO请求类型。
// 系统服务参数 :NtCreateFile
PIO_SECURITY_CONTEXT SecurityC
USHORT POINTER_ALIGNMENT FileA
USHORT ShareA
ULONG POINTER_ALIGNMENT EaL
PIO_SECURITY_CONTEXT SecurityC
USHORT POINTER_ALIGNMENT R
USHORT ShareA
PNAMED_PIPE_CREATE_PARAMETERS P
PIO_SECURITY_CONTEXT SecurityC
USHORT POINTER_ALIGNMENT R
USHORT ShareA
PMAILSLOT_CREATE_PARAMETERS P
ULONG POINTER_ALIGNMENT K
LARGE_INTEGER ByteO
ULONG POINTER_ALIGNMENT K
LARGE_INTEGER ByteO
PUNICODE_STRING FileN
FILE_INFORMATION_CLASS FileInformationC
ULONG POINTER_ALIGNMENT FileI
ULONG POINTER_ALIGNMENT CompletionF
FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationC
FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationC
PFILE_OBJECT FileO
BOOLEAN ReplaceIfE
BOOLEAN AdvanceO
ULONG ClusterC
HANDLE DeleteH
ULONG EaListL
ULONG POINTER_ALIGNMENT EaI
} QueryEa;
FS_INFORMATION_CLASS POINTER_ALIGNMENT FsInformationC
FS_INFORMATION_CLASS POINTER_ALIGNMENT FsInformationC
ULONG OutputBufferL
ULONG POINTER_ALIGNMENT InputBufferL
ULONG POINTER_ALIGNMENT FsControlC
PVOID Type3InputB
} FileSystemC
PLARGE_INTEGER L
ULONG POINTER_ALIGNMENT K
LARGE_INTEGER ByteO
ULONG OutputBufferL
ULONG POINTER_ALIGNMENT InputBufferL
ULONG POINTER_ALIGNMENT IoControlC
PVOID Type3InputB
} DeviceIoC //IO通信
SECURITY_INFORMATION SecurityI
ULONG POINTER_ALIGNMENT L
SECURITY_INFORMATION SecurityI
PSECURITY_DESCRIPTOR SecurityD
PDEVICE_OBJECT DeviceO
PDEVICE_OBJECT DeviceO
struct _SCSI_REQUEST_BLOCK *S
PSID StartS
PFILE_GET_QUOTA_INFORMATION SidL
ULONG SidListL
DEVICE_RELATION_TYPE T
} QueryDeviceR
CONST GUID *InterfaceT
PINTERFACE I
PVOID InterfaceSpecificD
PDEVICE_CAPABILITIES C
PIO_RESOURCE_REQUIREMENTS_LIST IoResourceRequirementL
} FilterResourceR
ULONG WhichS
ULONG POINTER_ALIGNMENT L
} ReadWriteC
BUS_QUERY_ID_TYPE IdT
} QueryId;
DEVICE_TEXT_TYPE DeviceTextT
LCID POINTER_ALIGNMENT LocaleId;
} QueryDeviceT
BOOLEAN InP
BOOLEAN Reserved[3];
DEVICE_USAGE_NOTIFICATION_TYPE POINTER_ALIGNMENT T
SYSTEM_POWER_STATE PowerS
PPOWER_SEQUENCE PowerS
#if (NTDDI_VERSION &= NTDDI_VISTA)
ULONG SystemC
SYSTEM_POWER_STATE_CONTEXT SystemPowerStateC
POWER_STATE_TYPE POINTER_ALIGNMENT T
POWER_STATE POINTER_ALIGNMENT S
POWER_ACTION POINTER_ALIGNMENT ShutdownT
ULONG SystemC
POWER_STATE_TYPE POINTER_ALIGNMENT T
POWER_STATE POINTER_ALIGNMENT S
POWER_ACTION POINTER_ALIGNMENT ShutdownT
#endif // (NTDDI_VERSION &= NTDDI_VISTA)
PCM_RESOURCE_LIST AllocatedR
PCM_RESOURCE_LIST AllocatedResourcesT
ULONG_PTR ProviderId;
PVOID DataP
ULONG BufferS
PVOID Argument1;
PVOID Argument2;
PVOID Argument3;
PVOID Argument4;
CompletionRoutine是一个I/O完成例程的地址,该地址是由与这个堆栈单元对应的驱动程序的更上一层驱动程
序设置的。通过调用IoSetCompletionRoutine函数来设置。设备堆栈的最低一级驱动程序并不需要完成例程,
因为它们必须直接完成请求。然而,请求的发起者有时确实需要一个完成例程,但通常没有自己的堆栈单元。
这就是为什么每一级驱动程序都使用下一级驱动程序的堆栈单元保存自己完成例程指针的原因。
PIO_COMPLETION_ROUTINE CompletionR
} IO_STACK_LOCATION, *PIO_STACK_LOCATION;可以看到这个结构体和许多操作有联系,我们可以通过函数IoGetCurrentIrpStackLocation获取当前栈位置
// 这其实是一个宏定义:#define IoGetCurrentIrpStackLocation (Irp) ((Irp)-&Tail.Overlay.CurrentStackLocation)
PIO_STACK_LOCATION
irpsp = IoGetCurrentIrpStackLocation(irp);下面粘上驱动程序(出自Windows内核安全与驱动开发)主代码,没有进行安全检查
NTSTATUS cwkDispatch(IN PDEVICE_OBJECT dev, IN PIRP irp)
PIO_STACK_LOCATION
irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status = STATUS_SUCCESS;
ULONG ret_len = 0;
while(dev == g_cdo)
// 如果这个请求不是发给g_cdo的,那就非常奇怪了。
// 因为这个驱动只生成过这一个设备。所以可以直接
// 返回失败。
//IRP_MJ_CREATE 打开请求的主功能号
//IRP_MJ_CLOSE
关闭请求的主功能号
if(irpsp-&MajorFunction == IRP_MJ_CREATE || irpsp-&MajorFunction == IRP_MJ_CLOSE)
// 生成和关闭请求,这个一律简单地返回成功就可以
// 了。就是无论何时打开和关闭都可以成功。
if(irpsp-&MajorFunction == IRP_MJ_DEVICE_CONTROL)
&span style=&white-space:pre&& &/span&// 处理DeviceIoControl。
&span style=&white-space:pre&& &/span&//获取缓冲区
&span style=&white-space:pre&& &/span&PVOID buffer = irp-&AssociatedIrp.SystemB
//获取输入缓冲区长度
ULONG inlen = irpsp-&Parameters.DeviceIoControl.InputBufferL
//获取输出缓冲区长度
ULONG outlen = irpsp-&Parameters.DeviceIoControl.OutputBufferL
switch(irpsp-&Parameters.DeviceIoControl.IoControlCode)
case CWK_DVC_SEND_STR:
ASSERT(buffer != NULL);
ASSERT(inlen & 0);
ASSERT(outlen == 0);
DbgPrint((char *)buffer);
// 已经打印过了,那么现在就可以认为这个请求已经成功。
case CWK_DVC_RECV_STR:
// 到这里的请求都是不接受的请求。未知的请求一律返回非法参数错误。
status = STATUS_INVALID_PARAMETER;
// 到这里的请求都是不接受的请求。未知的请求一律返回非法参数错误。
//输出长度
irp-&rmation = ret_
//记录请求完成情况
irp-&IoStatus.Status =
//结束请求
IoCompleteRequest(irp,IO_NO_INCREMENT);
在应用层只需要使用CreateFile打开设备,使用函数DeviceIoControl向内核发送就可以代码如下:
int _tmain(int argc, _TCHAR* argv[])
HANDLE device = NULL;
ULONG ret_
int ret = 0;
char *msg = {&Hello driver, this is a message from app.\r\n&};
// 打开设备.每次要操作驱动的时候,先以此为例子打开设备
device=CreateFile(CWK_DEV_SYM,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM,0);
if (device == INVALID_HANDLE_VALUE)
printf(&coworker demo: Open device failed.\r\n&);
return -1;
printf(&coworker demo: Open device successfully.\r\n&);
//发送控制代码直接到指定的设备驱动程序
if(!DeviceIoControl(device, CWK_DVC_SEND_STR, msg, strlen(msg) + 1, NULL, 0, &ret_len, 0))
printf(&coworker demo: Send message failed.\r\n&);
printf(&coworker demo: Send message successfully.\r\n&);
CloseHandle(device);
system(&pause&);
以上均为自己的一些看法,如有不对的地方还希望能及时给予批评指正。
看过本文的人也看了:
我要留言技术领域:
取消收藏确定要取消收藏吗?
删除图谱提示你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?
删除节点提示无法删除该知识节点,因该节点下仍保存有相关知识内容!
删除节点提示你确定要删除该知识节点吗?

我要回帖

更多关于 内核与应用层通信 的文章

 

随机推荐