内核呓语系列7 —— DPC与APC

2014-2-15 Nie.Meining Coding

今天接着讲中断后端。前面已经说过了,中断处理例程的优先级太高,有很多限制,尤其是不能让出处理器影响机器响应能力,因此,为了让中断例程处理尽可能少的的工作,Linux和Windows都采用了一些方法把次要工作延后到中断处理例程之外的中断后端去处理。Windows采用的方式是插入DPC,Linux中常用的方式有以下几种:
1. BH接口:最古老的方式。系统中总共只能有32个BH(用一个32位变量做mark),每个BH都是全局同步的(即使在多核环境中),因此很不灵活且效率很低,已经被取缔了;
2. task queues:内核定义了一堆task queue,驱动可以在合适的queue中注册他们的中断后端。依然不灵活、性能低,已取缔;
3. softirq: 一组静态定义(编译时静态分配)的中断后端,能在不同的处理器上并行执行(即使是两个相同类型的softirq),因此常用于对性能要求很高的情况。softirq不会被另一个softirq抢占,但会被中断处理例程抢占;
4. tasklet: 动态添加的中断后端,在softirq的基础上实现,能在不同的处理器上并行执行,但两个相同类型的tasklet不能并行执行。因此虽然性能略低于softirq,但性价比最高,是最常用的中断后端方式;
5. work queues:将任务排队到进程上下文中执行,每个cpu有一个event/n内核线程worker threads,复制处理排队的任务。这个跟Windows的work item机制(ExQueueWorkItem)很像。Windows系统中的System进程中有一个系统辅助线程池,池中的系统辅助线程专门处理排队的work item。因此有时候IRQL过高时,可以将任务以work item的形式排队给系统辅助线程在PASSIVE_LEVEL上执行。

因此,Linux中断后端的核心实际上是softirq,softirq和Windows的DPC机制很像,每个CPU核心一组。softirq属于中断上下文,而DPC处在DISPATCH_LEVEL中,因此他们都不能被阻断,不能等待也不能睡。此外在softirq基础上实现的tasklet由于高性价比,成为了最常用的中断后端机制。对于tasklet,每个cpu核心有两组结构:tasklet_vec(常规tasklets)和tasklet_hi_vec(高优先级tasklets)。此外,tasklet始终运行在schedule它的cpu上(也许能优化cpu cache)。
Windows中的DPC同样有两种类型,分别是普通DPC和线程DPC。普通DPC可以在任何线程中运行,但是线程DPC只能运行在专门的线程中。每个cpu的kprcb中有两个dpc链,即DpcData数组,两个元素分别是普通dpc链和线程dpc链。线程的DPC运行在专门执行DPC的线程中,这样的线程每个CPU一个,通过调用KiExecuteDpc循环查找DPC并处理。该线程的线程优先级最高,但IRQL依然是PASSIVE_LEVEL。

对于普通dpc,根据其优先级被插入到dpc链表的头部或尾部。并且普通DPC的IRQL级别是DISPATCH_LEVEL。

既然提到了dpc,就顺便把apc也一起讲了。首先DPC是针对全系统的,而APC是针对线程的,用于投递异步操作,比如异步方式的读写文件等,以及执行外来代码,比如插APC杀线程。结束线程的原理其实就是投递APC让其自杀。

每个线程有两个_KAPC_STATE结构:

1111.png2222.png

ApcState和SavedAptState用来存储对应的APC,attach到别的进程中时必须把没处理完的APC保存到SavedApcState中,detach的时候再恢复。这里有一个规律:ApcState总是包含要在当前进程环境中执行的APC对象,ApcStateIndex总是指向ApcState在ApcStatePointer数组中的下标。

APC分两种模式:用户模式APC和内核模式APC。_KAPC_STATE结构中的两条链表分别对应用户模式APC和内核模式APC:

3333.png

用户apc运行在用户模式下目标线程的当前上下文中,并且需要目标线程处在alertable等待状态才能被成功执行(SleepEx/KeWaitForSingleObject等)。(MJ:另外通过调用一个未公开的alert-test服务KeTestAlertThread,用户线程可以使用户模式APCs执行)。

内核APC执行在内核模式下,又分为普通和特殊两类。用户模式APC在所有内核模式APC执行完后才执行,并且仅在目标线程有alertable属性时才执行。

APC由结构_KAPC表示:

4444.png

ApcMode域代表用户模式或内核模式;KernelRoutine指向在APC LEVEL上执行的内核模式函数;NormalRoutine指向在PASSIVE_LEVEL上执行的函数;RundownRoutine指向在线程终止时执行的函数。对于内核模式APC,当NormalRoutine为空时则是所谓的“特殊APC”。特殊APC在APC链头部,优先级比普通APC高。因此需要先执行完特殊APC,再执行普通APC(最后才是用户模式APC)。另外,普通APC执行KernelRoutine后,会在PASSIVE_LEVEL上执行NormalRoutine,此时有可能被特素APC打断(NormalRoutine的IRQL太低)。

写了一大堆,今天就到这里吧……

发表评论:

Powered by emlog