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

转自:https://blog.csdn.net/longwang155069/article/details/104346778

本来本节是要学习内核启动的第一个进程的建立,也就是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架构使用的是第二种。
————————————————
版权声明:本文为CSDN博主「Loopers」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/longwang155069/article/details/104346778

上一篇:深入理解 Linux 内核中的栈【转】


下一篇:Sping基础