idle thread向其他任务线程的上下文切换过程

        以下文章讲解均以arm64架构、Linux 5.10.158源码为例子。

        在Linux kernel中,idle thread向其他任务线程的上下文切换,需要经历两大过程,分别是进程地址空间的切换、以及堆栈寄存器的转换。

        1、Linux kernel任务线程切换的过程context_switch

        context_switch切换过程大致如下所示。文章的这一part主要借鉴参考了这篇文章:深入理解Linux内核进程上下文切换[1],读者们可以阅读这篇文章更清楚的了解进程上下文切换的过程。

context_switch()
    ->switch_mm_irqs_off() //上下文页表切换
    ->switch_to() //进程堆栈寄存器切换

        1.1 进程地址空间的切换

        在arm64架构中,寄存器ttbr0_el1指向非特权模式下的地址空间转换的页表基地址,指向的是初始转换表,用于较低的虚拟地址(VA)范围。寄存器ttbr1_el1指向特权模式下的地址空间转换的页表基地址,用于较高的虚拟地址范围。进程切换前后,需要改写寄存器ttbr0_el1。

switch_mm_irqs_off()
    ->switch_mm()
        ->__switch_mm()
            ->check_and_switch_context()
                ->cpu_switch_mm()
                    ->cpu_do_switch_mm()
                        ->write_sysreg(ttbr1,ttbr1_el1)
                        ->write_sysreg(ttbr0,ttrb0_el1)

        在切换过程中,实际完成寄存器改写的是函数write_sysreg(ttbr1,ttbr1_el1)和write_sysreg(ttbr0,ttbr0_el1)。 这两个函数将ttbr1和ttbr0的值各自写入到ttbr1_el1寄存器和ttbr0_el1寄存器中。

        1.2 堆栈进程寄存器的切换

        下图代码介绍了堆栈寄存器切换过程的函数路径以及关键汇编代码。需要说明的是,语句 mov x10, #THREAD_CPU_CONTEXT,表示x10存储了thread.cpu_context成员相对于结构体struct task_struct的偏移量。struct task_struct有一个数据类型为struct thread_struct的thread成员,而thread有一个数据类型为struct cpu_context的cpu_context成员。

switch_to()
    ->__switch_to(struct task_struct *prev, struct task_struct *next)
        ->cpu_switch_to(prev,next)

SYM_FUNC_START(cpu_switch_to)
	mov	x10, #THREAD_CPU_CONTEXT
	add	x8, x0, x10
	mov	x9, sp
	stp	x19, x20, [x8], #16		// store callee-saved registers
	stp	x21, x22, [x8], #16
	stp	x23, x24, [x8], #16
	stp	x25, x26, [x8], #16
	stp	x27, x28, [x8], #16
	stp	x29, x9, [x8], #16
	str	lr, [x8] //x8寄存器加了6次16后,指向的是cpu_context中的pc寄存器
	add	x8, x1, x10
	ldp	x19, x20, [x8], #16		// restore callee-saved registers
	ldp	x21, x22, [x8], #16
	ldp	x23, x24, [x8], #16
	ldp	x25, x26, [x8], #16
	ldp	x27, x28, [x8], #16
	ldp	x29, x9, [x8], #16
	ldr	lr, [x8]
	mov	sp, x9
	msr	sp_el0, x1
	ptrauth_keys_install_kernel x1, x8, x9, x10
	scs_save x0, x8
	scs_load x1, x8
	ret
SYM_FUNC_END(cpu_switch_to)
NOKPROBE(cpu_switch_to)

        在上述代码中,x0和x1寄存器分别存储了prev和next的结构体开头地址。add x8,x0,x10语句,使得x8寄存器存储了prev结构体中thread.cpu_context地址值。stp x19, x20, [x8], #16语句,表示将x19和x20寄存器的值存储到x8寄存器所指的地址中(即prev的thread.cpu_context成员的x19、x20),随后x8寄存器向高位移动16个字节。str lr,[x8]语句中的lr寄存器,用于存储程序返回地址。

        在最后的ret汇编语句中,CPU将返回至lr所指的地址,在此处代指next线程的pc寄存器。到此为止,CPU从next线程上一次退出的地址开始运行。结合以下struct cpu_context结构体成员代码,可进一步明晰以上汇编代码的含义和意图。

struct cpu_context {
	unsigned long x19;
	unsigned long x20;
	unsigned long x21;
	unsigned long x22;
	unsigned long x23;
	unsigned long x24;
	unsigned long x25;
	unsigned long x26;
	unsigned long x27;
	unsigned long x28;
	unsigned long fp;
	unsigned long sp;
	unsigned long pc;
};

        2、idle thread向其他任务线程的切换过程函数路径图。

        下图为idle thread与其他任务线程的切换过程函数路径示意图。在这幅图中,idle thread即是0号线程。蓝色箭头代表函数内完整执行函数,如if need_resched()和if (cpu_idle_force_poll || tick_check_broadcast_expired() ),这两条语句的函数都是发生在do_idle()函数中。红色箭头代表函数与函数间的关系是递进式的,如schedule_idle()、__schedule()、context_switch()的关系,schedule_idle()调用函数__schedule(),而__schedule()调用context_switch()。context_switch()同时调用switch__mm_irqs_off()和switch_to()。虚线箭头表示省略调用过程中的嵌入递进关系。

        在上图中,外层循环的判断语句if need_resched() 是判断当前运行队列是否存在有需要调度的任务,注意这里是0号线程在执行这条判断语句,若need_resched()判定为真,即有其他任务线程需要切入,则CPU跳出内层循环,不执行idle state的状态。

        在内层循环中的判断语句if (cpu_idle_force_poll || tick_check_broadcast_expired()),有个变量cpu_idle_force_poll,这个变量是指CPU空闲时是否会进入轮询状态,若该变量为真,则CPU不会进入睡眠状态,而是会执行语句asm volatile("" : : : "memory")。这种模式通常在需要CPU快速响应时使用,但是会使CPU使用率达到100%,因此需要在低耗能和高实时性之间做出抉择。若使该变量为真,则需要使能编译选项CONFIG_GENERIC_IDLE_POLL_SETUP,同时在grub文件内配置nohlt选项。

        内层循环的判断语句还有个函数tick_check_broadcast_expired(),检查是否有任何CPU需要被唤醒。处于唤醒CPU的需要,系统会使用一个“广播设备”的特殊定时器来发送唤醒信号。tick_check_broadcast_expire()函数就是用于检查是否有CPU需要通过广播设备来唤醒。

        若条件cpu_idle_force_poll || tick_check_broadcast_expired()一直为真,则程序会一直进行内层循环,不断执行函数cpu_idle_poll()。当程序退出内层循环,开始进行切换线程的工作,idle thread执行函数schedule_idle(),选择合适的任务线程,并在函数context_switch()中进行进程地址空间的切换和堆栈进程寄存器的切换。当idle thread执行到ret汇编语句时,此时链接寄存器lr存储的是目标任务线程的返回地址,也就是在这时,idle thread在真正意义上完成了向目标任务线程的切换。

        在下一篇文章中,我将介绍其他任务线程切换回idle thread的函数调用过程。

引用:[1] 深入理解Linux内核进程上下文切换-腾讯云开发者社区-腾讯云

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值