ThreadInfo结构和内核栈的两种关系

Posted Loopers

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadInfo结构和内核栈的两种关系相关的知识,希望对你有一定的参考价值。

本来本节是要学习内核启动的第一个进程的建立,也就是0号进程,也称idle进程,也称swapper进程。但是在学习第一个进程建立之前需要先学习threadinfo和内核栈的关系。

目前内核存在两种threadinfo和内核的关系,接下来我们通过画图一一举例说明。

 

ThreadInfo结构在内核栈中

Threadinfo结构存储在内核栈中,这种方式是最经典的。因为task_struct结构从1.0到现在5.0内核此结构一直在增大。如果将此结构放在内核栈中则很浪费内核栈的空间,则在threadinfo结构中有一个task_struct的指针就可以避免。

struct thread_info 
    unsigned long        flags;        /* low level flags */
    mm_segment_t        addr_limit;    /* address limit */
    struct task_struct    *task;        /* main task structure */
    int            preempt_count;    /* 0 => preemptable, <0 => bug */
    int            cpu;        /* cpu */
;

可以看到thread_info结构中存在一个struct task_struct的指针。

 

我们接着看下struct task_struct结构体

struct task_struct 
    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    void *stack;
    atomic_t usage;
    unsigned int flags;    /* per process flags, defined below */
    unsigned int ptrace;
 
#ifdef CONFIG_SMP
    struct llist_node wake_entry;
    int on_cpu;
    unsigned int wakee_flips;
    unsigned long wakee_flip_decay_ts;
    struct task_struct *last_wakee;
 
    int wake_cpu;
#endif
    int on_rq;
    ......

可以看到struct task_struct结构体重有一个stack的结构,此stack指针就是内核栈的指针。

 

接下来再看看内核stack和thread_info结构的关系

union thread_union 
    struct thread_info thread_info;
    unsigned long stack[THREAD_SIZE/sizeof(long)];
;
 
#define THREAD_SIZE        16384
#define THREAD_START_SP        (THREAD_SIZE - 16)

内核定义了一个thread_union的联合体,联合体的作用就是thread_info和stack共用一块内存区域。而THREAD_SIZE就是内核栈的大小,ARM64定义THREAD_SIZE的大小为16K

 

现在我们已经理清了这三个结构体的关系,下面通过一张图来说明下这三者的关系。

那如何获取一个进程的task_struct结构呢?  我们获得当前内核栈的sp指针的地址,然后根据THREAD_SIZE对齐就可以获取thread_info结构的基地址,然后从thread_info.task就可以获取当前task_struct结构的地址了。

current的实现

内核中经常通过current宏来获得当前进程对应的struct task_sturct结构,我们来看下具体的实现。

#define get_current() (current_thread_info()->task)
#define current get_current()
 
/*
 * how to get the current stack pointer from C
 */
register unsigned long current_stack_pointer asm ("sp");
 
/*
 * how to get the thread information struct from C
 */
static inline struct thread_info *current_thread_info(void) __attribute_const__;
 
static inline struct thread_info *current_thread_info(void)

    return (struct thread_info *)
        (current_stack_pointer & ~(THREAD_SIZE - 1));

可以看出通过SP的地址通过对齐THREAD_SIZE,然后强转为thread_info结构,然后通过thread_info结构中的task就可以获取task_struct结构的值

ThreadInfo在task_struct结构中

上面的一种方式是thread_info结构和内核栈共用一块存储区域,而另一种方式是thread_info结构存储在task_struct结构中。

struct task_struct 
#ifdef CONFIG_THREAD_INFO_IN_TASK
    /*
     * For reasons of header soup (see current_thread_info()), this
     * must be the first element of task_struct.
     */
    struct thread_info        thread_info;
#endif
    /* -1 unrunnable, 0 runnable, >0 stopped: */
    volatile long            state;
 
    /*
     * This begins the randomizable portion of task_struct. Only
     * scheduling-critical items should be added above here.
     */
    randomized_struct_fields_start
 
    void                *stack;
    atomic_t            usage;
    /* Per task flags (PF_*), defined further below: */
    unsigned int            flags;
    unsigned int            ptrace;

可以看到必须打开CONFIG_THREAD_INFO_IN_TASK这个配置,这时候thread_info就会在task_struct的第一个成员。而task_struct中依然存在void* stack结构

 

接着看下thread_info结构,如下是ARM64架构定义的thread_info结构

struct thread_info 
    unsigned long        flags;        /* low level flags */
    mm_segment_t        addr_limit;    /* address limit */
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
    u64            ttbr0;        /* saved TTBR0_EL1 */
#endif
    union 
        u64        preempt_count;    /* 0 => preemptible, <0 => bug */
        struct 
#ifdef CONFIG_CPU_BIG_ENDIAN
            u32    need_resched;
            u32    count;
#else
            u32    count;
            u32    need_resched;
#endif
         preempt;
    ;
;

从此结构中则没有struct task_struct的指针了。

 

接着再来看下内核栈的定义:

union thread_union 
#ifndef CONFIG_THREAD_INFO_IN_TASK
    struct thread_info thread_info;
#endif
    unsigned long stack[THREAD_SIZE/sizeof(long)];
;

当CONFIG_THREAD_INFO_IN_TASK这个配置打开的时候,则thread_union结构中只存在stask成员了。

 

这时候我们再来看下当thread_info在task_struct结构中时,用一张图描述下。

当thread_info和内核栈是这种关系的时候,内核如何获取当前进程的task_struct结构呢?

 

还是和上面一样,通过分析current这个宏来分析

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;

 
#define current get_current()

可以看到内核通过读取sp_el0的值,然后将此值强转成task_struct结构就可以获得。那sp_el0是什么东西?

sp:堆栈指针寄存器

el0: ARM64架构分为EL0,EL1,EL2,EL3。EL0则就是用户空间,EL1则是内核空间,EL2则是虚拟化,EL3则是secure层。

sp_el0: 则就是用户空间栈指针寄存器。

 

SP_EL0值存储的是什么?

这个内存其实在后面的进程切换中会涉及到,这里先简单说明了。当进程发生切换时,需要将上一个进程的上下文保存到内核堆栈中,然后去恢复下一个进程的堆栈。

/*
 * Thread switching.
 */
__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);
    ptrauth_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;

当两个进程发生切换时,最终会调用到这里。然后最终会通过cpu_switch_to函数发生切换,cpu_switch_to函数是用汇编实现的,其中参数传递x0=prev, x1=next

/*
 * Register switch for AArch64. The callee-saved registers need to be saved
 * and restored. On entry:
 *   x0 = previous task_struct (must be preserved across the switch)
 *   x1 = next task_struct
 * Previous and next are guaranteed not to be the same.
 *
 */
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)

这段汇编的意思是将prev进程的x19到x29, x9, lr寄存器都存储在内核堆栈中,然后将next进程的x19-x29, x9, lr寄存器从堆栈中恢复。

我们关心的msr sp_el0, x1。其中x1就是next进程的struct task_struct结构。则sp_el0存储的是当前进程的task_struct结构。

 

当知道了当前进程的task_struct结构的地址,则thread_info结构的地址也就知道了。

#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
 * For CONFIG_THREAD_INFO_IN_TASK kernels we need <asm/current.h> for the
 * definition of current, but for !CONFIG_THREAD_INFO_IN_TASK kernels,
 * including <asm/current.h> can cause a circular dependency on some platforms.
 */
#include <asm/current.h>
#define current_thread_info() ((struct thread_info *)current)
#endif

通过将current强制转为struct thread_info结构就可以了。

 

至此我们已经分析了thread_info和内核栈的两种关系。而ARM64架构使用的是第二种。

 

以上是关于ThreadInfo结构和内核栈的两种关系的主要内容,如果未能解决你的问题,请参考以下文章

栈的两种存储结构

Java中栈的两种实现

请问SAN和NAS两种存储的区别是啥?

数据结构 - 栈和队列

栈和队列

Innodb 和 MyIsam 两种存储引擎的文件存储结构