1.NT式的驱动要导入的头文件是NTDDK.H,WDM式的驱动要导入的是WDM.H。
2.NT式不支持即插即用,通过服务来手动加载;WDM是即插即用, 通过inf来加载。
3.WDM在设备创建和PNP消息处理上有区别。
在和驱动通信过程中,我们一般都会碰到应该选择何种类型的缓冲类型进行通信。本文将简要的介绍一下这3中类型的IO缓冲。 我想用的最多的就是 缓冲区设备读写(Buffered)这种IO进行的。现在来说说IO manager是如何对这3中类型的IO进行处理的。
1.IO manager接收到由上层下发来的请求,这里假设这个请求既包含有输入缓冲,又有输出缓冲。这个看看DeviceIoControl中的参数中就可以既包含输入缓冲,又可以有输出缓冲。
2.IO manager检查这个IRP是何种类型的。如果是缓冲区设备读写(Buffered)的,那么IO Manager就会在NonPagePool中分配一个输入缓冲和输出缓冲之间最大的一个;如果是 直接读取设备(Direct)形式的,那么就会首先将上传传下来的缓冲创建一个MDL给IRP。而如果是Neither类型的,那么直接就使用应用层传下来的缓冲。
3.IO manager将IRP分派给指定的驱动进行处理。
4.驱动中根据IO类型,得到合适的缓冲地址。如果是 缓冲区设备读写(Buffered)的,那么就是IRP的InBuffer和OutBuffer共同使用SystemBuffer;如果是 直接读取设备(Direct),那么Inbuffer 和OutBuffer使用MdlAddress;而如果是Neither那么就使用Type3Buffer(InBuffer)和UserBuffer(OutBuffer).
5.驱动返回,这里自然就是将IO manager分配的内存中的OutBuffer拷贝给应用层的Buffer中了。
不过有些事需要值得注意的几点:
1.用户层传下来的如果是 缓冲区设备读写(Buffered)类型的,那就直接使用即可。如果是直接读取设备(Direct)类型的那么需要注意的是,在使用的时候需要将虚拟地址锁内存中进行使用。如果是Neither,因为这个是应用层的虚拟地址,而且这个虚拟地址是有进程上下文限制的,使用这个的时候务必使在指定的进程上下文中进行操作,如果不在制定的上下文可是使用KeStackAttachProcess 将驱动Attach到指定进程上面,然后在进行操作,不过要记得KeUnstackDetachProcess进行Detach。
2.在进行缓冲移动中,如果是很大快内存,那么最好是使用MDL的方式。因为缓冲区设备读写(Buffered)使用的是NonPagePool中分配内存,系统的NonPagePool是有限的,如何使用这种方式会降低系统性能。也可以使用Neither的方式,缺陷上面已经说了。
static在声明局部变量时和在声明全局变量时的区别:
1.全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
2.static函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中声明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中声明,要使用这些函数的源文件要包含这个头文件。
3.static全局变量与普通的全局变量有什么区别:
static全局变量只初使化一次,防止在其他文件单元中被引用;
4.static局部变量和普通局部变量有什么区别:
static局部变量只被初始化一次,下一次依据上一次结果值;
5.static函数与普通函数有什么区别:
static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
6.程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。 7.__cdecl(俗称C调用),__stdcall和__fastcall三种调用的不同点,他们作为函数的调用的三种方式,我们通过小例子来说明:int __stdcall test2(int a, int b) { return 0; } int __cdecl test(int x, int y) { return x + y; } int __fastcall test3(int x, int y) { return x + y; } int main() { int a = 8, b = 2; // // __cdecl // int c = test(a, b); // // __stdcall // c = test2(a, b); // // __fastcall // c = test3(a, b); return 0; }
上边的三种函数调用方式,如果默认,编译器会默认使用__stdcall的调用方式,对于__cdecl这种调用方式,一个很大的特点就是,调用完成后,由谁了完成栈的清理工作,通过反汇编上述代码, 我们可以得到答案,这个必须由调用者自己执行 add esp,X;这个X由你的参数的个数决定的。
__stdcall的调用方式则与这种不同,调研前:
当进入函数体后:
我们可以清楚的看到当函数返回的时候,执行了 ret X,这个X同样与参数的个数有关的。
__fastcall的也有自己的特点,我们从名字看就发现有些不一样了,fast调用,很快的哦,那么怎么样才能
快呢?一切取决于它在参数个数少于3时,使用寄存器传参数,这样会避免由于参数压栈带来的时间的开销。
这里是它自己与其他两种的调用方式的不同,那么它清栈的方式呢?
从这里我们可以看到,由于前两个参数传递使用的是寄存器,所以在返回清栈的时候,同样使用ret X;
只不过它与参数的个数有关。
1. 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使 用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
2. 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但 对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以可以使用WaitForSingleObject来等待进程和 线程退出。
3. 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根 据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数 器。
一、中断:系统停止当前正在运行的程序而转向其他服务,可能是因为优先级高的请求
服务了,或者是因为人为安排中断。中断是属于正常现象。
异常:是由于软件错误而引起的二、中断是CPU所具备的功能 -- 硬件
异常是软件运行过程中的一种开发过程中没有考虑到的程序错误 -- 软件