ARM Switch_to调用机制及调用过程

Switch_to调用机制及调用过程

        ARM Switch_to调用机制及调用过程_第1张图片

在/kernel/sched/core.c中有内核调度的核心函数:__schedule,在调度函数中通过调用context_switch进行进程上下文的切换。

context_switch对函数的调用见上图所示,主要完成的工作是mm的切换和硬件上下文的切换,我们主要讨论硬件上下文切换过程。

整体的调用过程如下图:

ARM Switch_to调用机制及调用过程_第2张图片

ARM Switch_to调用机制及调用过程_第3张图片

在arch/arm/include/asm/switch_to.h中有对switch_to的定义:

ARM Switch_to调用机制及调用过程_第4张图片

在源码中可以发现,switch_to(prev,next,last)是个宏定义,真正的操作在do{}中。

根据do{}的内容可知:

首先执行了__complete_pending_tlbi()函数,根据其定义的解释便知其作用是:在armV7架构中,运行的是抢占式内核,因此在TLB维护操作时需要进行抢占,因此为了能够在转移至另一个CPU工作前将维护工作完成,需要执行dsb(ish)来进行保证。因此说,__complete_pending_tlbi()只在armV7架构中其作用。

ARM Switch_to调用机制及调用过程_第5张图片

所以switch_to的重头戏便是:

last = __switch_to(prev,task_thread_info(prev),task_thread_info(next));

真正传递参数的为prev的task_struct、prev的task_thread_info以及next的task_thread_info,返回值为last。因此下边一节进入__switch_to的汇编进行此过程的详细分析。

通过源码的查找,在两个.S文件中发现了__switch_to的定义:arch/arm/kernel/entry-armv.S以及arch/arm/kernel/entry-v7m.S,这两个汇编文件中的__switch_to函数分别对应于ARMv3/4处理器和ARMv7处理器,首先对entry-armv.S进行分析。

ARMv3/4架构Switch_to汇编代码:

首先是函数注释,通过注释我们发现对应的处理器是ARMv3和ARMv4,并且传入的三个参数为:r0 = previous task_struct,r1 = previous thread_info, r2 = next thread_info



ENTRY、ENDPROC分别是对函数头尾的声明,.fnstart是函数开始执行的标志,这些我们在这里不讨论。主要分析被上述两块包含的内容。

通过对代码的大致分析,姑且先将其分为4部分进行分块分析:

1、Store mostregs on previous thread_info


         这块代码的工作就是定为ip即r1(preciousthread_info)中#TI_CPU_SAVE的位置,并将r4-sl,fp,sp,lr这几个寄存器的值存入ip所指向的相应的位置。

         (1)第一句add    ip, r1, #TI_CPU_SAVE

使用了伪指令TI_CPU_SAVE,通过定位,找到了TI_CPU_SAVE的声明位置:在arch/arm/kernel/asm-offset.c中有相应的定义,具体见下图:

ARM Switch_to调用机制及调用过程_第6张图片

即TI_CPU_DOMAIN指向了相应thread_info中的cpu_context,进一步对thread_info这一结构体进行分析:

位于arch/arm/include/asm/thread_info.h中

ARM Switch_to调用机制及调用过程_第7张图片

发现:cpu_context是另一个结构体,同样定义在此头文件中:

ARM Switch_to调用机制及调用过程_第8张图片


         因此此时ip便指向r1àcpu_context的地址,即相应结构体cpu_context_save的首地址。

         (2)存储寄存器的值:

         在源码中发现对于寄存器的存储过程有ARM和THUMB两种操作方式:


经过查阅发现在THUMB状态下,stmia命令最多store8个字,即r4-sl,fp,剩余的两个寄存器(sp、lr)需要再调用str进行store。

因此,总的来说这段代码的功能就是将r4-sl,fp,sp,lr寄存器的值存入previous thread_info的cpu_context位置,来保存上下文寄存器的值。

ARM Switch_to调用机制及调用过程_第9张图片

         2、Set r4 r5 r6 r7 r8

ARM Switch_to调用机制及调用过程_第10张图片


这块源码的主要目的是对r4、r5、r6、r7、r8赋值,过程大致可以分为对r4、r5赋值,对r6赋值,对r7、r8赋值。

(1)r4、r5赋值


在这段代码中用到了另一个伪指令:TI_IP_VALUE

在同样的位置(arch/arm/kernel/asm-offset.c),我们找到了TI_IP_VALUE的声明,其同样指向结构体thread_info,具体为tp_value,观察thread_info可知,tp_value为一个长度为2的一维数组,存储的值注释为TLSregister(TLS即Thread Local Storage,可以高效的访问TLS里面存储的信息而不用一次次的调用系统调用),具体源码如下:


因此,r4、r5分别被赋予r2(nextthread_info)的tp_value[0]和tp_value[1]。


这句话是执行switch_tls,通过对switch_tls的查找,定位到:arch/arm/include/asm/tls.h

ARM Switch_to调用机制及调用过程_第11张图片

通过对其中一种情况进行分析:

ARM Switch_to调用机制及调用过程_第12张图片

即将协处理器c13的值赋予tmp2(r7),并将tp(r4)、tpuser(r5)赋予c13,进而将tmp2(r7)的值存到[base]+#TI_IP_VALUE+4即(r1àtp_value[1])。

因此,这一步将协处理c13原来的值存入r1相应位置,同时将r4、r5的值赋给协处理器c13。同时通过对switch_tls的分析知道了,r4、r5存储的值其实是TLS寄存器和用户r/w寄存器的值。

(2)r6赋值

ARM Switch_to调用机制及调用过程_第13张图片



上述代码块的功能为寄存器r6和协处理器c3数据传递过程,在这里的代码是条件编译的,因此需先判断是否定义了CONFIG_CPU_USE_DOMAINS

经过代码的查阅分析,发现了对CONFIG_CPU_USE_DOMAINS功能的介绍:Thisoption enables or disables the use of domain switching via the set_fs()function.也就是说CONFIG_CPU_USE_DOMAINS规定了CPU是否通过set_fs()使用domain开关。

因此在使用domain时,首先执行第一条语句:mrc         p15, 0, r6, c3, c0, 0即通过mrc语句将协处理器c3的内容传递给寄存器r6,也就是说协处理器c3存储的就是domain寄存器的值。

接下来就是:str    r6, [r1,#TI_CPU_DOMAIN]

ldr    r6, [r2, #TI_CPU_DOMAIN]

这里定义了伪指令TI_CPU_DOMAIN,跟上述伪指令一样,这条指令同样执行了thread_info结构体:


即这两句的作用就是讲r6(prev domainreg)的值赋给r1结构体cpu_domain,并且将r2(next thread_info)对应的cpu_domain传递给r6(为接下来更新domain寄存器(c3)的值做准备)。

接下来的小代码块就是执行指令:mcr       p15, 0,r6, c3, c0, 0,即将r6(next domain reg)的值传递给协处理器c3。

(3)r7 r8赋值


这段代码也是条件编译的,经过查阅可知CONFIG_CC_STACKPROTECTOR是:Enable-fstack-protector buffer overflow detection;CONFIG_SMP在arm架构中的定义为:

ARM Switch_to调用机制及调用过程_第14张图片

即如果定义了栈保护并没有定义CONFIG_SMP则进行块中的三部操作。

首先是ldr        r7,[r2, #TI_TASK]:这里也用到了伪指令,同样这个伪指令指向thread_info结构体:


发现task指向结构体task_struct,即主task的结构体,task_struct在/include/linux/sched.h中定义。

即此句的工作是将r2指向的主结构体指针赋给r7。

接下来执行ldr        r8,=__stack_chk_guard:即将__stack_chk_guard的值赋给r8寄存器(__stack_chk_guard =0x000a0dff)。

接下来是ldr   r7, [r7,#TSK_STACK_CANARY]:这里又是伪指令TSK_STACK_CANARY,通过查找发现TSK_STACK_CANARY指向task_struct的stack_canary,即


即这句指令的作用是将r7(r2的*task_struct)的值替换为r2的task_struct结构体的stack_canary。

因此这块指令的结果就是在定义了栈保护时将r7赋值为r2的task_struct结构体的stack_canary。

最后一条指令为str        r7,[r8]:即将r7(r2的task_struct结构体的stack_canary)的值赋给[r8(0x000a0dff)]。

ARM Switch_to调用机制及调用过程_第15张图片

         3、bl atomic_notifier_call_chain


这里做的主要工作是将r0(prevtask_struct)的值赋给r5(r0接下来将为bl atomic_notifier_call_chain传递参数)。

第二条是将r4指向r2(nextthread_info)的cpu_context结构体。

第三条是给r0赋新值(thread_notify_head),经过查阅可知,


r0指向atomic_notifier_head结构体。

第四条是给r1赋值(#THREAD_NOTIFY_SWITCH),进查找发现#THREAD_NOTIFY_SWITCH定义为数值2。

接下来就是bl atomic_notifier_call_chain,跳转到atomic_notifier_call_chain进行操作,这部分的操作将在接下来的章节进行详细讲解。

ARM Switch_to调用机制及调用过程_第16张图片

         4、load regs from next thread_infoARM Switch_to调用机制及调用过程_第17张图片

mov r0, r5是将r5(prevtask_struct)的值重新赋值给r0(作为函数返回值)。

这块代码对应我们分析的第一块代码,也就是将从r4开始指向的结构体分别赋值给r4-sl,fp,sp,pc。也就是将r2(next thread_info)中cpu_context结构体的值赋值给r4-sl,fp,sp,pc。

这里最重要的就是结构体中lr赋值给pc,因为lr存储的是此进程在上次被切换时保留的下一条指令的地址,因此pc被赋值为lr时就能够继续上次被切换时要执行的位置继续执行。

因此,经过上述4块操作,完成的基本任务为:将旧的r4-sl,fp,sp,lr赋给prev thread_info中相应的结构体进行存储,同时将next thread_info的值进行。在此过程中用到了两个协处理器c3、c13,分别用来cpu domain和TLS reg的值。

ARM Switch_to调用机制及调用过程_第18张图片

armV7架构Switch_to代码流程:

在armV7架构中,运行的是抢占式内核,因此在TLB维护操作时需要进行抢占,因此为了能够在转移至另一个CPU工作前将维护工作完成,需要执行dsb(ish)来进行保证。

ARM Switch_to调用机制及调用过程_第19张图片

接下来进行的就是__switch_to的汇编代码。

ARM Switch_to调用机制及调用过程_第20张图片

大体流程跟ARMv3/4流程类似,因此我们在这里只讨论一下这两者间的区别。

很明显这里少了条件编译,因此不再有对协处理器c3、c13的处理(cpu domain和TLS reg)。同时没有了switch_tls的过程,即没有对TLS寄存器的处理。

总的来说因为ARMv7抢占式工作流程,需要先执行__complete_pending_tlbi()dsb(ish)以确保在CPU switch进行前完成维护工作。

arm 架构switch_to流程总结

         总的来说,arm架构下,硬件上下文存储在各自thread_info结构体中包含的cpu_context_save结构体中:

         structcpu_context_save {

         __u32       r4;

         __u32       r5;

         __u32       r6;

         __u32       r7;

         __u32       r8;

         __u32       r9;

         __u32       sl;

         __u32       fp;

         __u32       sp;

         __u32       pc;

         __u32       extra[2];            /*Xscale 'acc' register, etc */

};

__switch_to完成的工作就是将ARM寄存器的信息保留在相应的prev thread_infoàcpu_context_save,并将next的thread_infoàcpu_context_save信息保存在寄存器中从而完成硬件上下文的切换。

你可能感兴趣的:(ARM,Linux)