谈谈对APC的一点理解
异步过程调用(APCs) 是NT异步处理体系结构中的一个基础部分,理解了它,对于了解NT怎样操作和执行几个核心的系统操作很有帮助。
1) APCs允许用户程序和系统元件在一个进程的地址空间内某个线程的上下文中执行代码。 尽管APCs在nt体系结构下被广泛使用,但是关于怎样使用它的文档却非常的缺乏。本篇我们详细介绍下nt系统是怎样处理APCs的,并且记录导出的nt函数,方便设备驱动开发者在他们的程序中使用APCs。我也会展示一个非常可靠的NT的APC调度子程序KiDeliverApc的实现,来帮助你更好的掌握APC调度的内幕。 APC对象 在NT中,有两种类型的APCs:用户模式和内核模式。用户APCs运行在用户模式下目标线程当前上下文中,并且需要从目标线程得到许可来运行。特别是,用户模式的APCs需要目标线程处在alertable等待状态才能被成功的调度执行。通过调用下面任意一个函数,都可以让线程进入这种状态。这些函数是:KeWaitForSingleObject, KeWaitForMultipleObjects, KeWaitForMutexObject, KeDelayExecutionThread。 当一个用户模式APC被投递到一个线程,调用上面的等待函数,如果返回等待状态STATUS_USER_APC,在返回用户模式时,内核转去控制APC例程,当APC例程完成后,再继续线程的执行. 和用户模式APCs比较,内核模式APCs执行在内核模式下。可以被划分为常规的和特殊的两类。当APCs被投递到一个特殊的线程,特殊的内核模式APCs不需要从线程得到许可来运行。然而,常规的内核模式APCs在他们成功执行前,需要有特定的环境。此外,特殊的内核APC被尽可能快地执行,既只要APC_LEVEL级上有可调度的活动。在很多情况下,特殊的内核APC甚至能唤醒阻塞的线程。普通的内核APC仅在所有特殊APC都被执行完,并且目标线程仍在运行,同时该线程中也没有其它内核模式APC正执行时才执行。用户模式APC在所有内核模式APC执行完后才执行,并且仅在目标线程有alertable属性时才执行。 每一个等待执行的APC都存在于一个线程执行体,由内核管理的队列中。系统中的每一个线程都包含两个APC队列,一个是为用户模式APCs,另一个是为内核模式APCs的。 //------------------------------------------------------------------------------------------------------- 几个函数声明和结构定义: typedef struct _KAPC { APC环境 一个线程在它执行的任意时刻,假设当前的IRQL是在Passive级,它可能需要临时在其他的进程上下文中执行代码,为了完成这个操作,线程调用系统功能函数KeAttachProcess,在从这个调用返回时,线程执行在另一个进程的地址空间。先前所有在线程自己的进程上下文中等待执行的APCs,由于这时其所属进程的地址空间不是当前可用的,因此他们不能被投递执行。然而,新的插入到这个线程的APCs可以执行在这个新的进程空间。甚至当线程最后从新的进程中分离时,新的插入到这个线程的APCs还可以在这个线程所属的进程上下文中执行。 typedef struct _KAPC_STATE { lkd> dt _kthread 主APC环境是位于线程对象的ApcState 域,即: 线程中等待在当前进程上下文中执行的APC保存在ApcState的队列中。无论何时,NT的APC派发器(dispatcher)和其他系统元件查询一个线程未决的APCs时, 他们都会检查主APC环境,如果这里有任何未决的APCs,就会马上被投递,或者修改它的控制变量稍后投递。 第二个APC环境是位于线程对象的SavedApcState域,当线程临时挂接到其他进程时,它是用来备份主APC环境的。 当一个线程调用KeAttachProcess,在另外的进程上下文中执行后续的代码时,ApcState域的内容就被拷贝到SavedApcState域。然后ApcState域被清空,它的APC队列重新初始化,控制变量设置为0,当前进程域设置为新的进程。这些步骤成功的确保先前在线程所属的进程上下文地址空间中等待的APCs,当线程运行在其它不同的进程上下文时,这些APCs不被传送执行。随后,ApcStatePointer域数组内容被更新来反映新的状态,数组中第一个元素指向SavedApcState域,第二个元素指向ApcState域,表明线程所属进程上下文的APC环境位于SavedApcState域。线程的新的进程上下文的APC环境位于ApcState域。最后,当前进程上下文切换到新的进程上下文。 对于一个APC对象,决定当前APC环境的是ApcStateIndex域。ApcStateIndex域的值作为ApcStatePointer域数组的索引来得到目标APC环境指针。随后,目标APC环境指针用来在相应的队列中存放apc对象. 当线程从新的进程中脱离时(KeDetachProcess), 任何在新的进程地址空间中等待执行的未决的内核APCs被派发执行。随后SavedApcState 域的内容被拷贝回ApcState域。SavedApcState 域的内容被清空,线程的ApcStateIndex域被设为OriginalApcEnvironment,ApcStatePointer域更新,当前进程上下文切换到线程所属进程。 使用APCs 设备驱动程序使用两个主要函数来利用APCs, 第一个是KeInitializeApc,用来初始化APC对象。这个函数接受一个驱动分配的APC对象,一个目标线程对象指针,APC环境索引(指出APC对象存放于哪个APC环境),APC的kernel,rundown和normal例程指针,APC类型(用户模式或者内核模式)和一个上下文参数。 函数声明如下: NTKERNELAPI typedef enum _KAPC_ENVIRONMENT { KeInitializeApc 首先设置APC对象的Type和Size域一个适当的值,然后检查参数Environment的值,如果是CurrentApcEnvironment,那么ApcStateIndex域设置为目标线程的ApcStateIndex域。否则,ApcStateIndex域设置为参数Environment的值。随后,函数直接用参数设置APC对象Thread,RundownRoutine,KernelRoutine域的值。为了正确地确定APC的类型,KeInitializeApc 检查参数NormalRoutine的值,如果是NULL,ApcMode域的值设置为KernelMode,NormalContext域设置为NULL。如果NormalRoutine的值不是NULL,这时候它一定指向一个有效的例程,就用相应的参数来设置ApcMode域和NormalContext域。最后,KeInitializeApc 设置Inserted域为FALSE.然而初始化APC对象,并没有把它存放到相应的APC队列中。 从这个解释看,你可以了解到APCs对象如果缺少有效的NormalRoutine,就会被当作内核模式APCs.尤其是它们会被认为是特殊的内核模式APCs. typedef typedef 通常,无论是什么类型,每个APC对象必须要包含一个有效的KernelRoutine 函数指针。当这个APC被NT的APC dispatcher传送执行时,这个例程首先被执行。用户模式的APCs必须包含一个有效的NormalRoutine 函数指针,这个函数必须在用户内存区域。同样的,常规内核模式APCs也必须包含一个有效的NormalRoutine,但是它就像KernelRoutine一样运行在内核模式。作为可选择的,任意类型的APC都可以定义一个有效的RundownRoutine,这个例程必须在内核内存区域,并且仅仅当系统需要释放APC队列的内容时,才被调用。例如线程退出时,在这种情况下,KernelRoutine和NormalRoutine都不执行,只有RundownRoutine执行。没有这个例程的APC对象会被删除。 记住,投递APCs到一个线程的动作,仅仅是操作系统调用KiDeliverApc完成的。执行APC实际上就是调用APC内的例程。 一旦APC对象完成初始化后,设备驱动调用KeInsertQueueApc来将APC对象存放到目标线程的相应的APC队列中。这个函数接受一个由KeInitializeApc完成初始化的APC对象指针,两个系统参数和一个优先级增量。跟传递给KeInitializeApc函数的参数context 一样,这两个系统参数只是在APC的例程执行时,简单的传递给APC的例程。 NTKERNELAPI KiInsertQueueApc 仅仅接受一个APC对象和一个优先级增量。这个函数首先得到线程APC队列的spinlock并且持有它,防止其他线程修改当前线程的APC结构。随后,检查APC对象的Inserted 域。如果是TRUE,表明这个APC对象已经存放到APC队列中了,函数立即返回FALSE.如果APC对象的Inserted 域是FALSE.函数通过ApcStateIndex域来确定目标APC环境,然后把APC对象存放到相应的APC队列中,即将APC对象中的ApcListEntry 域链入到APC环境的ApcListHead域中。链入的位置由APC的类型决定。常规的内核模式APC,用户模式APC都是存放到相应的APC队列的末端。相反的,如果队列中已经存放了一些APC对象,特殊的内核模式APC存放到队列中第一个常规内核模式APC对象的前面。如果是内核定义的一个当线程退出时使用的用户APC,它也会被放在相应的队列的前面。然后,线程的主APC环境中的UserApcPending域杯设置为TRUE。这时KiInsertQueueApc 设置APC对象的Inserted 域为TRUE,表明这个APC对象已经存放到APC队列中了。接下来,检查这个APC对象是否被排队到线程的当前进程上下文APC环境中,如果不是,函数立即返回TRUE。如果这是一个内核模式APC,线程主APC环境中的KernelApcPending域设置为TRUE。 在WIN32 SDK文档中是这样描述APCs的: 当一个APC被成功的存放到它的队列后,发出一个软中断,APC将会在线程被调度运行的下一个时间片执行。然而这不是完全正确的。这样一个软中断,仅仅是当一个内核模式的APC(无论是常规的内核模式APC还是特殊的内核模式APC)针对于调用线程时,才会发出。随后函数返回TRUE。 1)如果APC不是针对于调用线程,目标线程在Passive权限等级处在等待状态; NTSYSAPI NT的APC派发器 NT检查是否线程有未决的APCs. 然后APC派发器子程序KiDeliverApc在这个线程上下文执行来开始将未决的APC执行。注意,这个行为中断了线程的正常执行流程,首先将控制权给APC派发器,随后当KiDeliverApc完成后,继续线程的执行。 究竟是执行(1)还是(2)取决于新线程所处的IRQL级别. 如果它的权限级别高于Passive级,SwapContext 执行(1),如果它是在Passive级,则选择执行(2). 当内核调用KiDeliverApc来执行一个用户模式APC时,线程中的PreviousMode域被设为UserMode. TrapFrame域指向线程的陷阱帧。当内核调用KiDeliverApc来执行内核APCs时,线程中的PreviousMode域被设为KernelMode. TrapFrame域指向NULL。 结论: |
谈谈对APC的一点理解相关推荐
- 学习金字塔——谈谈对学习的一点理解
学习金字塔是美国缅因州的国家训练实验室研究成果,它用数字形式形象显示了:采用不同的学习方式,学习者在两周以后还能记住内容(平均学习保持率)的多少. 根据知识留存率以及学习形式,将学习分为了主动学习和被 ...
- mixin机制 vue_谈谈vue中mixin的一点理解
谈谈vue中mixin的一点理解 vue中提供了一种混合机制--mixins,用来更高效的实现组件内容的复用.最开始我一度认为这个和组件好像没啥区别..后来发现错了.下面我们来看看mixins和普通情 ...
- 面试精讲之面试考点及大厂真题 - 分布式专栏 04 谈谈你对分布式的理解,为什么引入分布式?
04谈谈你对分布式的理解,为什么引入分布式? 引言 刚刚毕业第一份工作,没接触过分布式微服务相关的知识,后来换工作才了解到这些,面试官看了我简历里写了分布式相关,就开始揪住这个问题问,虽然一知半解地说 ...
- 分布式 - 谈谈你对分布式的理解,为什么引入分布式?
不啰嗦,我们直接开始! 划重点: 真正了解分布式系统的概念,日后工作中具有分布式系统设计思想. 能否在设计中对系统稳定性方面考虑周全. 能构建高 QPS 健壮的系统架构. 1.面试官:那谈谈你对分布式 ...
- c语言中字符串关于左值,关于左值lvalue和右值rvalue的一点理解
发现很多朋友对"lvalue"和"rvalue"理解有误,我先谈谈自己对此的一些理解,并期望能够引起更多朋友的广泛讨论.也算起到抛砖引玉的作用吧.引用:注:这里 ...
- python 3列表推导式的的一点理解!
python 3列表推导式的的一点理解! Python的列表推导式对于新手来说一般都难以理解,简单看个例子: [x * x for x in range(1,100)] 上面是一个很简单的列表推导式, ...
- JVM面试1 :谈谈你对Java的理解
谈谈你对Java的理解 如何查看字节码? 使用 javap
- MVVM是什么?谈谈你对MVVM的理解?
MVVM是什么?谈谈你对MVVM的理解? 我的理解MVVM它就是一种前端开发的架构模式,作用就是为了让前端业务逻辑和HTML代码更加分离.它的核心思想就是把每个页面分成了M(Model数据模型).V( ...
- TSYS2.0对动态碎片的一点理解
最近好多人对tsys很感兴趣,其实大家不知道的是tsys的asp版本早就已经停止开发了,为了不想让如此优秀的东西被埋没所以我在tsys的asp版 本的基础上使用PHP重新开发了一个功能更强的cms,名 ...
最新文章
- POJ 3630 Phone List
- Python函数定义和函数调用
- 2018浙大计算机考研经验,考研经验2018中科大计算机考研第一398分初复试经验诚挚分享.docx...
- 使用NuGet安装EntityFramework4.2
- 终于解决 k8s 集群中部署 nodelocaldns 的问题
- java web教室管理系统_“真香”教程,455集全套java视频教程,自己拿走去学习吧...
- 淘宝双十一火爆下的暗礁?
- 地图看世界,给整个世界画一条“胡焕庸线”
- 让Acrobat5支持Office XP(转)
- 揭秘北京龙泉寺,连清华北大学子都排队出家的神秘科研组织
- 解决插入word文档中的图片变得不清晰问题
- 1162开放英语4 (2)
- 【附代码实现】Attention注意力模块的keras\tf实现(ECA、BAM、Coordinate、DualAttention、GlobalContext等)
- (二)R语言数据结构——Vector, Matrix, List
- 荒野大镖客2-解压即玩
- 西门子ET200SP基座单元的区别与分类以及注意事项
- pandas 从文本中提取数字(正则表达式)
- 使用python在Lotus Notes发送邮件
- ttcam 怎么用_婴儿不喝水怎么办啊?怎样才可以让婴儿喝水呢?
- vue科大稽核系统登录页面