一、内核APC执行过程

通过分析 SwapContext ,KiSwapContexgt , KiSwapThread ,我们得出一个结论:切换线程后会执行内核APC,调用的函数是 KiDeliverApc 。

内核APC和用户APC都要由 KiDeliverApc 函数调用,KiDeliverApc 函数首先处理内核APC,然后根据 PreviousMode 参数,用户APC队列是否有数据来判断是否需要处理用户APC。

内核APC的执行比较简单(相对用户APC而言),它是直接在 KiDeliverApc 函数内调用内核APC函数的。

// 调用内核APC函数
(NormalRoutine)(NormalContext,SystemArgument1,SystemArgument2);

二、KiDeliverApc 源码(apcsup.c)

内核APC执行的细节都能在这个函数内分析出来,中文注释是我写的,但即使是看源码,也有不少细节我没分析清楚,也难免会有错误,欢迎读者留言指正。

用户APC的执行我会另写一篇博客介绍。

VOID
KiDeliverApc (IN KPROCESSOR_MODE PreviousMode,IN PKEXCEPTION_FRAME ExceptionFrame,IN PKTRAP_FRAME TrapFrame)/*++Routine Description:This function is called from the APC interrupt code and when one ormore of the APC pending flags are set at system exit and the previousIRQL is zero. All special kernel APC's are delivered first, followedby normal kernel APC's if one is not already in progress, and finallyif the user APC queue is not empty, the user APC pending flag is set,and the previous mode is user, then a user APC is delivered. On entryto this routine IRQL is set to APC_LEVEL.N.B. The exception frame and trap frame addresses are only guaranteedto be valid if, and only if, the previous mode is user.Arguments:PreviousMode - Supplies the previous processor mode.ExceptionFrame - Supplies a pointer to an exception frame.TrapFrame - Supplies a pointer to a trap frame.Return Value:None.--*/{PKAPC Apc;PKKERNEL_ROUTINE KernelRoutine;KLOCK_QUEUE_HANDLE LockHandle;PLIST_ENTRY NextEntry;ULONG64 NewPC;PVOID NormalContext;PKNORMAL_ROUTINE NormalRoutine;ULONG64 PC; PKPROCESS Process;PVOID SystemArgument1;PVOID SystemArgument2;PKTHREAD Thread;PKTRAP_FRAME OldTrapFrame;//// If the thread was interrupted in the middle of the SLIST pop code,// then back up the PC to the start of the SLIST pop. //if (TrapFrame != NULL) {#if defined(_AMD64_)if ((TrapFrame->Rip >= (ULONG64)&ExpInterlockedPopEntrySListResume) &&(TrapFrame->Rip <= (ULONG64)&ExpInterlockedPopEntrySListEnd)) {TrapFrame->Rip = (ULONG64)&ExpInterlockedPopEntrySListResume;}#elif defined(_IA64_)//// Add the slot number so we do the right thing for the instruction// group containing the interlocked compare exchange.//PC = TrapFrame->StIIP + ((TrapFrame->StIPSR & IPSR_RI_MASK) >> PSR_RI);NewPC = (ULONG64)((PPLABEL_DESCRIPTOR)ExpInterlockedPopEntrySListResume)->EntryPoint;if ((PC >= NewPC) &&(PC <= (ULONG64)((PPLABEL_DESCRIPTOR)ExpInterlockedPopEntrySListEnd)->EntryPoint)) {TrapFrame->StIIP = NewPC;TrapFrame->StIPSR &= ~IPSR_RI_MASK;}#elif defined(_X86_)if ((TrapFrame->Eip >= (ULONG)&ExpInterlockedPopEntrySListResume) &&(TrapFrame->Eip <= (ULONG)&ExpInterlockedPopEntrySListEnd)) {TrapFrame->Eip = (ULONG)&ExpInterlockedPopEntrySListResume;}#else
#error "No Target Architecture"
#endif}//// Raise IRQL to dispatcher level and lock the APC queue.//// 获取当前线程Thread = KeGetCurrentThread();OldTrapFrame = Thread->TrapFrame;Thread->TrapFrame = TrapFrame;// 获取当前进程(提供CR3的进程)Process = Thread->ApcState.Process;KeAcquireInStackQueuedSpinLock(&Thread->ApcQueueLock, &LockHandle);//// Get address of current thread object, clear kernel APC pending, and// check if any kernel mode APC's can be delivered.// // 接下来要执行内核APC,这里提前声明处理完毕Thread->ApcState.KernelApcPending = FALSE;// 遍历内核APC队列while (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]) == FALSE) {// 获取 APC,获取 APC 的成员NextEntry = Thread->ApcState.ApcListHead[KernelMode].Flink;Apc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);KernelRoutine = Apc->KernelRoutine;NormalRoutine = Apc->NormalRoutine;NormalContext = Apc->NormalContext;SystemArgument1 = Apc->SystemArgument1;SystemArgument2 = Apc->SystemArgument2;if (NormalRoutine == (PKNORMAL_ROUTINE)NULL) {// NormalRoutine 等于 NULL 的情况属于特殊内核APC,我不知道什么时候会插入这样的APC// 所以这里就不分析了,假如您读到这里,又知道相关的信息,不妨留言提示我一下^_^// 2020年11月29日21:04:21//// First entry in the kernel APC queue is a special kernel APC.// Remove the entry from the APC queue, set its inserted state// to FALSE, release dispatcher database lock, and call the kernel// routine. On return raise IRQL to dispatcher level and lock// dispatcher database lock.// RemoveEntryList(NextEntry);Apc->Inserted = FALSE;KeReleaseInStackQueuedSpinLock(&LockHandle);(KernelRoutine)(Apc,&NormalRoutine,&NormalContext,&SystemArgument1,&SystemArgument2);#if DBG// 蓝屏警告if (KeGetCurrentIrql() != LockHandle.OldIrql) {KeBugCheckEx(IRQL_UNEXPECTED_VALUE,KeGetCurrentIrql() << 16 | LockHandle.OldIrql << 8,(ULONG_PTR)KernelRoutine,(ULONG_PTR)Apc,(ULONG_PTR)NormalRoutine);}#endifKeAcquireInStackQueuedSpinLock(&Thread->ApcQueueLock, &LockHandle);}else{// 走这个分支说明 NormalRoutine 非空,是普通的内核APC,PspTerminateThreadByPointer 和 NtQueueApcThread 都走这里//// First entry in the kernel APC queue is a normal kernel APC.// If there is not a normal kernel APC in progress and kernel// APC's are not disabled, then remove the entry from the APC// queue, set its inserted state to FALSE, release the APC queue// lock, call the specified kernel routine, set kernel APC in// progress, lower the IRQL to zero, and call the normal kernel// APC routine. On return raise IRQL to dispatcher level, lock// the APC queue, and clear kernel APC in progress.//if ((Thread->ApcState.KernelApcInProgress == FALSE) &&    // 没有内核APC正在执行 并且(Thread->KernelApcDisable == 0))                  // 没有禁用内核APC{// 从内核 APC 队列中移除这个 APCRemoveEntryList(NextEntry);// APC Inserted 标志清零Apc->Inserted = FALSE;KeReleaseInStackQueuedSpinLock(&LockHandle);// 调用 KernelRoutine,举两个例子说明// 如果 APC 通过PspTerminateThreadByPointer 构造, KernelRoutine 是 PsExitSpecialApc ,那么执行的操作就是释放APC内存,并终止当前线程// 如果 APC 通过 NtQueueApcThread 构造,KernelRoutine 是 PspQueueApcSpecialApc ,执行的操作仅仅是释放APC内存// 不过 NtQueueApcThread 插入的属于用户APC,不走这里,而是等内核APC执行完后再执行// // KernelRoutine 的工作是释放APC内存,也可能包括一些额外的工作,如退出、挂起、恢复线程// KernelRoutine 是调用 KeInitializeApc 时决定的,是不确定的,各种函数对参数的使用情况都不一样// 例如 PspTerminateThreadByPointer 初始化 KernelRoutine 传的函数是 PsExitSpecialApc ,就只使用了第一个参数 Apc(KernelRoutine)(Apc,&NormalRoutine,&NormalContext,&SystemArgument1,&SystemArgument2);#if DBGif (KeGetCurrentIrql() != LockHandle.OldIrql) {KeBugCheckEx(IRQL_UNEXPECTED_VALUE,KeGetCurrentIrql() << 16 | LockHandle.OldIrql << 8 | 1,(ULONG_PTR)KernelRoutine,(ULONG_PTR)Apc,(ULONG_PTR)NormalRoutine);}#endif// NormalRoutine 是内核APC函数,经分析,我觉得能执行到这里,NormalRoutine 应该不是 NULL 的// 唯一可能修改 NormalRoutine 的就是上面调用的 KernelRoutine 函数if (NormalRoutine != (PKNORMAL_ROUTINE)NULL) {// 内核APC正在执行Thread->ApcState.KernelApcInProgress = TRUE;// 降低IRQL到0KeLowerIrql(0);// 调用内核APC函数(NormalRoutine)(NormalContext,SystemArgument1,SystemArgument2);// 恢复IRQL到APC_LEVEL(1)KeRaiseIrql(APC_LEVEL, &LockHandle.OldIrql);}KeAcquireInStackQueuedSpinLock(&Thread->ApcQueueLock, &LockHandle);// 没有内核APC正在执行Thread->ApcState.KernelApcInProgress = FALSE;} else {KeReleaseInStackQueuedSpinLock(&LockHandle);goto CheckProcess;}}}//// Kernel APC queue is empty. If the previous mode is user, user APC// pending is set, and the user APC queue is not empty, then remove// the first entry from the user APC queue, set its inserted state to// FALSE, clear user APC pending, release the dispatcher database lock,// and call the specified kernel routine. If the normal routine address// is not NULL on return from the kernel routine, then initialize the// user mode APC context and return. Otherwise, check to determine if// another user mode APC can be processed.//// 内核APC执行完毕// 如果 PreviousMode 是用户模式(1),并且有用户APC,并且用户APC队列非空if ((IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]) == FALSE) &&(PreviousMode == UserMode) && (Thread->ApcState.UserApcPending != FALSE)) {// 提前声明用户APC队列已清空Thread->ApcState.UserApcPending = FALSE;// 获取APC和其属性NextEntry = Thread->ApcState.ApcListHead[UserMode].Flink;Apc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);KernelRoutine = Apc->KernelRoutine;NormalRoutine = Apc->NormalRoutine;NormalContext = Apc->NormalContext;SystemArgument1 = Apc->SystemArgument1;SystemArgument2 = Apc->SystemArgument2;// 从用户APC队列中取出RemoveEntryList(NextEntry);// 标记插入状态为FALSEApc->Inserted = FALSE;KeReleaseInStackQueuedSpinLock(&LockHandle);// KernelRoutine 应该就是 PspQueueApcSpecialApc // 因为用户APC是 NtQueueApcThread 函数构造和插入的,它就是这样初始化APC的// PspQueueApcSpecialApc 的唯一作用是释放APC内存(KernelRoutine)(Apc,&NormalRoutine,&NormalContext,&SystemArgument1,&SystemArgument2);if (NormalRoutine == (PKNORMAL_ROUTINE)NULL) {// 此函数定义在 thredobj.c           KeTestAlertThread(UserMode);} else {// 准备回3环调用 NormalContextKiInitializeUserApc(ExceptionFrame,TrapFrame,NormalRoutine,     // 用户APC总入口 BaseDispatchAPC(3环函数)NormalContext,       // 3环APC函数SystemArgument1,  // 3环APC函数的参数SystemArgument2);  // 作用不明,BaseDispatchAPC 里用到了}} else {KeReleaseInStackQueuedSpinLock(&LockHandle);}//// Check if process was attached during the APC routine.// 检查当前进程是否发生变化(执行 APC 函数时发生了 attach)CheckProcess:if (Thread->ApcState.Process != Process) {// 蓝屏警告KeBugCheckEx(INVALID_PROCESS_ATTACH_ATTEMPT,(ULONG_PTR)Process,(ULONG_PTR)Thread->ApcState.Process,(ULONG)Thread->ApcStateIndex,(ULONG)KeIsExecutingDpc());}Thread->TrapFrame = OldTrapFrame;return;
}

三、总结

  1. 内核APC在线程切换的时候就会执行,这也就意味着,只要插入内核APC
    很快就会执行。

  2. 在执行用户APC之前会先执行内核APC。

  3. 内核APC在内核空间执行,不需要换栈,一个循环全部执行完毕。

(75)内核APC执行过程,分析 KiDeliverApc 函数相关推荐

  1. 4.内核APC执行过程

    APC函数的执行与插入并不是同一个线程: 在A线程中向B线程插入一个APC,插入的动作是在A线程中完成的,但什么时候执行则由B线程决定!,所以叫"异步过程调用" 内核APC函数与用 ...

  2. linux进程上下文切换的具体过程,Linux实验三 结合中断上下文切换和进程上下文切换分析Linux内核一般执行过程...

    fork系统调?创建?进程,也就?个进程变成了两个进程,两个进程执?相同的代码,只是fork系统调?在?进程和?进程中的返回值不同. 打开linux-5.4.34/arch/x86/entry/sys ...

  3. 5.用户APC执行过程

    当产生系统调用.中断或者异常,线程在返回用户空间前都会调用, _KiServiceExit函数,在_KiServiceExit会判断是否有要执行的用户APC,如果有则调用KiDeliverApc函数( ...

  4. linux内核make执行过程

    本篇基于上一篇<<linux内核make menuconfig执行过程>>基础上,追溯make执行过程. make 1. 与make menuconfig相同的部分 这部分内容 ...

  5. linux执行class文件_深入理解linux内核——可执行文件执行过程(2)

    接上篇.. 13.调用do_mmap()函数创建一个新线性区来对可执行文件正文段(即代码)进行映射.这个线性区的起始线性地址依赖于可执行文件的格式,因为程序的可执行代码通常是不可重定位的.因此,这个函 ...

  6. linux 文件可执行_深入理解linux内核——可执行文件执行过程(2)

    接上篇.. 13.调用do_mmap()函数创建一个新线性区来对可执行文件正文段(即代码)进行映射.这个线性区的起始线性地址依赖于可执行文件的格式,因为程序的可执行代码通常是不可重定位的.因此,这个函 ...

  7. Windows APC学习笔记(二)—— 挂入过程执行过程

    Windows APC学习笔记(二)-- 挂入过程&执行过程 基础知识 挂入过程 KeInitializeApc ApcStateIndex KiInsertQueueApc Alertabl ...

  8. 深度揭秘 Promise 微任务和执行过程

    Promise 大伙太熟悉了,不过这里不讲大伙都知道的表面简单知识,而是一起来深入剖析 Promise 的注册微任务和执行的完整过程.能正确的使用 Promise 且能做到知其然知其所以然~ 本文分为 ...

  9. LINUX内核分析第八周总结:进程的切换和系统的一般执行过程

    一.进程调度与进程切换 1.不同的进程有不同的调度需求 第一种分类: I/O密集型(I/O-bound) 频繁的进行I/O 通常会花费很多时间等待I/O操作的完成 CPU密集型(CPU-bound) ...

最新文章

  1. Ixia张林辉:测试系统让SDN更“迷人”
  2. C/C++ ini配置文件的格式及如何读写ini配置文件
  3. 图谱实战 | 京东商品图谱构建与实体对齐
  4. vbe代码对齐插件_写代码需要注意的问题
  5. tomcat内存优化
  6. 腾讯区块链专利申请量排名全国第一;摩拜超20.56万单车被破坏;Nginx 1.17.7发布 | 极客头条...
  7. asp.net基础 笔试题(全解完整答案)
  8. excel有条件的隐藏某行_暂时隐藏Excel条件格式
  9. python eel 多线程_Python的一个轻量级桌面GUI开发第三方库:Eel
  10. 一周AI看点 | 北航设立全国首个人工智能专业,前IBM沃森首席科学家任京东副总裁
  11. 如何评价微擎?怎么看待微擎模块应用?
  12. Redis Eviction policies (驱逐策略)
  13. 股票期货化数据文档大全覆盖国内6大易的历史数据和实时行情
  14. 关于深度可分离卷积 Depthwise Pointwise Convolution
  15. 【HEOI2012】采花
  16. Exception in thread main java.lang.NoSuchMethodError: scala.collection.immutable.HashSet$.empty()L
  17. activiti flowable 开源工作流引擎项目整合开发实施实践总结
  18. 常用的几种音频传输格式
  19. Python写得好,壁纸无烦恼!
  20. 简单实现 Android M 指纹识别(附源码)

热门文章

  1. 腾讯数据中心负责人揭秘:半年时间如何搭好“山洞鹅厂”
  2. 使用 做签名的post_java组件HuTool相关工具类的使用(五)
  3. Cloud Computing:云网端融合的简介、层次、典型代表、未来趋势之详细攻略
  4. 成功解决运行tensorflow时ModuleNotFoundError: No module named ‘numpy.core._multiarray_umath‘
  5. DL之simpleNet:利用自定义的simpleNet(设好权重)对新样本进行预测、评估、输出梯度值
  6. JPEG图片扩展信息读取与改动
  7. VOS3000设置落地网关优先级
  8. 搭建本地 Registry - 每天5分钟玩转 Docker 容器技术(20)
  9. sql添加列,删除列,修改列
  10. 【转】最小编辑距离 算法原理