current() macro

current() macro

current在内核中通常以宏的形式存在,其实现方式依赖于具体的架构和内核版本,有些实现是从堆栈中获取当前运行任务的task_struct结构指针,有些则从寄存器中获取。

  • current的实现

拿 arm64架构 + linux-5.4版本 为背景举例, current实际上是从sp_el0这个寄存器中读取当前任务的task_struct结构指针,这个宏的具体实现如下:

/* current实际上是get_current()函数 */
#define current get_current()

/* get_current()函数实际上是通过mrs指令从sp_el0寄存器取出当前任务的task_struct指针 */
/*
 * We don't use read_sysreg() as we want the compiler to cache the value where
 * possible.
 */
static __always_inline struct task_struct *get_current(void)
{
        unsigned long sp_el0;

        asm ("mrs %0, sp_el0" : "=r" (sp_el0));

        return (struct task_struct *)sp_el0;
}

 

  •  sp_el0值的来历

这个sp_el0存放当前任务的task_struct,它是怎么来的呢?

内核在发生任务切换动作前会将切换的next任务写入percpu变量__entry_task,这样正在cpu上运行的任务的task_struct保存在percpu变量__entry_task中;
同时,由于内核中会频繁使用当前任务"current",而通过读取percpu变量来获取当前任务还是有一定开销;为此,正在运行的任务(也就是current)从用户态陷入到内核态时,会将这个percpu变量存放到sp_el0寄存器(该寄存器是用户态堆栈指针,在内核态暂时没有使用)中,这样每次获取当前任务时,可直接使用sp_el0来获取。

    • 每次任务切换时更新__entry_task为当前任务
__switch_to()-->entry_task_switch(next)
static void entry_task_switch(struct task_struct *next)
{
    __this_cpu_write(__entry_task, next);
}

 

  • 任务从用户态进入到内核态时将__entry_task取出到sp_el0中
.macro  kernel_entry, el, regsize = 64
    ......
    //取percpu变量__entry_task到tsk
    ldr_this_cpu    tsk, __entry_task, x20
    ......
    .if     \el == 0
    msr     sp_el0, tsk        //如果异常发生在用户态,则将tsk取到sp_el0
    .endif

 

对于sp_el0是如何/何时存放当前任务的,实际上就是在当前任务从用户态进入内核态时动态从percpu变量__entry_task获取的;而这个percpu变量__entry_task则是在前一个任务调用__switch_to()切换到当前任务切换运行时设置的。 

 

from:

https://www.cnblogs.com/liuhailong0112/p/14921228.html

 

运行kernel thread时将当前正在运行的kernel thread的task_struct指针保存到sp_el0

如果是在kernel space执行的kernel thread,同样会用到sp_el0,同样用来来保存正在运行的task_struct,这个是在__switch_to()里去设置的.

为什么在执行kernel thread时可以使用user space使用的sp_el0呢?因为此时这个cpu core正在kernel space运行,所以它就没有在执行user space的程序,所以此时sp_el0 user space是没有在使用的,所以可以拿来保存当前正在运行的kernel thread的task_struct.

4.19/arch/arm64/kernel/process.c

如下的cpu_switch_to() x1即是next task_struct,即即将被调度执行的task_struct:

ENTRY(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]
    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
    ret
ENDPROC(cpu_switch_to)

 

__notrace_funcgraph struct task_struct *__switch_to(struct task_struct *prev,
                struct task_struct *next)
{
    struct task_struct *last;

    fpsimd_thread_switch(next);
    tls_thread_switch(next);
    hw_breakpoint_thread_switch(next);
    contextidr_thread_switch(next);
    entry_task_switch(next);
    uao_thread_switch(next);
    ssbs_thread_switch(next);

    /*
     * Complete any pending TLB or cache maintenance on this CPU in case
     * the thread migrates to a different CPU.
     * This full barrier is also required by the membarrier system
     * call.
     */
    dsb(ish);

    /* the actual thread switch */
    last = cpu_switch_to(prev, next);

    return last;
}

 

posted @ 2021-09-30 17:55  aspirs  阅读(229)  评论(0编辑  收藏  举报