3.7.6. thread_info与内核栈stack的关系

3.7.6.1. thread_info简介

thread_info结构被称为迷你进程描述符,是因为在这个结构中并没有直接包含与进程相关的字段,而是通过task字段指向某个具体的 进程描述符.通常这块内存区域的大小是8k,也就是两个页的大小.一个进程的内核栈和thred_info结构之间的关系图如下

../../../_images/thread_info.png

从上图可知,内核是从该内存区域的顶层向下(从高地址向低地址)增长的,而thread_info结构则是从该区域向上增长.内核栈的栈顶地址 存储在esp寄存器中.所以当进程从用户态切换到内核态后,esp寄存器指向这个区域的末端

由于一个页大小是4k,一个页的起始地址都是4K的整数倍,即后12位都为0,取得esp内核栈栈顶的地址,将其后12位取0,就可以得到内存区域的 起始地址,该地址即是thread_info的地址.通过thread_info又可以得到task_struct的地址而得到进程pid

3.7.6.2. thread_union实现

thread_info与内核堆栈在内核中的定义位于:include/linux/sched.h文件中

union thread_union {
#ifndef CONFIG_ARCH_TASK_STRUCT_ON_STACK
    struct task_struct task;
#endif
#ifndef CONFIG_THREAD_INFO_IN_TASK
    struct thread_info thread_info;
#endif
    unsigned long stack[THREAD_SIZE/sizeof(long)];
};

3.7.6.3. thread_info接口

内核将thread_info与内核堆栈放在固定的空间里,内核只要知道堆栈的位置,那么内核堆栈对应的thread_info结构的地址就可以获得

static inline struct thread_info *current_thread_info(void)
{
    return (struct thread_info*)(current_stack_pointer & ~(THREAD_SIZE - 1));
}

register unsigned long current_stack_pointer asm("sp");

3.7.6.4. 0号进程的thread_info

在linux中,0号进程就是内核启动之后的第一个进程,其通过init_task变量定义,其定义在init/init_task.c文件中,起中stack成员指向init_stack结构

struct task_struct init_task
#ifdef CONFIG_ARCH_TASK_STRUCT_ON_STACK
    __init_task_data
#endif
= {
#ifdef CONFIG_THREAD_INFO_IN_TASK

    .thread_info = INIT_THREAD_INFO(init_task),
    .stack_refcount = ATOMIC_INIT(1),
#endif
    .state = 0,
    .stack = init_stack,        //0号进程的stack指向了init_stack
    .usage = ATOMIC_INIT(2),
    ...
    ...
};

init_stack 其定义在include/asm-generic/vmlinux.lds.h中

#define INIT_TASK_DATA(align)               \
    . = ALIGN(align);                       \
    __start_init_task = .;                  \
    init_thread_union = .;                  \
    init_stack = .;                         \
    KEEP(*(.data..init_task))               \
    KEEP(*(.data..init_thread_info))        \
    . = __start_init_task + THREAD_SIZE;    \
    __end_init_task = .;

0号进程的stack定义位于链接脚本里,其中关于init_stack的定义通过INIT_TASK_DATA宏进行定义,链接脚本中定义了链接符号 __start_init_task指向了堆栈开始的地址,并且init_thread_union与init_stack的地址都指向了这里.并且将__end_init_task指向 了堆栈的顶部

3.7.6.5. 堆栈的生长方式与thread_info的关系

堆栈的生长方式分为向上增长和向下增长,通常堆栈是向下增长的,此时内核使用thread_union结构将thread_info和内核堆栈绑定到一起, 并且thread_info位于区域的底部,而堆栈的站定位于区域的顶部

../../../_images/stack_info.png

如果堆栈的生长方向是向上的,那么thread_union的结构如下

../../../_images/stack_info_1.png