加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

解析windows内核每日一讲  陷阱调度

(2013-08-14 13:24:07)
分类: windows操作系统
内容来源于深入解析windows内核第5版                                        由 jeff_司马徽为您呈现

Trap Dispatching
    中断和异常是使处理转向的系统事件,硬件或者软件都可以检测到。术语trap指的是处理器捕获正在运行的线程并跳转到操作系统中一个固定地址处执行的机制。在windows中处理器将控制流转移到陷阱句柄(a trap handler)一个与中断或异常相关的函数。下图描述了一些激活陷阱句柄的条件:

http://s5/mw690/5d29455egx6BQu0b6i8e4&690 陷阱调度" TITLE="解析windows内核每日一讲  陷阱调度" />

     内核是这样区分中断和异常的:
     中断是任何时刻都可能发生的与处理器当前执行无关的异步事件,主要由I/O设备,处理器时钟,或计时器,并且能够打开或关闭对其响应。
     异常是特定指令执行中产生的,使用同样的数据在相同条件下重新运行程序会在再次产生异常。异常包括内存访问越界,有些调试指令和除零错误等。内核也将系统服务调用视为异常(虽然技术上讲它们属于系统陷阱)
    硬件软件都可以产生中断和异常,比如总线错误异常是由硬件产生的,而除零是软件错误。类似地,I/O设备可以产生中断,内核本身能发布软件中断(比如 APC或者DPC)
    硬件异常或中断产生时,处理器会记录执行线程内核栈的所有硬件状态以便继续执行时恢复。若线程运行在用户态,windows切换到线程的内核态栈。然后windows在内核线程的中断栈上创建一个trap frame并将线程执行状态即硬件数据存储入其中。trap frame是完整线程上下文的子集,可以通过在内核调试器中键入dt nt!_ktrap_frame来观察其定义。内核或者会将软件中断视为硬件中断的一部分处理,或者会在线程调用与软件中断相关的内核函数时同步处理。
    在大多数情况下,内核安装了将控制权移交给陷阱处理函数之前和之后进行一般陷阱处理任务的前端处理函数。比如,
    若发生了设备中断,内核硬件中断陷阱handler将控制移交给设备驱动为该中断设备提供的中断服务例程(ISR)。
    若发生了系统服务调用,通用系统服务陷阱handler会将控制移交给执行器中的特定系统服务函数。
    内核也会为没有预料到或无法处理的陷阱安装陷阱处理器。这些陷阱处理器一般会执行系统函数KeBugCheckEx,它会在内核检测到类似于这样的问题或不正确的行为时中止系统,或对其放任自流,会引起数据崩溃。
    Interrupt Dispatching
    硬件产生的中断一般源于I/O设备,它们必须通知处理器它们需要服务。中断驱动型设备能够将处理器从I/O事务中解放出来,提交系统效率。线程启动向设备输出或从设备输入的I/O操作,然后执行其他工作以等待设备完成传输。设备完成时,其中断处理器以获得服务。定点设备,打印机,键盘,磁盘驱动,网卡一般都是中断驱动的。
    系统软件也可能产生中断,比如,内核可能发布软件中断以启动线程调度并异步强行进入某个线程的执行。内核也能屏蔽中断以使处理器不响应中断,但并不常用,只有在其正在处理中断或分派异常时会发生。
    内核安装中断陷阱处理器以响应设备中断。中断陷阱处理器或者将控制转移到一个处理中断的外部例程(比如ISR)或者转移到响应中断的内核例程。设备驱动提供ISR以处理设备中断,同时内核提供中断处理例程以处理其他各类中断。
    接下来会看到,硬件是如何向处理器传递设备中断,内核支持的中断类型,设备驱动与内核交互的方式(作为中断处理的一部分)以及内核识别的软件中断(还有用于实现它们的内核对象)。

Hardware Interrupt Processing
    在windows支持的硬件平台上,外部I/O中断会进入到中断控制器的某一引脚。控制器随后会在处理器的那条引脚形成中断。一旦处理器中断,其询问中断控制器以获取中断请求(IRQ)。中断控制器将IRQ翻译成中断数字,使用这个数字作为索引到称为中断向量表(interrupt dispatch table IDT)中查找,并将控制转到相应的中断向量例程。在系统启动时,windows用指向处理每个中断和异常的内核例程的指针填充IDT。
    windows将硬件IRQ映射到中断向量表IDT中的中断数字,并且系统也使用IDT配置异常的陷阱处理程序。比如,x86和x64对于页错误(线程试图访问虚拟内存中没有定义或没在内存中的地址时的错误)的异常数字是oxe。因此oxe是IDT中指向系统页错误处理程序的入口点。虽然windows支持的硬件平台支持多达256条入口,但特定硬件能够支持的IRQ数是硬件使用的中断控制器的设计决定的。
    每个处理器有一个单独的IDT,这样,不同处理器可以运行不同的ISR,如果合适的话。比如,在多处理器系统中,每个处理器都接收时钟中断,但仅有一个处理器会响应该中断并更新系统时钟。然而所有处理器使用中断测量线程时间片,并在时间片结束时启动调度。类似地,有些系统可能配置成需要某特定处理器处理特定设备中断。

x86 Interrupt Controllers
    大部分x86系统使用i8259A可编程中断控制器PIC,或者i82489高级可中断编程控制器APIC的变种,大部分新电脑都有APIC。PIC标准起源于IBM PC,i8259A只在单处理器平台上使用,且只有8条中断线。然而IBM PC体系结构定义了添加第二个PIC的标准,称为slave,其所有中断线都经由master PIC的一条中断线。这能够容纳15个中断(7个在master上,8个在slave上),APIC和SAPIC(Streamlined Advanced Programmable Interrupt Controllers)在多处理器系统上使用,且有256条中断线。英特尔和其他公司一起定义了MP标准(Multiprocessor Specification),是围绕多处理器x86平台上使用APIC的。为了提供对单处理器的兼容,以及在单处理器上启动多处理器系统的启动代码,APIC支持15个中断的PIC兼容模式,并将中断仅通知给首处理器。下图描述了APIC体系结构:

http://s3/mw690/5d29455egx6BRPi2gOme2&690 陷阱调度" TITLE="解析windows内核每日一讲  陷阱调度" />

    APIC实际包含由这样几个组件组成:一个从设备接收中断的I/O APIC,从位于总线上从I/O APIC接收中断并中断与他们连接在一起的CPU的本地APIC,以及一个将APIC输入翻译成等同于PIC的信号的兼容i8259A的中断控制器。由于系统上可能有多I/O APIC,所以主板一般在它们与处理器们之间有一块核心逻辑电路。这块逻辑电路用于实现中断路由算法,此算法在处理器之间平衡中断负载,充分利用本地性,并将设备中断传递到刚刚处理过相同类型中断的同一个处理器上。软件程序可以对I/O APICs再度编程以使用一套固定的路由算法绕过那块芯片组逻辑电路。windows通过“中断上图中某处理器”的路由模式对APIC进行编程。
    x64中断控制器和IA64中断控制器内容略去
    
Software Interrupt Request Levels (IRQLs)
    虽然中断控制器维护着一份中断优先级,但windows将其称为interrupt request levels (IRQLs)的一套中断优先级模式强加在系统上。内核认为在x86上,IRQLs是0到31的数字,在x64和ia64上,是0到15,数值越大则中断的优先级越高。虽然内核为软件中断定义了一套标准的IRQLs,但HAL将硬件中断数字映射到IRQLs上。下图定义了为x86体系结构定义的IRQLs

http://s6/mw690/5d29455egx6BSsJ1nNza5&690 陷阱调度" TITLE="解析windows内核每日一讲  陷阱调度" />

    下图显示x64和ia64体系的IRQLs
  
http://s8/mw690/5d29455egx6BSsXFR2L67&690 陷阱调度" TITLE="解析windows内核每日一讲  陷阱调度" />

    中断处理是有优先级的,高优先级中断会剥夺低优先级中断的处理权。高优先级中断发生时,处理器保存中断线程状态并调用与中断相关的陷阱调度器。陷阱调度器提升IRQL级别并调用该中断的服务例程。在服务例程执行后,中断调度器会将处理器IRQL降到中断发生前的级别并恢复硬件状态。中断的线程恢复执行。当内核降低IRQL时,之前屏蔽掉了的低优先级中断可以重新恢复响应。如果这种情况发生,内核会处理这个新的中断。
    IRQL优先级与线程调度优先级完全不同,调度优先级是线程的一项属性,然而IRQL是中断源的属性,比如键盘或鼠标。此外,每个处理器有一个IRQL设定会随系统代码的执行变化而变化。
    每个处理器的IRQL设定决定了其可以响应哪些中断。IRQL也用于同步对内核数据结构的访问。内核态线程运行时,其会通过调用KeRaiseIrql和KeLowerIrql或者间接调用获取内核同步对象的函数抬高或拉低处理器IRQL。如下图所示,从IRQL高于当前级别的中断源发生的中断会中断处理器,而低于或等于的会被屏蔽。

http://s15/mw690/5d29455egx6BSwnOugS6e&690 陷阱调度" TITLE="解析windows内核每日一讲  陷阱调度" />

    由于访问PIC是相对慢的操作,需要访问I/O总线以改变IRQL(比如为了PIC和32位高级配置和电源接口Advanced Configuration and Power Interface(ACPI)系统)的HAL实现了性能优化,称为懒IRQL,它避免了PIC访问。IRQL提升时,HAL在内部标记新的IRQL而不改变中断屏蔽码。如果接下来发生了低优先级中断,HAL会为先前的中断设置合适的中断屏蔽码并延迟低优先级的中断直到IRQL降低。因此,如果没有低优先级中断在IRQL提升期间发生,则HAL不需要修改PIC。
    内核态线程根据其所要做的事选择升高或降低其所运行的处理器的IRQL。比如 中断发生时陷阱处理器(也可能是处理器)提升了处理器的IRQL到中断源的指定IRQL。被屏蔽的中断或者由其他处理器处理,或者延迟到IRQL降低时处理。因此,系统所有组件,包括内核和设备驱动,都试图使IRQL处于被动级别(有时也称为低级别)。因为这样的话设备驱动能够更及时地响应硬件中断。
    由于改变处理器IRQL有如此重大的影响,所以其只能在内核态下完成。所以执行用户态代码时处理器IRQL总处于被动级别。只有在处理器执行内核态代码时,IRQL才会高些。
    每个中断级别有一个特定目标。比如,内核发布一个interprocessor interrupt (IPI)以请求另一个处理器完成一个动作,比如调度某线程执行或更新translation look-aside buffer (TLB) cache地址转换表。系统时钟在固定间隔时产生中断,内核通过更新时钟并测量线程执行时间来响应。如果硬件平台支持两个时钟,则内核添加一个时钟中断以测量性能。HAL提供了多中断级别用于中断驱动的设备。中断的确切数量是随处理器和系统配置而改变的。内核使用软件中断初始化线程调度以及异步切入某线程执行。
    
    Mapping Interrupts to IRQLs,IRQL与中断控制器定义的中断请求interrupt requests (IRQs)并不相同,windows所运行的硬件平台并没有实现硬件中的IRQL的概念。因此,windows如何确定为特定中断分配相应的IRQL呢?答案存在于HAL中。windows中一种设备设备驱动类型称为总线驱动a bus driver,其决定了存在于其总线(PCI,USB等)上的设备,以及设备能够分配的中断。总线驱动将这个信息报告给Plug and Play管理器,由其在考虑了为所有其他设备进行的可接受的中断分配后决定将中断分配给每个设备。然后其调用一个Plug and Play中断仲裁器,由其将中断映射到IRQL。
    分配算法在windows包含的不同HAL中而不同。在ACPI系统上(包括x86,x64,ia64),HAL通过将分配给IRQ的中断向量除以16作为指定中断的IRQL。至于为IRQ选择中断向量,这取决于系统中断控制器类型。在今天的APIC系统中,这个数字是在循环赛中产生的,所以无法通过中断向量或IRQL计算出IRQ。
    预定义IRQL,从图3-4中的最高级别来看预定义IRQL的使用:
    1 系统只有在中止系统KeBugCheckEx时才会使用高级别并屏蔽所有中断
   Power fail level起源于原始windows NT设计文档,其设定了系统电源错误代码的行为,但这个IRQL从没用过。
   Inter-processor interrupt级用于请求另一个处理器进行一项操作,比如更新处理器TLB缓存,系统关闭,或系统崩溃
   Clock级用于系统时钟,系统用来更新系统时间,同时为线程分配CPU时间
   The system’s real-time clock(或另一个源,比如本地APIC计时器)在内核profiling(一种性能测量机制)使能时使用profile级。当内核profiling激活时,内核profiling陷阱处理器记录中断发生时执行代码的地址。一组地址表会随时间增长而产生,其可用工具导出并分析。使用内核profiling工具Kernrate可以配置并观察profiling产生的统计数据,其在WDK中。
    6 设备IRQL用于为各设备中断判定优先级
   correctible machine check interrupt级是CPU报告了一个严重但(系统)可更正的硬件条件或错误之后使用的。
   DPC/dispatch级 and APC级中断是内核和设备驱动产生的软件中断
    9 最低的passive级其实不是一个中断级,其是在执行正常线程并可接受所有中断时的状态
    对运行在DPC/dispatch级及以上的代码有一个重要限制,即不能等待一个对象,因为这会使调度器选择另一个线程执行,这是一个不合法的操作,因为调度器将其数据结构同步到了DPC/dispatch级,因此不能重新调度。另一个限制是在IRQL DPC/dispatch或更高级时只能访问非分页池。
    这条规则实际是第一条限制的副作用,因为访问不驻留在内存中的内存页会引起页错误。页错误发生时,内存管理器初始化一次磁盘I/O然后等待文件系统驱动从磁盘中读入页。这次等待使得调度器进行上下文切换(可能会交给idle线程,如果没有用户线程运行的话),因此违反了调度器不能调用的规则(因为在等待磁盘读的时候IRQL仍为DPC/dispatch级或更高)
    如果这两条限制中某条违反,系统会崩溃,其崩溃码为IRQL_NOT_LESS_OR_EQUAL或DRIVER_IRQL_NOT_LESS_OR_EQUAL。违反这些限制在设备驱动中是很常见的bug,Windows驱动验证器Driver Verifier可以协助寻找这种bug
    Interrupt Objects,内核提供了一种可移植的机制,称为中断对象的内核控制对象,其允许设备驱动为他们的设备注册ISR。一个中断对象包含所有内核需要的将设备ISR与特定级别中断,包括ISR地址,设备中断的IRQL级别,ISR在内核IDT中的入口 联系起来的所有信息。中断对象初始化时,称为dispatch code的一些汇编语言指令会从中断处理模板KiInterruptTemplate,中复制出来并存储到对象中。中断发生时,这段代码会执行。
    中断对象驻留代码会调用真正的中断调度器,内核的KiInterruptDispatch或KiChainedDispatch例程,传递一个指向中断对象的指针。KiInterruptDispatch是仅有一个中断对象注册的中断向量使用的例程。KiChainedDispatch是多中断对象共享的向量使用的。中断对象包含第二个调度器例程需要用来定位及调用设备驱动提供的ISR的信息。
    中断对象也存储与中断相对应的IRQL,以便KiInterrupt-Dispatch 或 KiChainedDispatch在调用ISR之前提升IRQL到正确级别,以及在ISR例程返回后降低IRQL。这两步是必须的,因为初始化调度时没有传递指针到中断对象的方法,因为初始化调度是硬件完成的。多处理器系统上,内核为每个CPU分配并初始化一个中断对象,在那个CPU上使能本地APIC以接收特定中断。
    另一个内核中断调度器是KiFloatingDispatch,用于需要保存浮点状态的中断。内核态代码无法使用浮点(MMX,SSE,3DNow!)操作,因为那些寄存器在上下文切换时无法保存,而ISR可能需要使用这些寄存器(比如进行快速绘图操作的视频卡ISR)。当connecting一个中断时,驱动能够设置FloatingSave参数为TRUE,请求内核使用浮点调度例程,这将使浮点寄存器得以保存。(然而这会极大地增加中断延迟)而且这只在32位系统上支持
    下图显示了包含中断对象的典型中断控制流程

http://s8/mw690/5d29455egx6BU4w7dz177&690 陷阱调度" TITLE="解析windows内核每日一讲  陷阱调度" />

    将一个ISR与某中断级别相关联称为connecting an interrupt object,将ISR从IDT词条中解关联称为disconnecting an interrupt object。这些操作通过调用内核函数IoConnectInterrupt和IoDisconnectInterrupt实现,使得设备驱动能够在驱动加载进系统时打开ISR,在驱动卸载时关闭ISR
    使用中断对象注册ISR使设备驱动不用直接摆弄中断硬件(随处理器结构不同而不同),也不用知道IDT的细节。这个内核特征助力了可移植设备驱动,因为它消除了编写汇编代码的需要,或在设备驱动中反映处理器差异
    中断对象也提供了其他好处。通过使用中断对象,内核能够将ISR的执行和可能与ISR共享数据的这个设备驱动的其他部分进行同步。
    更进一步,中断对象使得内核能够在同一中断级别调用多个ISR。如果多个设备驱动器创建中断对象并将其连接到同一IDT入口,当在特定行的中断发生时中断调度器会调用每个例程。这使得内核能够轻松地支持“菊花链”配置,在这种配置中,多设备共用一个中断行。在某个ISR返回一个状态给中断调度器声明其为中断所有者时链被自动打破。
    如果共用同一中断的多个设备在同一时间请求服务,没有得到其ISR确认的设备会在中断调度器降低IRQL时再次中断。只用在想使用同一中断的所有设备驱动告知内核它们能够共享中断的情况下它们才允许链。如果它们没有,则Plug and Play管理器重新组织他们的中断分配以确保其尊重了每个中断分配的共享要求。如果中断向量是共享的,中断对象调用KiChainedDispatch,其会调用每个注册中断对象的ISR直到其中一个声明该中断或者执行完了所有ISR。在早先样例!idt的输出中,向量oxa2被连接到了多个链起来的中断对象。
    即使在之前的windows版本中关联与解关联是将内核系统功能从开发者抽象出来的可移植操作,其仍需要从设备驱动开发者获取大量信息,如果这些参数没有适当地填写这会引起很多从细小的bug到严重的硬件损害的后果。作为内核与HAL中许多增加中断机制的功能,vista引进了一项新的API,IoConnectInterruptEx,其添加了对更多高级类型中断的支持(称为message-based interrupts)并且增强了当前对标准中断(也称为line-based interrupts)的支持。新IoConnectInterruptEx API也比其前辈需要更少的参数。去掉的参数主要有vector (interrupt number), IRQL,affinity(姻亲关系),edge versus level-trigged parameters.
    Software Interrupts
    虽然硬件产生了大部分的中断,但windows也为很多任务生成软件中断,包括:
    1 初始化线程调度
    2 非时间关键 中断处理
    3 处理计时器失效
    4 在特定线程上下文异步执行一个过程
    5 支持异步I/O操作
    Dispatch or Deferred Procedure Call (DPC) Interrupts, 当线程不再继续执行时,可能是因为其已经终止或者其自愿进入等待状态,内核直接调用调度器进行上下文切换。然而有时内核会在多层代码的深层得知要重新调度的消息,这时内核会请求调度但会延迟其发生,直到其完成当前活动。使用DPC软件中断可以帮助它完成这项延迟。
    当需要同步对共享内核数据结构的访问时,内核总会将处理器的IRQL提升到DPC/dispatch级或以上。这会屏蔽其他软件中断和线程调度。当内核检测到应当调度时,其会发出一个DPC/dispatch级中断,但由于IRQL不小于这个级别,处理器不会马上响应该中断。当内核完成当前活动时,内核会降低IRQL并查看是否有挂起的中断。如果有,IRQL会降到DPC/dispatch级以下调度中断会得以处理。使用软件中断激活线程调度器是延迟调度到合适条件发生的方法。然而,windows也使用软件中断延迟其他类型的处理。
    除了线程调度,内核在这个IRQL上也处理DPC。DPC函数是执行系统任务的函数----没有当前任务time-critical的任务。函数称为deferred是因为他们可能无法立即执行。
    DPC使得OS能够产生中断并在内核态执行一个系统函数。内核使用DPC处理时间过期(并释放等待计时器的线程)并在线程时间片结束后进行处理器重新调度。为给硬件中断提供及时的服务,在设备驱动的帮助下,windows试图使IRQL维持在设备IRQL以下。一种方法是让设备驱动的ISR完成最少量的工作以响应其设备,保存易变中断状态,延迟数据传输或其他并不time-critical的中断处理活动以在DPC/dispatch IRQL的某DPC中执行。
    一个DPC是由一个DPC对象代表的,它是一个在用户态程序中不可见的内核控制对象,但其对设备驱动和其他系统代码可见。DPC对象包含的最重要的信息是内核处理DPC中断时调用的系统函数地址。等待执行的DPC例程存储在系统的内核管理的队列中,每个处理器一个这样的队列,称为DPC队列。为请求一个DPC,系统代码调用内核初始化一个DPC对象并将其置于一个DPC队列。
    默认情况下,内核将DPC对象置于DPC所请求的处理器(通常是ISR执行的处理器)的DPC队列的末尾。然而设备驱动可以重载这个行为,通过指定DPC优先级(低,中,高,默认是中)并将DPC置于特定处理器上。被指定了CPU的DPC称为targeted DPC。若DPC优先级为低或中,内核会将DPC置于队列尾,若为高,置于队头。
    处理器IRQL从DPC/dispatch级或更高降到更低(比如APC或被动级)时,内核会处理DPC。windows确保IRQL处于DPC/dispatch级并从当前处理器队列中取DPC对象,直到其为空,并调用每个DPC函数。只有在队列为空时,内核才会让IRQL降到低于DPC/dispatch级并让正常线程继续执行。
    DPC优先级会以另一种方式影响系统行为,内核通常会在DPC/dispatch级中断到来后开始从DPC队列中取DPC对象。内核只有在DPC定向在ISR所请求的处理器上且其优先级为中或高时才会产生这样的中断。若此DPC优先级为低,则只有在处理器的outstanding 的DPC请求的个数超过阈值或处理器上请求的DPC数在一个时间窗之内很低

http://s1/mw690/5d29455egx6BYwAhp0kb0&690 陷阱调度" TITLE="解析windows内核每日一讲  陷阱调度" />


    若一个DPC定向于与ISR所运行的不同的CPU,且此DPC优先级为高,内核会立刻向目标CPU发送信号(发送调度IPI)以排干其DPC队列。若优先级为中或低,在目标处理器上排队的DPCs必须超过阈值以使内核触发DPC/dispatch中断。系统空闲线程也会为其所运行的处理器“排干DPC队列”。虽然DPC定向(CPU)和优先级是弹性的,但设备驱动很少需要改变DPC对象的默认行为。下表总结了初始化DPC队列“排干”的情况:

http://s6/mw690/5d29455egx6C0xfDzApf5&690 陷阱调度" TITLE="解析windows内核每日一讲  陷阱调度" />

    由于用户态线程在低IRQL执行,所以一个DPC中断正常线程执行的可能性非常大。DPC例程会无视正在运行的线程是什么,即DPC例程运行时,无法假定当前映射的是什么地址空间。DPC例程可以调用内核函数,但它们无法调用系统服务,产生页错误以及创建或等待调度者对象。然而,它们能够访问非分布系统内存地址,因为系统地址空间总能够得到映射,不管当前进程是什么。
    DPC是主要提供给设备驱动的,但内核也会使用它们。内核最经常是使用DPC处理时间片到期。系统时钟的每次滴答,都会发生一个时钟IRQL的中断。时钟中断handler(运行在时钟IRQL)更新系统时间并减小一个跟踪当前线程运行时间的计数器的值。计数器到0时,线程时间片到期,内核可能需要重新调度处理器,这是一项在DPC/dispatch IRQL级应该做的低优先级的任务。时钟中断handler将一个DPC加入队列以初始化线程调度,然后完成其工作并降低IRQL。因为DPC中断优先级低于设备中断,挂起的设备中断会在时钟中断完成后在DPC中断前处理。
    由于DPC不管当前执行的是哪个线程都会执行,这是系统对客户端系统或工作站处理任务响应不及时的首要原因之一,因为最高优先级的线程也会被挂起的DPC中断。有些DPC跑的时间很长,以致于用户会感到视频或声音卡,甚至会有鼠标或键盘延迟,所以对于有长DPC运行的驱动,windows支持threaded DPCs。
    threaded DPCs,顾名思义,通过在实时优先级(31)线程上的passive级执行DPC例程来发挥功能。这使得DPC可以抢占大部分用户态线程的执行权(因为大部分应用线程不是在实时优先级上运行的),但会允许其它中断,非threaded的DPC,APC或高优先级线程抢占例程执行权。
    threaded DPC的执行机制是默认开启的,但你可以通过将HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SessionManager\Kernel\ThreadDpcEnable的值设置为0将其关闭。因为threaded DPCs能够关闭,所以使用threaded DPCs的驱动开发者必须遵循非threaded DPCs同样的规则,不能访问分页内存,使调度者等待,或者对它们执行时的IRQL级别进行任何的假设。此外,它们不能用KeAcquire/ReleaseSpinLockAtDpcLevel APIs,因为这些函数假定CPU处于dispatch级。threaded DPCs必须使用KeAcquire/ReleaseSpinLockForDpc,其在检查了当前IRQL后进行执行适当的步骤。
    Asynchronous Procedure Call (APC) Interrupts 异步过程调用提供了一种方式使用户程序和系统代码在特定用户线程(由此便有特定进程地址空间)的上下文中执行。因为APC是在特定线程的上下文中排队执行的,并且运行在比DPC/dispatch级更低的IRQL,所以它们没有像DPC那样的限制。APC例程能够获得资源(对象),等待对象句柄,导致页错误以及调用系统服务。
    APC是由内核控制对象,APC对象描述的。等待执行的APC存在于一个内核管理的APC队列中。不像DPC队列,其是系统范围的,APC队列是特定于线程的----即每个线程有自己的APC队列。当请求排队一个APC时,内核将其插入将要执行这个APC的线程的队列中。内核接着会请求一个APC级的软件中断,且当线程最终开始运行时,其执行APC。
    有两种APC:内核态和用户态。内核态APC不需要“允许”便可以在目标线程的上下文运行,而用户态APC需要这种“允许”。内核态APC中断某线程,接着不需要其介入和同意就可以执行一个过程。内核态APC也存在两种:正常的和特殊的。特殊APC执行在APC级,且允许APC例程修改一些APC参数。正常APC在passive级执行,并且从特殊APC例程接收修改过的参数(若未修改则是原始参数)。
    正常和特殊APC都可以被禁用,通过提升IRQL到APC级或调用KeEnterGuardedRegion.KeEnterGuardedRegion会设置 调用线程KTHREAD结构的SpecialApcDisable域 以屏蔽APC传输。线程只能通过调用KeEnterCriticalRegion以设置调用线程KTHREAD结构的KernelApcDisable域 ,从而屏蔽正常APC。下表总结了每种APC的插入和传输行为。

http://s15/mw690/5d29455egx6C6bU5n8Wfe&690 陷阱调度" TITLE="解析windows内核每日一讲  陷阱调度" />

    执行器使用内核态APC完成操作系统的工作,这种工作必须在一个特定线程的地址空间(在上下文)。它能使用特殊内核态的APC,比如,指挥一个线程停止执行一个可中断的系统服务,或者记录 在一个线程地址空间中的一个异步I/O操作的结果。环境子系统使用特殊内核态APC使一个线程暂停或中止自身,或设置或获取其用户态执行上下文。POSIX子系统使用内核态APC仿真POSIX信号到POSIX进程的传输。
    内核态APC的另一个重要使用场合是与线程暂停和中止相关的。因为这些操作能从任意线程初始化,并能对任何线程进行,内核使用APC查询线程上下文以及中止线程。设备驱动会经常,在他们持有一个锁的时候,阻塞APC或者进入一段关键或受保护的区域以阻止这些操作。否则,这些锁可能永远不会被释放,系统会挂起。
    设备驱动也使用内核态APC。比如,如果一个I/O操作初始化了并且一个线程进入等待状态,另一个进程中的一个线程可能被调度执行。当设备传输完数据,I/O系统必须回到初始化I/O的那个线程,以便其能复制I/O的结果到包含那个线程的进程的地址空间的缓存。I/O系统使用特殊的内核态APC进行这个操作,除非应用使用了SetFileIoOverlappedRange API或者I/O完成端口,在这种情况下缓存或者在内存中是全局的,或者是 在线程从端口拉取了一个完整的项后复制过去的。
    几个windows API,比如ReadFileEx, WriteFileEx 和QueueUserAPC,使用用户态APC。比如,ReadFileEx, WriteFileEx 函数允许调用者指定在I/O操作完成时的回调例程。I/O回调例程是通过向发起I/O的线程排队一个APC实现的。然而回调例程在APC排除后并不是必须发生,因为用户态APC只会在其是处于alertable的等待状态的线程时才会传递给它。线程可以通过等待一个对象句柄并指明其等待是alertable的(使用WaitForMultipleObjectsEx函数),或者通过直接测试其是否有挂起的APC(使用SleepEx) 来进入等待状态。在两种情况下,如果用户态APC处于挂起状态,内核中断(alerts)该线程,将控制传递给APC例程,并在APC例程结束时恢复线程的执行。不像内核态APC,能够在APC级执行,而用户态APC只能在passive级执行。
    APC交付会recorder等待队列----即哪些线程在等待什么,以及以什么样的顺序等待 的列表。当APC交付时,如果线程处于等待状态,那么在APC例程执行完后,等待 会重新执行。若等待仍未解决,则线程返回等待状态,但这时其会处于其等待的对象的列表的尾部。比如,因为APC用于暂停线程执行,如果线程在等待任何对象,其等待会被移除,直到线程恢复,在那之后线程会处于某对象的等待线程列表的尾部。进行可唤醒内核态等待的线程也会在某线程中止期间被唤醒,这个线程可以检查其被唤醒是因为有线程中止还是其他原因。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有