慢慢欣赏arm64内核启动28 __primary_switched之第一部分

展示代码

/*
 * 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 暂时不考虑

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值