调度时机分析之被动调度(之中断处理返回)

本文深入剖析了Linux内核2.6.12.6版本中被动调度机制下的中断返回过程,重点介绍了用户态抢占调度的具体实现细节,包括中断处理函数的调用流程、中断返回时的上下文切换及调度决策。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前一篇博客中是关于被动调度的系统调用返回部分,这篇博客将接着写被动调度的中断返回部分。

 

分析基于内核版本2.6.12.6

Linux进程的调度主要分为主动调度和被动调度两大类。整个linux运行过程中,被动调度分为用户态抢占调度和内核态抢占调度。

用户态抢占调度发生在当系统调用、中断处理、异常处理等返回用户态时,或者进程的时间片用完时。

这篇博客就是写用户态抢占调度的中断处理返回部分的,是看代码的一个总结,写下来希望能够加深理解。

 

中断处理返回

当从中断返回时是一个调度点,下面分析从中断返回相关的代码。

ENTRY(common_interrupt  /*中断公共入口*/

interrupt do_IRQ  

/* 0(%rsp): oldrsp-ARGOFFSET */  

ret_from_intr:    

popq  %rdi  

cli     //关中断

subl $1,%gs:pda_irqcount  

#ifdef CONFIG_DEBUG_INFO  

movq RBP(%rdi),%rbp  

#endif  

leaq ARGOFFSET(%rdi),%rsp  

exit_intr:     

GET_THREAD_INFO(%rcx       //获取当前内核栈的thread_info的地址保存到rcx寄存器中

testl $3,CS-ARGOFFSET(%rsp //读取栈中的cs,判断中断是否发生在用户态

je retint_kernel        //中断发生在内核态,返回内核态

common_interrupt函数是中断的公共入口,它首先使用用宏interrupt来调用do_IRQ函数处理中断。ret_from_intr标签开始就是执行完中断之后,中断返回相关的代码。

先来看下interrupt宏的实现代码:

/* 0(%rsp): interrupt number */  

 .macro interrupt func  

 CFI_STARTPROC simple  /*用在每个函数的开始用于初始化一些内部数据结构*/

 CFI_DEF_CFA rsp,(SS-RDI 

 CFI_REL_OFFSET rsp,(RSP-ORIG_RAX 

 CFI_REL_OFFSET rip,(RIP-ORIG_RAX 

 cld   /*清方向标志, 在串指令操作期间,方向标志为DISI寄存器选择递增方式或递减方式。如果D=1.则寄存器内容自动地递减;如果D=0,则寄存器内容自动地递增*/

SAVE_ARGS     /*保存现场*/

 leaq -ARGOFFSET(%rsp),%rdi # arg1 for handler  

testl $3,CS(%rdi  /*判断中断发生时CPU是否运行于用户态*/

 je 1f           /*是内核态*/

 swapgs         /*切换gs寄存器的用户态值和内核值*/

1: addl $1,%gs:pda_irqcount # RED-PEN should check preempt count  

 movq %gs:pda_irqstackptr,%rax  

 cmoveq %rax,%rsp         

 pushq %rdi   # save old stack   

 call \func     /*调用func函数*/

 .endm  

SAVE_ARGS宏是将寄存器压栈,也就是所谓的“保存现场”。运行SAVE_ARGS宏之后系统堆栈的示意图如下:

代码段寄存器cs的最低两位代表着中断发生时cpu的运行级别CPL0表示内核态,3表示用户态。如果最低两位为非0,则说明中断发生于用户空间。此宏中判断中断发生时cpu是否运行于用户态,如果是内核态则直接跳转到标签1处执行,否则先调用swapgs命令切换gs寄存器的用户态值和内核值。Testl指令将$3CS(%rdi)相与,je就是当相与结果为0,也就是为内核态。

接下来就是将当前cpupdapda_irqcount字段加1,并将pda中的pda_irqstackptr字段赋给rsp,即中断栈帧。最后使用call命令调用中断处理函数func,也就是do_IRQ函数。do_IRQ函数的参数为pt_regs结构,参数是通过rdi寄存器传入的。

从ret_from_intr开始处理中断返回,如果中断发生在内核态,则返回内核态,跳转到retint_kernel执行;如果中断发生在用户态,则返回用户态,跳转到retint_with_reschedule执行。

retint_kernel将在内核态抢占调度部分分析,这里是分析中断处理返回时用户态抢占的情况,因此接下来看看retint_with_reschedule代码。

/* Interrupt came from user space */

/*

 * Has a correct top of stack, but a partial stack frame

 * %rcx: thread info. Interrupts off.

 */

retint_with_reschedule:

movl $_TIF_WORK_MASK,%edi

retint_check:

movl threadinfo_flags(%rcx),%edx   

andl %edi,%edx   

jnz  retint_careful  //判断是否还有其他工作需要做有的话跳转到retint_careful执行

retint_swapgs: 

swapgs     //当处理器离开内核时使用swapgs命令在gs寄存器的内核与用户态值之间切换

retint_restore_args:

cli    

RESTORE_ARGS 0,8,0

iret_label:

iretq

首先判断是否还有其他工作需要处理,有其他工作处理的情况下跳转到retint_careful执行。没有其他工作处理则首先调用swapgs命令在gs寄存器的内核与用户态值之间切换,为离开内核做准备。然后调用RESTORE_ARGS宏恢复之前SAVE_ARGS宏保存现场所压栈的那些寄存器。最后就是调用iretq离开中断。

下面看下当还有其他工作需要处理时retint_careful的执行情况。

/* edi: workmask, edx: work */

retint_careful:

bt    $TIF_NEED_RESCHED,%edx     //检查是否需要重新调度

jnc   retint_signal      //不需要重新调度跳转到retint_signal

sti        //开中断

pushq %rdi

call  schedule

popq %rdi

GET_THREAD_INFO(%rcx)

cli       //关中断

jmp retint_check

首先检查是否需要重新调度,不需要重新调度就跳转到retint_signal执行。需要重新调度的话先开中断,然后调用schedule函数执行任务的调度。当调度的任务执行完之后,重新获得cpu时,跳转到retint_check处再次检查是否有工作需要处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值