Windows驱动开发(10) - 驱动程序的同步处理(一)

Windows驱动开发(10) - 驱动程序的同步处理(一)

1、基本概念

1.1 可重入与不可重入

可重入,是指函数的执行结果不和执行顺序有关。反之,如果执行结果和执行顺序有关,则称这个函数是“不可重入”的。

1.2 同步与异步

1) 同步就是指一个线程要等待上一个线程执行完之后才开始执行当前的线程。
2) 异步是指一个线程去执行,它的下一个线程不必等待它执行完就开始执行

2、中断请求级

在Windows的时候,设计者将中断请求分别划分为软件中断和硬件中断,并将这些中断都映射成不同级别的中断请求级(IRQL)。

2.1 中断请求(IRQ)与可编程中断控制器(PIC)

中断请求(IRQ)一般有两种,一种是外部中断,也就是硬件产生的中断;
另一种是由软件指令int n产生的中断。

IRQ编号设备名称用途
IRQ0Tine计算机系统计时器
IRQ1KeyBoard键盘
IRQ2RedirectIRQ9与IRQ9相接,MPU-401MDI使用该IRQ
IRQ3COM2串口设备
IRQ4COM1串口设备
IRQ5LPT2建议声卡使用该IRQ
IRQ6FDD软驱传输控制用
IRQ7LPT1打印机传输控制用
IRQ8CMOSAlert即时时钟
IRQ9RedirectIRQ2与IRQ2相接。可设定给其他硬件使用
IRQ10Reversed建议保留给网卡使用该IRQ
IRQ11Reversed建议保留给AGP显卡使用
IRQ12PS/2Mouse按PS/2鼠标,若无也可以设定给其他硬件使用
IRQ13FPU协处理器用,例如FPU(浮点运算器)
IRQ14PrimaryIDE主硬盘传输控制用
IRQ15Secondarylde从硬盘传输控制用

2.2 高级可编程控制器(APIC)

  传统PC一般使用2片Intel 8259A中断控制器,然而,面在的X86计算机都是应用高级可编译控制器,即Advanced Programmable Interrupt Controller(ACIC)
  APIC兼容PIC,且APIC把IRQ的数据增加到了24个,我们可以用设备管理器查看这24个中断。

2.3 中断请求级(IRQL)

  在APIC中,IRQ的数量增加到了24个,每个IRQ有各自的优先级别,正在运行的线程随时可被中断打断,进入到中断处理程序。当优先级高的中断来临时,处在优先级低的中断处理程序,也会被打断,进入到更高级的中断处理函数。
  Windows将中断进入了扩展,提出一个中断请求级(IRQL)的概念。其中规定了32个中断请求级别,分别是0-2级别为软件中断,3-31为硬件中断。
  Windows将24个IRQ映射到了从DISPATCH_LEVEL到PROFILE_LEVEL之间,不同硬件的中断处理程序运行在不同的IRQL级别中。硬件IRQL称为设备中断请求级,或称DIRQL。Windows大部分时间运行在软件中断级别中。当设备中断来临时,操作系统提升IRQL至DIRQL级别,并且运行中断处理函数。
IRQL

  用户模式的代码是运行在最低优先级的PASSIVE_LEVEL级别。驱动程序的DriverEntry函数,派遣函数、AddDevice等函数一般是运行在PASSIVE_LEVEL级别,它们在必要时可以申请进入DISPATCH_LEVEL级别。
  Windows负责线程调度的组件是运行在DISPATCH_LEVEL级别,当前线程运行完时间片后,系统自动从PASSIVE_LEVEL级别升到DISPATCH_LEVL级别。当线程切换完毕后,又从DISPATCH_LEVEL级别降到PASSIVE_LEVEL级别。
  在内核模式下,可以通过调用KeGetCurrentIrql内核函数来得到当前的IRQL级别。

2.4 线程调试与线程优先级

 在APP编程中,会听到线程优先级概念。线程优先级和IRQL是两个容易混淆的概念。所有的应用程序都运行在PASSIVE_LEVEL级别上,它的优先级别最低,可以被其他IRQL级别的程序打断。
 线程优先级是指线程是否有更多机会运行在CPU上,线程优先级高的线程有更多的机会被内核调用。
 ReadFile内部创建IRP_MJ_READ,然后这个IRP被传递到驱动程序的派遣函数中,这时派遣函数运行于ReadFile所在的线程中,或者说ReadFile和派遣函数位于同一个线程上下文中。

2.5 IRQL的变化

  为了更好理解IRQL概念我们描述一个线程运行过程。这个线程在运行中,被一个中断打断,并且在中断服务执行时,被更高级的中断打断,运行的过程如下图,线程运行分为以下几个阶段。
(1)阶段1:一个普通线程A正在运行;
(2)阶段2:这个时刻有一个中断发生,它的IRQL为0xD。CPU中断当前运行的线程A,将IRQL提升到0xD级别。
(3)阶段3:这时有一个更高级别的中断发生,它的IRQL是0x1A 。这时CPU将IRQL提升到0x1A级别。
(4)阶段4:这时候又有一个中断发生,但它的IRQL为0x18,低于上一个中断优先级。CPU不会理睬这个中断。
(5)阶段5:这时IRQL为0x1A 的中断结束,OS进入IRQL为0x18的中断服务。
(6)阶段6:这时IRQL为0x18中断结束,于是进入IRQL为0xD 的中断服务。
(7)阶段7:最后IRQL为0xD 的中断结束,操作系统恢复线程A。

IRQL的变化

  线程运行在PASSIVE_LEVEL级别,这个时候OS随时可能将当前线程切换到别的线程。但是如果提升IRQL到DISPATCH_LEVEL级别,这时会不会出现线程切换。这是一种很常用的处理机制,但这种方法只能使用于单CPU的系统。对于多CPU的系统,需要采用别的同步处理机制。

2.6 IRQL与内存分页

  在使用内存分页时,可能会导致页故障。因为分页内存随时可能从物理内存交换到磁盘文件。读取不在物理内存中的分页时,会引发一个页故障,从而执行这个异常的处理函数。异常处理函数会重新将磁盘文件的内容交换到物理内存中。
  页故障允许出现在PASSIVE_LEVEL级别的程序中,但如果在DISPATCH_LEVEL或者更高级别IRQL的程序中会带来系统崩溃。
  对于等于或高于DISPATCH_LEVEL级别的程序不能使用分页内存,必须使用非分页内存。驱动程序的StartIO全程、DPC例程、中断服务例程都运行在DISPATCH_LEVEL或更高的IRQL,因为这些例程不能使用分页内存,否则会导致系统崩溃。

2.7 控制IRQL提升与降低

  有些时候驱动程序中需要提升IRQL级别,在运行一段时间后,再降回原来的IRQL级别,这样做的目的一般基于同步处理的需要。
  首先驱动程序需要知道当前状态是什么IRQL级别,可以通过KeGetCurrentIrql内核函数获取当前的IRQL级别。
  然后驱动程序使用内核函数KeRaiseIrql将IRQL提高。KeRaiseIrql需要两个参数,第一个参数是提升后的IRQL级别,第二个参数保存提升前的IRQL级别。
  最后,驱动程序在某个时刻需要将IRQL恢复到以前的IRQL级别,驱动程序可以调用KeLowerIrql内核函数。下面的代码演示了在驱动中如何提升与降低IRQL级别:

VOID RasiseIRQL_Test()  
{  
    KIRQL oldirql;  
    // 确保当前IRQL等于或小于DISPATCH_LEVEL  
    ASSERT(KeGetCurrentIrql() <= DIPATCH_LEVEL);  
    // 提升IRQL到DISPATCH_LEVEL,并将先前的IRQL保存起来  
    KeRaiseIrql(DISPATCH_LEVEL, &oldirql);  
    //...  
    // 恢复到先前的IRQL  
    KeLowerIrql(oldirql);  
}  
已标记关键词 清除标记
表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符 “速评一下”
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页