kernel 3.10内核源码分析--中断--中断和异常返回流程
一、问题
1、内核调度与中断/异常/系统调用的关系如何?
2、信号处理与中断/异常/系统调用的关系如何?
3、内核抢占与中断/异常/系统调用的关系如何?
4、内核线程的调度有何特别之处?中断/异常/系统调用返回时,内核线程会发生调度吗?
这些问题都需要分析清楚中断/异常的返回流程,才能解答。
二、中断/异常的返回流程
1、中断/异常的返回内核软件流程
中断、异常(包括系统调用)和fork返回的处理流程如下:
关键点:
1)中断返回和异常返回的流程基本一致,差别主要在于异常返回时,需要先关一次中断。因为Linux实现中,异常使用的是陷阱门,通过时不会自动关中断;而中断使用的是中断门,通过时会自动关中断。
2)中断/异常(包括系统调用)返回时,是进行调度(schedule)的重要时机点,其中,中断(时钟中断)返回时调度依赖的最主要的时机点,时钟中断处理函数中不会直接进行调度,只是根据相应的调度算法,决定是否需要调度,以及调度的next task,如果需要调度,则设置NEED_RESCHED标记。调度(schedule)的实际执行是在中断返回的时候,检查NEED_RESCHED标记,如果设置则进行调度。
3)信号处理是在当前进程从内核态返回用户态时进行的,在发生中断、异常(包括系统调用)、或fork时,都有可能从内核态返回用户态,都是处理信号的时机。注意:只有current进程的信号才能在此时得到处理。其它非正在运行的进程的信号无法处理。
4)关于内核抢占。中断/异常发生在内核态时,也就是说中断/异常返回时,需要返回内核态,走resume_kernel流程,此时,如果内核支持内核抢占,则此时是个关键的调度时机点,如果内核不支持抢占,则不会发生调度。也就是说:如果当前进程上下文处于内核态,当不支持内核抢占时,则无论进程的优先级和时间片如何,都是不能发生调度的,只能在返回用户态时,才能发生调度。从这点可以看出,当不支持内核抢占时,Linux的实时性很差(开启内核抢占后稍好),当在内核态(中断、软中断、其它内核流程)执行时间或流程太长时,可能导致进程调度饥饿,极端情况下,当在内核态发生死锁时,会直接导致整个系统因无法调度而死锁,当然针对这种情况(softlockup),内核提供了专门的watchdog机制来检测。
5)关于内核线程的调度,跟普通线程相比,从原理和机制上看,没有特别之处。但关键的不同在于:内核线程始终运行在内核态,当没有开启内核抢占时,设想当一个内核线程被中断/异常打断,此时从中断/异常返回时会发生调度吗?答案是不会,因为当前进程的上下文处于内核态,在没有开启内核抢占的情况下,是不会发生调度行为的,除非该内核线程主动调用schedule()释放CPU控制权。也就是说,内核线程触发主动调用schedule,否则会一直占用CPU。所以在编写内核线程时,需要在相关任务处理结束后,主动调用schedule,这点需要注意。
2、中断/异常返回时硬件完成的处理流程
中断或异常返回时,必然会执行iret指令,然后将控制器交回给之前被中断打断的进程,硬件自动完成如下操作:
1)从当前栈(内核栈)中弹出cs、eip和eflag,并load到相应的寄存器中寄存器。(如之前有硬件错误码入栈,需要先弹出这个错误码)。
2)权限检查。比对ISR的CPL是否等于cs中的低两位的值。如果是,iret终止返回;否则,转入下一步。
3)从当前栈(内核栈)中弹出之前压入的用户态堆栈相关的ss和esp,并load到相应寄存器,至此,即完成了从内核栈到用户栈的切换。
4)后续处理。主要包括:检查ds、es、fs及gs段寄存器,如果其中一个寄存器包含的选择符是一个段描述符,并且其DPL值小于CPL,那么,清相关的段寄存器。目的是为了防止用户态的程序利用内核以前所用的段寄存器,以防止恶意用户程序利用其访问内核地址空间。
三、代码分析
中断、异常(包括系统调用)、fork返回时,会分别跳转到entry_32.S汇编代码中的ret_from_intr、ret_from_exception、ret_from_fork标号处执行。相应代码分析如下:
1、中断返回(ret_from_intr)
1)主流程
点击(此处)折叠或打开
- /*从中断返回*/
- ret_from_intr:
- /*将当前进程的thread_info结构体的指针存入%ebp帧寄存器中*/
- GET_THREAD_INFO(%ebp)
- #ifdef CONFIG_VM86
- /* 取中断之前寄存器EFLAGS的高16位和段寄存器CS的内容构成的32位长整数放入eax中,其目的是检验:
- * 1.中断之前CPU是否运行于VM86模式
- * (EFLAGS的高16位中的第二位用来标识CPU运行在VM86模式下)
- * 2.中断之前CPU运行于用户空间还是系统空间
- *(CS的低两位代表着中断发生时CPU的运行级别CPL。若是CS的低两位为1,表示中断发生于用户空间,)
- */
- movl PT_EFLAGS(%esp), %eax # mix EFLAGS and CS
- movb PT_CS(%esp), %al
- andl $(X86_EFLAGS_VM | SEGMENT_RPL_MASK), %eax
- #else
- /*
- * We can be coming here from child spawned by kernel_thread().
- */
- movl PT_CS(%esp), %eax
- andl $SEGMENT_RPL_MASK, %eax
- #endif
- // 判断是否返回用户态或者v8086模式,如果不是,则转入resume_kernel,否则进入resume_userspace
- cmpl $USER_RPL, %eax
- jb resume_kernel # not returning to v8086 or userspace
2)返回用户态
点击(此处)折叠或打开
- // 如果是返回用户态
- ENTRY(resume_userspace)
- LOCKDEP_SYS_EXIT
- /*
- * 前面已经关了中断了,这次再关的原因是,还有其它流程会自己跳转
- * 到这里,比如system_call
- */
- DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don
3)调度和信号处理:
点击(此处)折叠或打开
- work_pending:
- # 返回用户态时,只需要判断need_resched是否置位,不需要判断preempt_count
- # 如果need_resched置位,则发生调度,否则跳转到work_notifysig
- testb $_TIF_NEED_RESCHED, %cl
- # 进行信号处理
- jz work_notifysig
- work_resched:
- # 需要调度,调用schedule函数
- call schedule
- # 调度返回,注意:到这里已经是新的进程上下文了,后面有机会处理信号
- LOCKDEP_SYS_EXIT
- # 关中断
- DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
- # setting need_resched or sigpending
- # between sampling and the iret
- # 关闭trace irq功能
- TRACE_IRQS_OFF
- movl TI_flags(%ebp), %ecx
- # 再次确认是否还有其它事情处理
- andl $_TIF_WORK_MASK, %ecx # is there any work to be done other
- # than syscall tracing?
- # 如果没有,则恢复上下文
- jz restore_all
- # 如果有,再次检查是否需要调度,如果需要,则再次跳转到work_resched进行重新调度
- testb $_TIF_NEED_RESCHED, %cl
- jnz work_resched
- # 如果不需要调度,则继续到work_notifysig,进行信号处理了,也就是说如果这里发生调度,也是有机会处理信号的。
- work_notifysig: # deal with pending signals and
- # notify-resume requests
- #ifdef CONFIG_VM86
- testl $X86_EFLAGS_VM, PT_EFLAGS(%esp)
- movl %esp, %eax
- jne work_notifysig_v86 # returning to kernel-space or
- # vm86-space
- 1:
- #else
- movl %esp, %eax
- #endif
- # 开trace
- TRACE_IRQS_ON
- # 开中断(前面关了),意味着信号处理是开中断执行的,还是中断优先级高
- ENABLE_INTERRUPTS(CLBR_NONE)
- # 再次判断CS低两位,当为1时,表示中断/异常之前处于用户态,否则为内核态,据此再次确认是否需要返回内核态
- movb PT_CS(%esp), %bl
- andb $SEGMENT_RPL_MASK, %bl
- cmpb $USER_RPL, %bl
- # 返回内核态
- jb resume_kernel
- # edx清零
- xorl %edx, %edx
- # 调用C函数,其中进行通知链即信号的处理
- call do_notify_resume
- # 信号处理完后,重新跳转到resume_userspace,此时如果没有新的信号产生,则会在前面就通过restore_all恢复了,不会再到这里了
- jmp resume_userspace
- #ifdef CONFIG_VM86
- ALIGN
- work_notifysig_v86:
- pushl_cfi %ecx # save ti_flags for do_notify_resume
- call save_v86_state # %eax contains pt_regs pointer
- popl_cfi %ecx
- movl %eax, %esp
- jmp 1b
- #endif
- END(work_pending)
4)返回内核态
点击(此处)折叠或打开
- /*如果配置了内核抢占*/
- #ifdef CONFIG_PREEMPT
- ENTRY(resume_kernel)
- /*
- * 前面已经关了中断了,这次再关的原因是,还有其它流程会自己跳转
- * 到这里,比如system_call
- */
- DISABLE_INTERRUPTS(CLBR_ANY)
- /*判断是否可以抢占*/
- cmpl $0,TI_preempt_count(%ebp) # non-zero preempt_count ?
- /*抢占计数非0,不能抢占,则不产生调度,直接恢复上下文*/
- jnz restore_all
- /*可以抢占,则需要调度*/
- need_resched:
- /*判断need_resched是否被设置*/
- movl TI_flags(%ebp), %ecx # need_resched set ?
- testb $_TIF_NEED_RESCHED, %cl
- /*没设置need_resched,则不需要调度,直接恢复上下文*/
- jz restore_all
- /*
- *判断发生中断时(因为PT_EFLAGS(%esp)中保存的是进入中断时的EFLAGS值,这是由CPU硬件自动压栈的,中断走中断门,会自动关中断,异常走陷阱门,不自动关中断)是否关中断了,
- *如果关了,表示是异常上下文(Fixme:应该是中断吧),则直接恢复上下文。
- */
- testl $X86_EFLAGS_IF,PT_EFLAGS(%esp) # interrupts off (exception path) ?
- jz restore_all
- /*如果没关中断,表示为中断上下文?则调用preempt_schedule_irq,进行调度*/
- call preempt_schedule_irq
- jmp need_resched
- END(resume_kernel)
2、异常返回(ret_from_exception)
异常返回跟中断返回流程基本一致,差别主要在于异常返回时,需要先关一次中断。因为Linux实现中,异常使用的是陷阱门,通过时不会自动关中断;而中断使用的是中断门,通过时会自动关中断。
点击(此处)折叠或打开
- /*从异常返回*/
- ret_from_exception:
- /*
- * 这里为什么要关中断?而从中断返回不需要? 因为异常走的是陷阱门,
- * 默认是不关中断执行的,而中断走的是中断门,默认是关中断执行的?
- *
- */
- /*关中断*/
- preempt_stop(CLBR_ANY)
- /*从中断返回*/
- ret_from_intr:
- ...
3、fork返回(ret_from_fork)
fork返回的后半部分处理跟异常/中断返回一致,前面一部分有单独的处理:包括调用schedule_tail和跳转syscall_exit进行相关处理
点击(此处)折叠或打开
- #fork返回,单独处理
- ENTRY(ret_from_fork)
- CFI_STARTPROC
- pushl_cfi %eax
- #进行调度收尾处理,包括回收DEAD(X)状态的进程
- call schedule_tail
- #获取thread_info放入ebp中
- GET_THREAD_INFO(%ebp)
- popl_cfi %eax
- #重设kernel eflags
- pushl_cfi $0x0202 # Reset kernel eflags
- popfl_cfi
- #跳转到syscall_exit进行系统调用退出相关的处理。
- jmp syscall_exit
- CFI_ENDPROC
- END(ret_from_fork)
原文地址: http://blog.chinaunix.net/uid-14528823-id-4761421.html
kernel 3.10内核源码分析--中断--中断和异常返回流程相关推荐
- Linux kernel 3.10内核源码分析--进程上下文切换
一.疑问 进程调度时,当被选中的next进程不是current进程时,需要进行上下文切换. 进行上下文切换时,有一些问题不太容易理解,比如: 1.进程上下文切换必然发生在内核态吗? 2.上下文切换后原 ...
- Linux Kernel 3.10内核源码分析--块设备层request plug/unplug机制
一.基本原理 Linux块设备层使用了plug/unplug(蓄流/泄流)的机制来提升IO吞吐量.基本原理为:当IO请求提交时,不知直接提交给底层驱动,而是先将其放入一个队列中(相当于水池),待一定时 ...
- Linux kernel 3.10内核源码分析--TLB相关--TLB概念、flush、TLB lazy模式
一.概念及基本原理 TLB即Translation Lookaside Buffer,是MMU中的一种硬件cache,用于缓存页表,即缓存线性地址(虚拟地址)到物理地址的映射关系. 如果没有TLB,那 ...
- kernel 3.10内核源码分析--内核栈及堆栈切换
1.概念 Linux中有3种栈: 1)用户栈.当进程处于用户态时使用,位于进程地址空间(用户态部分(如:0-0xc0000000))底部,用户态分配局部变量和函数调用时时,使用该栈,跟平时我们见到和理 ...
- Linux kernel 3.10内核源码分析--slab原理及相关代码
1.基本原理 我们知道,Linux保护模式下,采用分页机制,内核中物理内存使用buddy system(伙伴系统)进行管理,管理的内存单元大小为一页,也就是说使用buddy system分配内存最少需 ...
- Linux kernel 3.10内核源码分析--进程退出exit_code
进程退出时,有相应的exit_code,可用于判断进程退出的原因. 比如,waitpid()接口用于等待进程退出,此时被等待退出的进程的返回值比较重要,需要用其来判断进程退出的相应状态,而这就是通过进 ...
- ernel 3.10内核源码分析--KVM相关--虚拟机运行
1.基本原理 KVM虚拟机通过字符设备/dev/kvm的ioctl接口创建和运行,相关原理见之前的文章说明. 虚拟机的运行通过/dev/kvm设备ioctl VCPU接口的KVM_RUN指令实现, 在 ...
- v42.05 鸿蒙内核源码分析(中断切换) | 系统因中断活力四射 | 百篇博客分析鸿蒙源码
子曰:"知者不惑,仁者不忧,勇者不惧." <论语>:子罕篇 百篇博客系列篇.本篇为: v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 硬件架构相关 ...
- 鸿蒙内核分析,鸿蒙内核源码分析(中断概念篇) | 外人眼中权势滔天的当红海公公...
关于中断部分系列篇将用三篇详细说明整个过程. ● 中断概念篇 中断概念很多,比如中断控制器,中断源,中断向量,中断共享,中断处理程序等等.本篇做一次整理.先了解透概念才好理解中断过程.本篇的主角是海公 ...
最新文章
- TorchScript神经网络集成技术
- 美国防部官员讨论量子科学、5G和定向能的发展
- Windows Server中的故障转移群集的实现机制
- SAPscripts 到导数程序中取数据的实例
- js 浅拷贝直接赋值_js 深拷贝 vs 浅拷贝
- 4-Qt6控制台项目信号与槽
- iOS面试题02-UI篇
- Django项目配置mysql主从数据库实现读写分离
- python二分法查找时间点_python有序查找算法:二分法
- VS2010删除所有断点时不弹出提示窗口
- 将区块链哈希转化为文字标题?IPSE哈希技术Hashlink解释
- Linux文件系统选择
- Snmp网络协议及Java开发相关
- GitHub遭遇史上最强DDoS攻击:峰值流量1.35Tbps!
- mysql详细教程大全
- MATLAB 矩阵及运算
- java读取txt文件入库_java读取txt文件批量入库
- 正三角形二面体群表示为二阶矩阵形式
- 测试一枚-记录工作-覆盖率(二)
- FOFA网络空间搜索引擎使用教程