在前段时间检查异常链接致使的内存泄漏排查的过程当中,主要涉及到了windows异步I/O相关的知识,看了许多包括重叠I/O、完成端口、IRP、设备驱动程序等Windows下I/O相关的知识,虽然学习到了不少东西,可是仍然须要自顶而下的将全部知识进行梳理。react
本片文章主要讲解同步I/O与异步I/O相关知识,但愿经过编写本篇文章为起点,对windows内核原理知识进行学习与梳理。发现并弥补遗漏的知识点并加以学习。同时经过理解windows内核原理,设计出更好、更合理的应用程序。编程
I/O即输入输出。在如今操做系统,输入输出是计算机完整功能必不可少的一部分。处理器负责各类计算任务,而后经过各类输入输出设备与外界进行交互。常见的输入输出设备包括键盘、鼠标、显示器、硬盘、网络适配器接口等。有了硬件设备,在软件层面上,使得操做系统经过以一致的方式与设备驱动交互从而的操控硬件设备。而应用程序经过统一的接口与系统内核进行交互。
Windows从一开始就设计了可扩展的I/O接口。在应用层经过统一的Win32 API
,将I/O请求分配给正确的设备驱动程序。设备驱动程序调用设备控制器来操控硬件。而内核经过硬件抽象层与硬件进行交互。硬件抽象层提供了供内核和驱动调用的例程。windows
例程就是系统提供的API或服务。网络
在Windows下分为内核模式和用户模式。应用程序运行在用户模式下,操做系统和驱动程序运行在内核模式下。应用程序经过调用Win32 API
与Windows内核交互。
Windows内核则经过设备驱动程序与设备控制器进行通信,而设备控制器则直接操控硬件设备。
设备驱动程序分为即插即用驱动程序、内核扩展驱动程序和文件系统驱动程序。其中文件系统驱动程序用于接收I/O请求,而后将请求转换为真正的存储设备或网络设备的I/O请求。
并发
设备控制器能够经过内存映射I/O的方式将设备的内存与主存映射,经过内存映射I/O后,处理器访问的就不是主存而是设备控制器的寄存器内存。可是这种方式的访问效率并不高,不适合大数据量I/O读写。一般硬盘和网络驱动器采用直接访问内存(DMA)的方式进行大量数据的I/O操做。DMA须要硬件支持,硬件会有DMA控制器,在硬件执行I/O操做的时候,不会占用CPU的指令周期,DMA控制器会和设备进行I/O操做。当数据传输完成后,DMA则会通知处理器I/O操做完成。app
当咱们要把文件从硬盘读取到内存时,硬盘的读取速度是远小于内存的写入速度的。所以当咱们使用一个线程从硬盘读取文件到内存中时。一般须要等待硬盘将数据从硬盘读取到内存中,此时线程将被阻塞,可是不会消耗指令周期。当读取完毕时,线程继续执行后续操做。
虽然DMA执行的时候当前线程被阻塞,此时处理器能够获取另外一个线程内核执行其余操做,因为线程是很是昂贵的资源,所以使用同步I/O的方式若须要并发执行时,须要大量的建立线程资源,这就产生了大量的线程上下文切换。负载均衡
在大多数x86和x64的多处理器,线程上下文切换时间间隔大约为15ms。
CPU每过大约15ms将CPU寄存器当前的线程上下文存回到该线程的上下文,而后该线程不在运行。而后系统检查剩下的可调度线程内核对象,选择一个线程的内核对象,将其上下文载入导CPU寄存器中。
关于Windows线程相关内容能够查阅《Windows via C/C++ 第五版》的第七章框架
前面提到了当硬件进行I/O传输时,实际上一般使用DMA技术执行I/O操做,不会占用CPU的指令周期。所以只要操做系统支持异步I/O,则能够极大的提高系统性能,最大程度的下降线程数量,减小线程上下文切换产生的性能损失。异步
在Windows下的异步I/O咱们也能够称之为重叠(overlapped)I/O。重叠的意思是执行I/O请求的时间与线程执行其余任务的时间是重叠的,即执行真正I/O请求的时候,咱们的工做线程能够执行其余请求,而不会阻塞等待I/O请求执行完毕。
当使用一个线程向设备发出一个异步I/O请求时,该请求被传给设备驱动程序,设备驱动程序处理I/O请求时并不会等待I/O请求完成,而是将I/O请求加入到设备驱动程序的队列中,而后返回一个I/O处理中的信号。而实际的I/O操做则由设备驱动程序将I/O请求传给指定的硬件设备执行I/O操做。应用程序的线程并不须要挂起等待I/O请求的完成,从而能够继续执行其余任务。当某一时刻设备驱动程序完成了该I/O请求处理,设备控制器经过中断指令通知I/O请求完成,处理器则将通知I/O请求已完成。
在Windows中一共支持四种接收完成通知的方式。分别为触发设备内核对象、触发时间内核对象、可提醒I/O以及I/O完成端口。
当设备驱动加载时会建立一个设备驱动对象,设备驱动程序还会为设备建立对应的设备对象。设备对象表明的是每个物理设备或逻辑设备。设备对象描述了一个特定设备的状态信息,包括I/O请求的状态。在经过异步I/O将I/O请求添加到队列以前,会将设备内核对象设置为未触发,此时就可使用该设备内核对象进行同步操做,当I/O请求完成后则会将设备内核对象设置为触发状态。使用设备内核对象进行线程同步时,没法区分当前完成通知的I/O是读操做仍是写操做,所以不管是读仍是写都会将其状态设置为触发状态。
经过设备内核对象进行I/O通知因为没法区分读写操做,所以并无什么用。经过事件内核对象咱们能够将读写事件分离。在调用读写操做的时候会返回对应的读写事件内核对象。这样咱们就能够等待对应的事件内核对象知道是什么I/O操做完成。咱们能够经过等待多个事件内核对象,可是一次性最多只能等待64个事件内核对象,即一个线程最多只能建立64个事件内核对象进行等待。若须要监控上万个链接,则须要建立上百个线程进行监控。
在系统建立线程的时候会建立一个与线程相关的队列,该队列被称为异步调用(APC)队列,当发出一个I/O请求时,咱们能够告诉设备驱动程序在调用线程的APC队列中添加一项完成函数,在I/O完成通知时调用完成函数进行回调。I/O完成通知最大的问题是,请求时哪一个线程调用的,必须由哪一个线程回调。它不支持负载均衡机制。
I/O完成端口的设计理论依据是并发编程的线程数必须有一个上限,即最佳并发线程数为CPU的逻辑线程数。I/O完成端口充分的发挥了并发编程的优点的同时又避免了线程上下文切换带来的性能损失。
完成端口多是最复杂的内核对现象,可是它又是Windows下性能最佳的I/O通知方式。
首先咱们须要建立一个I/O完成端口,建立完成端口的时候能够指定线程数量。经过将设备与I/O完成端口进行关联。此使咱们发出的I/O请求时,系统内核返回IO_PENDDING
状态,而后线程就能够继续处理其余事情。而DMA继续执行I/O操做,将数据从设备读取到设备控制器的缓冲区中,并对其进行必要的校验后,将数据经过系统总线传输到内存中。当数据传输完成后,DMA发出中断指令通知数据传输完毕,系统则会经过前面建立的I/O线程将I/O完成请求加入到I/O完成队列中。
而后咱们经过调用Win32 API
就能够获取到对应的设备I/O完成请求通知,通知会将I/O完成请求从完成队列移除。
IO_PENDDING
状态表示请求受理成功,当底层设备完成了真实的I/O请求后会经过中断控制器经过中断操做通知CPU,CPU会调度一个线程通知上层设备驱动程序,将完成通知加入到完成队列中。此时上层应用便可获取到完成通知。本文地址:http://www.javashuo.com/article/p-onjikisg-kw.html 做者博客:杰哥很忙 欢迎转载,请在明显位置给出出处及连接