展示代码
/*
* The following fragment of code is executed with the MMU enabled.
*
* x0 = __PHYS_OFFSET
*/
SYM_FUNC_START_LOCAL(__primary_switched)
adrp x4, init_thread_union
add sp, x4, #THREAD_SIZE
adr_l x5, init_task
msr sp_el0, x5 // Save thread_info
#ifdef CONFIG_ARM64_PTR_AUTH
__ptrauth_keys_init_cpu x5, x6, x7, x8
#endif
分析代码
第1行到第5行是注释,提示这段代码是在使能 MMU 之后执行。入参是 x0,即 __PHYS_OFFSET
第7行将 init_thread_union 的页面物理地址存放在 x4
根据上一章节,打开MMU后,跳入到了函数 __primary_switched 的链接地址去执行。
所以 x4 存放的也是 init_thread_union 链接地址。
我们再来说说 init_thread_union
在Linux内核中,init_thread_union代表了内核启动时创建的第一个线程,也就是初始线程(init线程)的栈空间。这个线程通常被称为0号进程,是内核创建的第一个进程,也是所有其他进程的祖先。
init_thread_union定义通在内核的链接脚本(如vmlinux.lds)中,它分配了一块内存空间作为init线程的栈。
在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 = .;
这个栈空间足够大,可以保证init线程在执行过程中有足够的栈空间使用。在内核启动过程中,会将init_thread_union的地址设置为初始线程的栈指针(sp),这样当内核开始执行时,它就有了一个有效的栈空间。
以下是init_thread_union的一些关键点:
定义和作用:init_thread_union定义了init进程的栈空间。它是内核启动时创建的第一个线程的栈底地址,定义在.data段中。在链接脚本中,init_thread_union会被赋予一个地址,并没有真正分配内存,而是在声明时采用extern的方式,在vmlinux.lds中定义。
内存布局:在内核的内存布局中,init_thread_union紧跟在__start_init_task之后,并且它的结束地址是__end_init_task。这个栈空间的大小由THREAD_SIZE定义,通常是一个相对较大的空间,以确保init线程有足够的空间进行系统初始化工作。
初始化过程:在内核启动的早期阶段,特别是在__primary_switched函数中,会设置init_thread_union的地址作为栈指针(sp),并初始化init_task结构体,这是init线程的task_struct。这个过程涉及到将init_thread_union的地址加载到寄存器中,然后将其设置为栈指针,并将init_task的地址保存到sp_el0寄存器中,以便在异常处理时能够访问当前线程的信息。
特殊性质:由于init_thread_union是内核启动的第一个线程,它具有一些特殊的性质。例如,它的task_struct(即init_task)是静态定义的,而不是通过动态分配内存创建的。此外,init线程是唯一一个没有通过fork或kernel_thread系统调用创建的线程。
总的来说,init_thread_union为内核的初始化提供了必要的栈空间,并确保了内核能够顺利地执行初始化代码。
第8行涉及到宏定义 THREAD_SIZE
其定义如下
#define THREAD_SIZE (UL(1) << THREAD_SHIFT)
而 THREAD_SHIFT 的定义如下
/*
* VMAP'd stacks are allocated at page granularity, so we must ensure that such
* stacks are a multiple of page size.
*/
#if defined(CONFIG_VMAP_STACK) && (MIN_THREAD_SHIFT < PAGE_SHIFT)
#define THREAD_SHIFT PAGE_SHIFT
#else
#define THREAD_SHIFT MIN_THREAD_SHIFT
#endif
$ cat .config | grep CONFIG_VMAP_STACK
CONFIG_VMAP_STACK=y
#ifdef CONFIG_KASAN
#define KASAN_SHADOW_OFFSET _AC(CONFIG_KASAN_SHADOW_OFFSET, UL)
#define KASAN_SHADOW_END ((UL(1) << (64 - KASAN_SHADOW_SCALE_SHIFT)) \
+ KASAN_SHADOW_OFFSET)
#define KASAN_THREAD_SHIFT 1
#else
#define KASAN_THREAD_SHIFT 0
#define KASAN_SHADOW_END (_PAGE_END(VA_BITS_MIN))
#endif /* CONFIG_KASAN */
#define MIN_THREAD_SHIFT (14 + KASAN_THREAD_SHIFT)
$ cat .config | grep CONFIG_KASAN
# CONFIG_KASAN is not set
PAGE_SHIFT 的宏定义如下,参考《慢慢欣赏arm64内核启动13 primary_entry之__create_page_tables代码第三部分》
#define PAGE_SHIFT CONFIG_ARM64_PAGE_SHIFT
$ cat .config | grep CONFIG_ARM64_PAGE_SHIFT
CONFIG_ARM64_PAGE_SHIFT=12
所以THREAD_SHIFT值为 1^14
第9行将 init_task 的地址存放在 x5,init_task 一个静态的全局变量,定义在 init/init_task.c 如下
= MAX_PRIO - 20,
.static_prio = MAX_PRIO - 20,
.normal_prio = MAX_PRIO - 20,
.policy = SCHED_NORMAL,
.cpus_ptr = &init_task.cpus_mask,
.cpus_mask = CPU_MASK_ALL,
.nr_cpus_allowed= NR_CPUS,
.mm = NULL,
.active_mm = &init_mm,
.restart_block = {
.fn = do_no_restart_syscall,
},
.se = {
.group_node = LIST_HEAD_INIT(init_task.se.group_node),
},
.rt = {
.run_list = LIST_HEAD_INIT(init_task.rt.run_list),
.time_slice = RR_TIMESLICE,
},
.tasks = LIST_HEAD_INIT(init_task.tasks),
#ifdef CONFIG_SMP
.pushable_tasks = PLIST_NODE_INIT(init_task.pushable_tasks, MAX_PRIO),
#endif
#ifdef CONFIG_CGROUP_SCHED
.sched_task_group = &root_task_group,
#endif
.ptraced = LIST_HEAD_INIT(init_task.ptraced),
.ptrace_entry = LIST_HEAD_INIT(init_task.ptrace_entry),
.real_parent = &init_task,
.parent = &init_task,
.children = LIST_HEAD_INIT(init_task.children),
.sibling = LIST_HEAD_INIT(init_task.sibling),
.group_leader = &init_task,
RCU_POINTER_INITIALIZER(real_cred, &init_cred),
RCU_POINTER_INITIALIZER(cred, &init_cred),
.comm = INIT_TASK_COMM,
.thread = INIT_THREAD,
.fs = &init_fs,
.files = &init_files,
#ifdef CONFIG_IO_URING
.io_uring = NULL,
#endif
.signal = &init_signals,
.sighand = &init_sighand,
.nsproxy = &init_nsproxy,
.pending = {
.list = LIST_HEAD_INIT(init_task.pending.list),
.signal = {{0}}
},
.blocked = {{0}},
.alloc_lock = __SPIN_LOCK_UNLOCKED(init_task.alloc_lock),
.journal_info = NULL,
INIT_CPU_TIMERS(init_task)
.pi_lock = __RAW_SPIN_LOCK_UNLOCKED(init_task.pi_lock),
.timer_slack_ns = 50000, /* 50 usec default slack */
.thread_pid = &init_struct_pid,
.thread_group = LIST_HEAD_INIT(init_task.thread_group),
.thread_node = LIST_HEAD_INIT(init_signals.thread_head),
#ifdef CONFIG_AUDIT
.loginuid = INVALID_UID,
.sessionid = AUDIT_SID_UNSET,
#endif
#ifdef CONFIG_PERF_EVENTS
.perf_event_mutex = __MUTEX_INITIALIZER(init_task.perf_event_mutex),
.perf_event_list = LIST_HEAD_INIT(init_task.perf_event_list),
#endif
#ifdef CONFIG_PREEMPT_RCU
.rcu_read_lock_nesting = 0,
.rcu_read_unlock_special.s = 0,
.rcu_node_entry = LIST_HEAD_INIT(init_task.rcu_node_entry),
.rcu_blocked_node = NULL,
#endif
#ifdef CONFIG_TASKS_RCU
.rcu_tasks_holdout = false,
.rcu_tasks_holdout_list = LIST_HEAD_INIT(init_task.rcu_tasks_holdout_list),
.rcu_tasks_idle_cpu = -1,
#endif
#ifdef CONFIG_TASKS_TRACE_RCU
.trc_reader_nesting = 0,
.trc_reader_special.s = 0,
.trc_holdout_list = LIST_HEAD_INIT(init_task.trc_holdout_list),
#endif
#ifdef CONFIG_CPUSETS
.mems_allowed_seq = SEQCNT_SPINLOCK_ZERO(init_task.mems_allowed_seq,
&init_task.alloc_lock),
#endif
#ifdef CONFIG_RT_MUTEXES
.pi_waiters = RB_ROOT_CACHED,
.pi_top_task = NULL,
#endif
INIT_PREV_CPUTIME(init_task)
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
.vtime.seqcount = SEQCNT_ZERO(init_task.vtime_seqcount),
.vtime.starttime = 0,
.vtime.state = VTIME_SYS,
#endif
#ifdef CONFIG_NUMA_BALANCING
.numa_preferred_nid = NUMA_NO_NODE,
.numa_group = NULL,
.numa_faults = NULL,
#endif
#ifdef CONFIG_KASAN
.kasan_depth = 1,
#endif
#ifdef CONFIG_KCSAN
.kcsan_ctx = {
.disable_count = 0,
.atomic_next = 0,
.atomic_nest_count = 0,
.in_flat_atomic = false,
.access_mask = 0,
.scoped_accesses = {LIST_POISON1, NULL},
},
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
.softirqs_enabled = 1,
#endif
#ifdef CONFIG_LOCKDEP
.lockdep_depth = 0, /* no locks held yet */
.curr_chain_key = INITIAL_CHAIN_KEY,
.lockdep_recursion = 0,
#endif
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
.ret_stack = NULL,
.tracing_graph_pause = ATOMIC_INIT(0),
#endif
#if defined(CONFIG_TRACING) && defined(CONFIG_PREEMPTION)
.trace_recursion = 0,
#endif
#ifdef CONFIG_LIVEPATCH
.patch_state = KLP_UNDEFINED,
#endif
#ifdef CONFIG_SECURITY
.security = NULL,
#endif
#ifdef CONFIG_SECCOMP_FILTER
.seccomp = { .filter_count = ATOMIC_INIT(0) },
#endif
};
EXPORT_SYMBOL(init_task);
在Linux内核中,init_task 是一个非常特殊的 task_struct 实例,它代表了内核启动时创建的第一个进程,也就是0号进程。
这个进程是系统所有其他用户空间进程的祖先,并且它在内核空间中运行,没有对应的用户空间进程。
init_task 是在内核初始化阶段由 start_kernel 函数创建的,它负责执行系统启动的剩余初始化工作,包括内存管理、设备驱动初始化、文件系统挂载等。
init_task 的定义位于 init/init_task.c 文件中,它被静态初始化为一个 task_struct 的实例,并被赋予了一些特殊的属性,比如它的进程ID(PID)被设置为0,表示它是系统的第一个进程。
init_task 也是内核中所有其他进程的原型,它的 task_struct 包含了所有进程共有的字段,如进程状态、优先级、程序计数器、内存指针等 。
init_task 的栈空间由 init_thread_union 提供,这是一个位于内核数据段的特殊区域,它包含了 init_task 的栈和 thread_info 结构。
init_thread_union 被对齐到特定的边界,并且被编译进内核映像的 .data.init_task 段中 。
在系统启动的后期,init_task 会创建两个重要的内核线程:kernel_init 和 kthreadd。kernel_init 是PID为1的进程,它负责执行用户空间的初始化脚本,最终转变为用户空间的 init 进程。
而 kthreadd 是所有内核线程的管理者,它负责创建和调度其他内核线程 。
init_task 最终会退化为系统的idle进程,当CPU没有其他进程可以调度时,就会执行idle进程。idle进程的优先级最低,它在系统中起到资源空闲时的占位符作用 。
总结来说,init_task 是Linux内核中的一个特殊进程,它是系统初始化过程中的关键组成部分,负责创建和管理系统中的其他所有进程。它是内核中第一个被创建的进程,也是所有用户空间进程的祖先。
第10行将 x5 存放在 sp_el0
好处如下,在ARM64架构中,通过SP_EL0寄存器快速获取当前任务的task_struct:
// ARM64的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()
第12行到第14行涉及条件编译宏 CONFIG_ARM64_PTR_AUTH 暂时不考虑
1229

被折叠的 条评论
为什么被折叠?



