前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ThreadInfo结构和内核栈的两种关系

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

作者头像
DragonKingZhu
发布2020-03-24 16:19:46
2.4K0
发布2020-03-24 16:19:46
举报

本来本节是要学习内核启动的第一个进程的建立,也就是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架构使用的是第二种。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ThreadInfo结构在内核栈中
    • current的实现
    • ThreadInfo在task_struct结构中
      • 还是和上面一样,通过分析current这个宏来分析
      • SP_EL0值存储的是什么?
      相关产品与服务
      对象存储
      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档