为什么 thread_info 应该是 task_struct 中的第一个元素?

Why thread_info should be the first element in task_struct?

在阅读Linux内核源代码时,我发现一件事让我很困惑。

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

...
};

完整的代码是here,你能告诉我为什么这个必须是task_struct中的第一个元素吗?

评论说:

    /*
     * For reasons of header soup (see current_thread_info()), this
     * must be the first element of task_struct.
     */
    struct thread_info      thread_info;

然后<linux/thread_info.h>:

/*
 * 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

换句话说,<linux/thread_info.h>不能包含<linux/sched.h>,其中定义了task_struct,这样它就不能访问它的成员。但是,它可以将 task_struct* 转换为指向其第一个成员 (C and C++ aliasing rules explicitly allow that) 的指针,即 struct thread_info*.

找了很多书,网站,调试了Linux内核,明白了thread_info的设计,也明白了为什么一定要放在task_struct的第一个元素。

首先,我们需要了解Linux kernel stack

然后,我们来到核心:我们需要在task_struct之前到达内核堆栈,并且我们需要在一段时间之前通过内核堆栈到达task_struct。所以我们必须有一些方法来做到这一点。

1。 task_struct 到内核堆栈

使用起来很简单stack如下

static inline void *task_stack_page(const struct task_struct *task)
{
  return task->stack;
}

此外,我们可以通过 task_pt_regs(task)

到达 pt_regs

2.kernel 堆叠到 task_struct

这里是thread_info的用法。 让我在这里放两个版本

struct thread_info {
  struct task_struct  *task;    /* main task structure */
  __u32      flags;    /* low level flags */
  __u32      status;    /* thread synchronous flags */
  __u32      cpu;    /* current CPU */
  mm_segment_t    addr_limit;
  unsigned int    sig_on_uaccess_error:1;
  unsigned int    uaccess_err:1;  /* uaccess failed */
};


struct thread_info {
  unsigned long flags;          /* low level flags */
  unsigned long status;    /* thread synchronous flags */    
};


第一个是旧版本,它包括task_struct,从中我们可以直接进入task_struct。但是存储成本太高,所以新版本全部删除了。

在旧版本中,我们有这个函数来获取它。

static inline struct thread_info *current_thread_info(void)
{
  return (struct thread_info *)(current_top_of_stack() - THREAD_SIZE);
}

可是新版本怎么才能达到task_struct呢?

因为我们使用了新的current_thread_info

#include <asm/current.h>
#define current_thread_info() ((struct thread_info *)current)
#endif

电流定义如下

struct task_struct;

DECLARE_PER_CPU(struct task_struct *, current_task);

static __always_inline struct task_struct *get_current(void)
{
  return this_cpu_read_stable(current_task);
}

#define current get_current

也就是说,我们有 task_struct 来自 cpu.

这是我们展示的源代码的最后一部分

DEFINE_PER_CPU(struct task_struct *, current_task) = &init_task;

......

__visible __notrace_funcgraph struct task_struct *
__switch_to(struct task_struct *prev_p, struct task_struct *next_p)
{
......
this_cpu_write(current_task, next_p);
......
return prev_p;
}


...

#define this_cpu_read_stable(var)       percpu_stable_op("mov", var)

通过这种方式,我们可以很容易地得到task_struct