文章目录
我们前面看了start_kernel前的汇编进行的初始化工作,现在开始看start_kernel函数了:
1. set_task_stack_end_magic
void set_task_stack_end_magic(struct task_struct *tsk)
{
unsigned long *stackend;
stackend = end_of_stack(tsk);//获取内核栈末尾地址
*stackend = STACK_END_MAGIC;//设置为魔数,
//在每次栈空间被使用时,内核都会检查该位置的值是否保持不变
//如果发现变化,就会发出栈溢出的警报。
#define task_stack_end_corrupted(task) \
(*(end_of_stack(task)) != STACK_END_MAGIC)
}
set_task_stack_end_magic函数就做了一件事,获取内核栈的末尾地址,然后给这个地址赋值STACK_END_MAGIC。然后linux内核是使用task_stack_end_corrupted这个宏判断内核是否栈溢出的。linux内核会在cpu睡眠的时候检查当前栈是否发生溢出。如果开启了schedule debug则会在schedule的时候检查上一个进程的栈是否发生溢出。如果我们真的担心这个问题,最好的做法是在内核的启动参数,一般是grub中添加stacktrace,那么开启内核的函数调用堆栈追踪功能,同事会检查堆栈是否溢出。
2.smp_setup_processor_id
void __init smp_setup_processor_id(void)
{
//读取MPIDR_EL1寄存器,并且只保留4个等级表示该core属于哪个cluse等信息
u64 mpidr = read_cpuid_mpidr() & MPIDR_HWID_BITMASK;
set_cpu_logical_map(0, mpidr);//把core信息写入__cpu_logical_map
/*
* clear __my_cpu_offset on boot CPU to avoid hang caused by
* using percpu variable early, for example, lockdep will
* access percpu variable inside lock_release
*/
//tpidr_el1存放当前cpu的线程ID
set_my_cpu_offset(0);//初始化0到对应cpu的tpidr_el1和tpidr_el2寄存器
pr_info("Booting Linux on physical CPU 0x%010lx [0x%08x]\n",
(unsigned long)mpidr, read_cpuid_id());
}
smp_setup_processor_id函数主要做了两个事情:
- 读取MPIDR_EL1,把该寄存器的4个等级的aff提取出来,写入该cpu号为下标的__cpu_logical_map数组中。这4组aff值在每个core中是唯一的,加起来可以表示core在整个soc的位置。aff0表示一个core中的第几个线程,因为有可能使用超线程技术,一个core中有两个线程;aff1表示core在cluster中的第几个core;aff2表示这是在第几个cluster中。这个值是soc厂商写死的,用于表示core的信息。
- 初始化tpidr_el1和tpidr_el2寄存器为0。
以前我一直以为它表示当前cpu处于0号进程,当时认为这个寄存器是用来存放当前cpu正在处理的进程的进程号的,加速cpu找到current这个task_struct结构体。现在看了代码才知道,这个寄存器是存放每cpu的offs值的,用于快速找到每cpu变量所在的内存。
3. debug_objects_early_init
我们没有开启CONFIG_DEBUG_OBJECTS,是个空函数。
4. cgroup_init_early
int __init cgroup_init_early(void)
{
static struct cgroup_fs_context __initdata ctx;
struct cgroup_subsys *ss;
int i;
ctx.root = &cgrp_dfl_root;
init_cgroup_root(&ctx);//注册和初始化根控制组
cgrp_dfl_root.cgrp.self.flags |= CSS_NO_REF;
//初始化init_task.cgroups,这个RCU指针结构体,看看他的定义就懂了
//struct css_set __rcu *cgroups;
RCU_INIT_POINTER(init_task.cgroups, &init_css_set);
//组装好基本的控制组框架
for_each_subsys(ss, i) {//遍历所有启用的cgroup子系统,他们存放在cgroup_subsys数组中
WARN(!ss->css_alloc || !ss->css_free || ss->name || ss->id,
"invalid cgroup_subsys %d:%s css_alloc=%p css_free=%p id:name=%d:%s\n",
i, cgroup_subsys_name[i], ss->css_alloc, ss->css_free,
ss->id, ss->name);
WARN(strlen(cgroup_subsys_name[i]) > MAX_CGROUP_TYPE_NAMELEN,
"cgroup_subsys_name %s too long\n", cgroup_subsys_name[i]);
ss->id = i;//设置ID
ss->name = cgroup_subsys_name[i];//设置cgroup名字
if (!ss->legacy_name)
ss->legacy_name = cgroup_subsys_name[i];//设置legacy_name
if (ss->early_init)
cgroup_init_subsys(ss, true);//cgroup的某一个子系统
}
return 0;
}
我们cgroup开启了cpuset、cpu、cpuacct、io、memory、devices、perf_event、hugetlb、pids这几个子系统。
cgroup_init_early函数主要做了几件:
- 调用函数init_cgroup_root注册和初始化根控制组;
- 初始化init_task.cgroups,这个RCU类型的指针;
- 遍历所有启用的cgroup子系统,初始化他们,主要是使用cgroup_init_subsys函数初始化子系统。
4.1 init_cgroup_root
void init_cgroup_root(struct cgroup_fs_context *ctx)
{
struct cgroup_root *root = ctx->root;
struct cgroup *cgrp = &root->cgrp;
INIT_LIST_HEAD(&root->root_list);//初始化cgroup_root的链表
atomic_set(&root->nr_cgrps, 1);//设置cgroup的数量为1
cgrp->root = root;
init_cgroup_housekeeping(cgrp);//初始化根cgroup的后勤工作
//初始化root的flags、release_agent_path、name、cgrp。
root->flags = ctx->flags;
if (ctx->release_agent)
strscpy(root->release_agent_path, ctx->release_agent, PATH_MAX);
if (ctx->name)
strscpy(root->name, ctx->name, MAX_CGROUP_ROOT_NAMELEN);
if (ctx->cpuset_clone_children)
set_bit(CGRP_CPUSET_CLONE_CHILDREN, &root->cgrp.flags);
}
init_cgroup_root函数主要做了一下工作:
- 初始化cgroup_root的链表,设置cgroup的数量为1;
- 调用函数init_cgroup_housekeeping初始化根cgroup的后勤工作;
- 初始化root的flags、release_agent_path、name、cgrp。
4.1.1 init_cgroup_housekeeping
static void init_cgroup_housekeeping(struct cgroup *cgrp)
{
struct cgroup_subsys *ss;
int ssid;
INIT_LIST_HEAD(&cgrp->self.sibling);//初始化存放兄弟cgroup的链表
INIT_LIST_HEAD(&cgrp->self.children);//初始化存放子cgroup的链表
INIT_LIST_HEAD(&cgrp->cset_links);//初始化指向cgrp_cset_links的列表
INIT_LIST_HEAD(&cgrp->pidlists);//初始化存放进程pid的链表
mutex_init(&cgrp->pidlist_mutex);//初始化保护pidlists的互斥锁
//初始化cgrp的其他成员参数
cgrp->self.cgroup = cgrp;
cgrp->self.flags |= CSS_ONLINE;
cgrp->dom_cgrp = cgrp;
cgrp->max_descendants = INT_MAX;
cgrp->max_depth = INT_MAX;
INIT_LIST_HEAD(&cgrp->rstat_css_list);
prev_cputime_init(&cgrp->prev_cputime);//空函数
for_each_subsys(ss, ssid)
INIT_LIST_HEAD(&cgrp->e_csets[ssid]);
init_waitqueue_head(&cgrp->offline_waitq);
//初始化工作队列,用于release的时候调用cgroup1_release_agent函数启动一个用户态程序
INIT_WORK(&cgrp->release_agent_work, cgroup1_release_agent);
}
init_cgroup_housekeeping主要是做了以下工作:
- 初始化存放兄弟cgroup的链表self.sibling
- 初始化存放子cgroup的链表self.children
- 初始化指向cgrp_cset_links的列表cgrp->cset_links
- 初始化存放进程pid的链表cgrp->pidlists
- 初始化保护pidlists的互斥锁cgrp->pidlist_mutex
- 初始化cgrp的其他成员参数
4.2 cgroup_init_subsys
因为启动阶段,ss->early_init为0,cgroup_init_subsys没有执行,先不看。
5. local_irq_disable
#define local_irq_disable() \
do { \
bool was_disabled = raw_irqs_disabled();\
raw_local_irq_disable(); \
if (!was_disabled) \
trace_hardirqs_off(); \
} while (0)
local_irq_disable这个宏主要做了3个工作:
- 调用函数raw_irqs_disabled读取irq的状态;
- 调用函数raw_local_irq_disable关中断;
- 判断步骤一获取到的状态是否关闭,如果还没有关闭,就执行trace_hardirqs_off函数进行trace。
5.1 raw_irqs_disabled
#define raw_irqs_disabled() (arch_irqs_disabled())
static inline int arch_irqs_disabled(void)
{
return arch_irqs_disabled_flags(arch_local_save_flags());
}
static inline unsigned long arch_local_save_flags(void)
{
unsigned long flags;
asm volatile(ALTERNATIVE(
"mrs %0, daif",
__mrs_s("%0", SYS_ICC_PMR_EL1),
ARM64_HAS_IRQ_PRIO_MASKING)
: "=&r" (flags)
:
: "memory");
return flags;
}
static inline int arch_irqs_disabled_flags(unsigned long flags)
{
int res;
asm volatile(ALTERNATIVE(
"and %w0, %w1, #" __stringify(PSR_I_BIT),
"eor %w0, %w1, #" __stringify(GIC_PRIO_IRQON),
ARM64_HAS_IRQ_PRIO_MASKING)
: "=&r" (res)
: "r" ((int) flags)
: "memory");
return res;
}
raw_irqs_disabled主要做了:
- 调用函数arch_local_save_flags获取daif,其中表示了cpu的irq情况,
- 调用函数arch_irqs_disabled_flags对步骤一的返回值进行处理,只保留irq的bit。
5.2 raw_local_irq_disable
#define raw_local_irq_disable() arch_local_irq_disable()
static inline void arch_local_irq_disable(void)
{
if (system_has_prio_mask_debugging()) {
u32 pmr = read_sysreg_s(SYS_ICC_PMR_EL1);
WARN_ON_ONCE(pmr != GIC_PRIO_IRQON && pmr != GIC_PRIO_IRQOFF);
}
asm volatile(ALTERNATIVE(
"msr daifset, #2 // arch_local_irq_disable",
__msr_s(SYS_ICC_PMR_EL1, "%0"),
ARM64_HAS_IRQ_PRIO_MASKING)
:
: "r" ((unsigned long) GIC_PRIO_IRQOFF)
: "memory");
}
raw_local_irq_disable主要是执行了汇编 “msr daifset, #2” ,从而达到关闭irq。
5.3 trace_hardirqs_off
void trace_hardirqs_off(void)
{
lockdep_hardirqs_off(CALLER_ADDR0);
if (!this_cpu_read(tracing_irq_cpu)) {
this_cpu_write(tracing_irq_cpu, 1);
tracer_hardirqs_off(CALLER_ADDR0, CALLER_ADDR1);
if (!in_nmi())
trace_irq_disable_rcuidle(CALLER_ADDR0, CALLER_ADDR1);
}
}
这个函数追进去看不太懂,只知道这是ftrace的东西。
6. boot_cpu_init
void __init boot_cpu_init(void)
{
int cpu = smp_processor_id();
/* Mark the boot cpu "present", "online" etc for SMP and UP case */
set_cpu_online(cpu, true);//记录当前cpu为online
set_cpu_active(cpu, true);//记录当前cpu为active
set_cpu_present(cpu, true);//记录当前cpu为present
set_cpu_possible(cpu, true);//记录当前cpu为possible
#ifdef CONFIG_SMP
__boot_cpu_id = cpu;//记录引导的cpu ID
#endif
}
boot_cpu_init主要是设置当前cpu的状态,这些状态保存在全局变量__cpu_online_mask、__cpu_active_mask、__cpu_present_mask、__cpu_possible_mask中。
7. page_address_init
void __init page_address_init(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(page_address_htable); i++) {
INIT_LIST_HEAD(&page_address_htable[i].lh);
spin_lock_init(&page_address_htable[i].lock);
}
}
static struct page_address_slot {
struct list_head lh; /* List of page_address_maps */
spinlock_t lock; /* Protect this bucket's list */
} ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];
struct page_address_map {
struct page *page;
void *virtual;
struct list_head list;
};
static struct page_address_map page_address_maps[LAST_PKMAP];
page_address_init函数只有一个作用就是初始化page_address_htable数组,这个数组是一个哈希桶,这个列表存放的是struct page_address_map结构体,看到这个结构体我么就知道这是一个page到虚拟地址的映射关系,也就是说我们经常挺熟的反向映射。
8. early_security_init
不太能看懂,后续补,先学习一下。