[TOC]
https://github.com/wdfk-prog/linux-study
init/init_task.c
内核线程的一个核心特征是:它们没有自己独立的用户地址空间。它们只在内核空间运行,而内核地址空间是所有进程共享的。因此,内核线程不需要一个属于自己的内存描述符 struct mm_struct,所以它们的 task->mm 指针通常是 NULL
init_task 就是大名鼎鼎的 PID 0 进程,也常被称为 swapper 进程。从它的标志位 .flags = PF_KTHREAD 可以看出,它是一个内核线程。
init_task 的调度策略(policy)是 SCHED_NORMAL,这意味着它是一个普通的分时调度任务,而不是实时任务。但是初始化阶段调用了init_idle(),使得它的sched_class 是 SCHED_IDLE,这样它就可以作为 CPU 的空闲任务运行。但是fork出来的其他进程继承的还是 SCHED_NORMAL 策略。
进程的“始祖”:init_task 是系统中所有进程的祖先。在系统启动后,它会创建第一个内核线程 kernel_init(它最终会成为 PID 1 的 init 进程)和 kthreadd 内核线程(PID 2,负责管理其他内核线程)。之后系统中所有其他的进程,无论是内核线程还是用户进程,都是直接或间接地由这两个进程派生而来。因此,init_task 是整个进程树的树根。
启动CPU的空闲任务:在系统启动完成后,init_task 就转变为0号CPU的空闲任务(idle task)。当0号CPU上没有任何其他可运行的任务时,调度器就会选择 init_task 来运行,它的工作就是调用 do_idle() 让CPU进入节能的休眠状态。
系统状态的“模板”:这个结构体在编译时被完全初始化,它定义了一个进程诞生时的“原始”或“默认”状态。从它的初始化列表中,我们可以看到一个进程包含的所有方面:调度信息、内存管理、进程关系、信号处理、文件系统上下文、安全凭证等等。后续通过 fork 或 clone 创建新进程时,都会直接或间接地以这些初始值为蓝本进行复制和修改。
init_task
/*
* 设置第一个任务表,改动后果自负!基地址=0,界限=0x1fffff (=2MB)
*/
/*
* init_task 是内核的第一个任务(进程),即PID 0。它在内核编译时被静态地分配和初始化。
* __aligned(L1_CACHE_BYTES) 确保该结构体按L1缓存行对齐,以获得最佳的访存性能。
*/
struct task_struct init_task __aligned(L1_CACHE_BYTES) = {
#ifdef CONFIG_THREAD_INFO_IN_TASK
/* 如果配置了将thread_info嵌入task_struct中,则进行初始化。*/
.thread_info = INIT_THREAD_INFO(init_task),
/* 内核栈的引用计数,初始化为1。*/
.stack_refcount = REFCOUNT_INIT(1),
#endif
/* 进程状态,0代表TASK_RUNNING (可运行)。*/
.__state = 0,
/* 指向它的内核栈,init_stack是在启动时静态分配的一块内存。*/
.stack = init_stack, // //栈底位置.由lds链接脚本中定义设置
/* 进程描述符的使用计数,初始化为2。一个给自己,一个给swapper任务引用。*/
.usage = REFCOUNT_INIT(2),
/* 进程标志位,PF_KTHREAD表明这是一个内核线程。*/
.flags = PF_KTHREAD,
/* 优先级,MAX_PRIO是最低优先级,-20使其具有较高的普通优先级。*/
.prio = MAX_PRIO - 20,
.static_prio = MAX_PRIO - 20,
.normal_prio = MAX_PRIO - 20,
/* 调度策略,默认为SCHED_NORMAL(普通分时调度)。*/
.policy = SCHED_NORMAL,
/* 指向CPU亲和性掩码的指针。*/
.cpus_ptr = &init_task.cpus_mask,
.user_cpus_ptr = NULL, // 内核线程不能由用户空间设置CPU亲和性。
/* 允许运行的CPU掩码,CPU_MASK_ALL表示可以在所有CPU上运行。*/
.cpus_mask = CPU_MASK_ALL,
.max_allowed_capacity = SCHED_CAPACITY_SCALE,
.nr_cpus_allowed= NR_CPUS,
/* 指向内存描述符的指针。作为内核线程,它没有自己的地址空间,故为NULL。*/
.mm = NULL,
/* 指向活动的内存描述符。关键点:它指向为PID 1准备的init_mm,为系统提供初始内存上下文。*/
.active_mm = &init_mm,
.faults_disabled_mapping = NULL,
/* 系统调用重启块,设置为不重启系统调用。*/
.restart_block = {
.fn = do_no_restart_syscall,
},
/* CFS调度器实体,初始化其组节点链表。*/
.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
/* 可推送到其他CPU的任务列表,用于负载均衡。*/
.pushable_tasks = PLIST_NODE_INIT(init_task.pushable_tasks, MAX_PRIO),
#endif
#ifdef CONFIG_CGROUP_SCHED
/* 调度相关的控制组,指向根任务组。*/
.sched_task_group = &root_task_group,
#endif
// ... [为简洁起见,省略了大量其他子系统的初始化,它们大多是设置为0或空列表] ...
/* 进程关系:它是自己的父进程,是进程树的根。*/
.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,
/* 凭证(用户ID、组ID等),指向拥有root权限的init_cred。*/
RCU_POINTER_INITIALIZER(real_cred, &init_cred),
RCU_POINTER_INITIALIZER(cred, &init_cred),
/* 任务名,通常是 "swapper/0"。*/
.comm = INIT_TASK_COMM,
/* 体系结构相关的线程状态。*/
.thread = INIT_THREAD,
/* 文件系统上下文,指向初始的init_fs。*/
.fs = &init_fs,
/* 打开的文件描述符,指向初始的init_files。*/
.files = &init_files,
#ifdef CONFIG_IO_URING
.io_uring = NULL,
#endif
/* 信号描述符和信号处理函数表。*/
.signal = &init_signals,
.sighand = &init_sighand,
/* 命名空间代理,指向初始的init_nsproxy。*/
.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-futex (优先级继承) 相关的锁。*/
.pi_lock = __RAW_SPIN_LOCK_UNLOCKED(init_task.pi_lock),
/* 定时器松弛度,默认为50微秒。*/
.timer_slack_ns = 50000,
/* 线程的PID结构体指针。*/
.thread_pid = &init_struct_pid,
/* 线程组内的链表节点。*/
.thread_node = LIST_HEAD_INIT(init_signals.thread_head),
// ... [省略了更多特定配置下的初始化字段] ...
};
/* 将init_task这个符号导出,使得可加载内核模块也能访问它。*/
EXPORT_SYMBOL(init_task);
init/version.c
# init/version-timestamp.c
/* FIXED STRINGS! Don't touch! */
const char linux_banner[] =
"Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";
linux_banner linux版本号
/*
* init_uts_ns 和 linux_banner 都包含构建版本和时间戳,它们在构建过程的最后一步实际上是固定的。它们首先使用 __weak 进行编译,然后不使用 __weak。
*/
struct uts_namespace init_uts_ns __weak;
const char linux_banner[] __weak;
include/linux/init.h
__setup_param
/*
* 仅适用于真正的核心代码。 有关正常方法,请参见 moduleparam.h 。
*
* 强制对齐,以便编译器不会将
* obs_kernel_param .init.setup 中的 “array” 相距太远。
*/
#define __setup_param(str, unique_id, fn, early) \
static const char __setup_str_##unique_id[] __initconst \
__aligned(1) = str; \
static struct obs_kernel_param __setup_##unique_id \
__used __section(".init.setup") \
__aligned(__alignof__(struct obs_kernel_param)) \
= { __setup_str_##unique_id, fn, early }
/*
* 注意:__setup函数返回值:
* @fn 如果 option 参数为 “handled”,则返回 1(或非零),如果 option 参数为 “not handled”,则返回 0。
*/
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
//这里允许早期初始化参数执行调用
/*
* 注意:@fn根据module_param,而不是__setup!
* 即,@fn 返回 0 表示无错误,或返回非零表示错误
*(可能@fn返回 -errno 值,但这无关紧要)。
* 如果 @fn 返回非零,则发出警告。
*/
#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)
early_initcall 早期初始化函数
/*
* initcall现在按功能被分组到不同的子段中。子段内部的顺序由链接顺序决定。
* 为了向后兼容,initcall()会将调用放入设备初始化子段。
*
* __define_initcall()的'id'参数是必需的,以便多个initcall可以指向
* 同一个处理函数而不会导致重复符号的构建错误。
*
* Initcall通过将指针放置在initcall段中来运行,内核在运行时会遍历这些段。
* 链接器可能会进行死代码/数据消除并将其完全移除,所以initcall段必须
* 在链接器脚本中被标记为KEEP()。
*/
/*
* 格式: <模块名>__<计数器>_<行号>_<函数名>
*
* @fn: 要注册的函数名。
*
* 原理: 这个宏使用多层嵌套的__PASTE(a,b)宏(通常定义为a##b)来创建一个
* 对于整个内核构建来说都极大概率是唯一的标识符。它拼接了模块名、
* 一个编译时计数器(__COUNTER__)、源代码行号(__LINE__)和函数名本身。
* 这个独一无二的ID(__iid)被用于生成唯一的变量名,以防止链接错误。
*/
#define __initcall_id(fn) \
__PASTE(__KBUILD_MODNAME, \
__PASTE(__, \
__PASTE(__COUNTER__, \
__PASTE(_, \
__PASTE(__LINE__, \
__PASTE(_, fn))))))
/*
* 格式: __<前缀>__<唯一ID><等级ID>
*
* @prefix: 变量名前缀,通常是'initcall'。
* @__iid: 由__initcall_id生成的唯一标识符。
* @id: 初始化的等级名,如'early'。
*
* 原理: 这个宏同样使用__PASTE拼接出一个全局唯一的变量名。
* 例如:__initcall__mymodule__5_10_myfunc_early
*/
#define __initcall_name(prefix, __iid, id) \
__PASTE(__, \
__PASTE(prefix, \
__PASTE(__, \
__PASTE(__iid, id))))
/*
* 这是最底层的定义宏,负责实际生成静态函数指针变量。
* @fn: 要注册的初始化函数指针。
* @__unused: 一个未被使用的占位符参数,用于保持宏接口的一致性。
* @__name: 将要生成的静态变量的唯一名称,由上层宏构造。
* @__sec: 将要放入的链接器段的名称,由上层宏构造。
*/
#define ____define_initcall(fn, __unused, __name, __sec) \
/* 定义一个静态的、类型为initcall_t(即 int (*)(void) )的函数指针变量。*/
static initcall_t __name __used \
/* __used是GCC属性,告知编译器即使此静态变量看似未被使用,也不要丢弃它。*/
/* __attribute__((__section__(__sec)))是GCC属性,指示编译器将此变量放入名为__sec的特定链接器段中。*/
__attribute__((__section__(__sec))) = fn;
/*
* 一个简单的辅助宏,在当前上下文中,它直接返回函数名fn。
*/
#define __initcall_stub(fn, __iid, id) fn
/*
* 一个辅助宏,用于生成链接器段的名称。
* @__sec: 段名的基础部分,如'.initcallearly'。
* @__iid: (在此宏中未使用)。
*/
#define __initcall_section(__sec, __iid) \
/* '#'是预处理器中的“字符串化”操作符。它会将宏参数__sec转换为一个字符串字面量。
* C语言会自动将相邻的字符串字面量拼接,最终形成如".initcallearly.init"的段名。*/
#__sec ".init"
/*
* 一个中间宏,负责组装所有参数以调用最底层的____define_initcall。
* @fn: 要注册的函数。
* @id: 初始化等级名。
* @__sec: 段名的基础部分。
* @__iid: 唯一的数字ID。
*/
#define __unique_initcall(fn, id, __sec, __iid) \
/* 调用底层宏,并为其传递所有构造好的参数。*/
____define_initcall(fn, \
__initcall_stub(fn, __iid, id), \
__initcall_name(initcall, __iid, id), \
__initcall_section(__sec, __iid))
/*
* 一个中间宏,负责生成唯一的数字ID。
* @fn: 要注册的函数。
* @id: 初始化等级名。
* @__sec: 段名的基础部分。
*/
#define ___define_initcall(fn, id, __sec) \
/* 调用上一层宏,并将__initcall_id(fn)的结果作为唯一ID (__iid) 传递。*/
__unique_initcall(fn, id, __sec, __initcall_id(fn))
/*
* 一个高层宏,负责根据等级名自动构造段名的基础部分。
* @fn: 要注册的函数。
* @id: 初始化等级名,如'early', '1'等。
*/
#define __define_initcall(fn, id) \
/* 调用上一层宏,并使用##拼接符,将".initcall"和等级名id拼接成段名的基础部分,如.initcallearly。*/
___define_initcall(fn, id, .initcall##id)
/*
* 早期initcall在初始化SMP之前运行。
*
* 仅用于内建代码,不用于模块。
*/
/*
* 这是最终提供给内核开发者的用户接口宏。
* @fn: 要注册的初始化函数。
*/
#define early_initcall(fn) __define_initcall(fn, early)
init/main.c
start_kernel 启动内核
/*
* 调试助手:通过这个标志,我们知道我们处于“早期启动代码”中,只有启动处理器在禁用 IRQ 的情况下运行。
* 这意味着两件事 - 在清除标志之前不得启用 IRQ,并且在设置标志时允许一些禁用 IRQ 不允许的作。
*/
bool early_boot_irqs_disabled __read_mostly;
asmlinkage //标记为C函数
__visible //可被外部链接
__init //标记为仅在初始化阶段使用,并在初始化阶段之后释放已使用的内存资源
__no_sanitize_address //标记为不进行内存错误检查
__noreturn //标记为不返回
__no_stack_protector //标记为不使用栈保护
void start_kernel(void)
{
char *command_line;
char *after_dashes;
set_task_stack_end_magic(&init_task); //设置栈底magic用于溢出检测
//arch/arm/kernel/setup.c
smp_setup_processor_id(); //pr_info("Booting Linux on physical CPU 0");
//CONFIG_DEBUG_OBJECTS
debug_objects_early_init(); //调试内核对象
//#if IS_ENABLED(CONFIG_STACKTRACE_BUILD_ID) || IS_ENABLED(CONFIG_VMCORE_INFO)
init_vmlinux_build_id(); //初始化内核版本号
//CONFIG_CGROUPS cgroups 是 Linux 内核提供的一种机制,用于对系统资源(如 CPU、内存、I/O 等)进行分组和管理
cgroup_init_early(); //初始化cgroup
local_irq_disable();
early_boot_irqs_disabled = true;
/*
*中断仍处于禁用状态。进行必要的设置,然后启用它们。
*/
boot_cpu_init(); //激活第一个CPU
/*
CONFIG_HIGHMEM
ARM 处理器的地址空间只有 4 GB,它必须容纳用户地址空间、内核地址空间以及一些内存映射 IO。这意味着,如果你有大量的物理内存和/或 IO,并不是所有的内存都可以被内核 “永久映射”。未永久映射的物理内存称为 “高内存”。
根据所选的内核/用户内存拆分、最小 vmalloc 空间和实际的 RAM 量,您可能不需要此选项,这应该会导致内核速度稍快。 */
page_address_init(); //设置内核页表地址
pr_notice("%s", linux_banner); //打印内核版本号
setup_arch(&command_line); //设置体系结构相关的参数,获取命令行参数
/* LSM 需要静态密钥和静态调用
LSM(Linux Security Modules,Linux 安全模块)是 Linux 内核中的一个框架,用于实现可插拔的安全机制。
它为内核提供了一种通用的接口,允许开发者通过加载不同的安全模块来增强系统的安全性。
*/
jump_label_init();
static_call_init();
early_security_init();
/* 额外的启动配置允许系统管理员在启动时将配置文件作为内核 cmdline 的补充扩展传递。
* 引导配置文件必须附在 initramfs 的末尾,并附上校验和、大小和魔术字。有关详细信息,
请参见 <file:Documentation/admin-guide/bootconfig.rst>。*/
setup_boot_config();
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas(); //设置每个CPU的内存区域
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
early_numa_node_init();
boot_cpu_hotplug_init();
pr_notice("Kernel command line: %s\n", saved_command_line);
/* parameters may set static keys */
parse_early_param();
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, NULL, &unknown_bootoption);
print_unknown_bootoptions();
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
NULL, set_init_arg);
if (extra_init_args)
parse_args("Setting extra init args", extra_init_args,
NULL, 0, -1, -1, NULL, set_init_arg);
/* 架构和非计时 rng init,在 allocator init 之前 */
random_init_early(command_line);
/*
* 这些使用大量 bootmem 分配,并且必须在页面分配器初始化之前
*/
setup_log_buf(0);
vfs_caches_init_early();
sort_main_extable();
trap_init();
mm_core_init(); //内存管理初始化
poking_init();
ftrace_init();
/* trace_printk can be enabled here */
early_trace_init();
/*
* 在启动任何中断(例如定时器中断)之前设置调度器。
* 完整的拓扑设置在 smp_init() 时间进行 - 但与此同时,我们仍然有一个正常运行的调度器。
*/
sched_init();
//禁用中断,防止在初始化期间发生中断
if (WARN(!irqs_disabled(),
"Interrupts were enabled *very* early, fixing it\n"))
local_irq_disable();
radix_tree_init(); //初始化radix树
maple_tree_init(); //初始化maple树
/*
* Set up housekeeping before setting up workqueues to allow the unbound
* workqueue to take non-housekeeping into account.
*/
// housekeeping_init();
/*
* 允许提前创建工作队列和工作项排队/取消。 工作项执行依赖于 kthread,并在 workqueue_init() 之后启动。
*/
workqueue_init_early();
/* RCU 节点与线程和工作队列初始化 */
rcu_init();
kvfree_rcu_init();
/* Trace events are available after this */
trace_init();
if (initcall_debug)
initcall_debug_enable();
context_tracking_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ();
// tick_init();
// rcu_init_nohz();
init_timers();
srcu_init();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
/* 这必须在时间记录初始化之后*/
random_init();
/* These make use of the fully initialized rng */
kfence_init();
boot_init_stack_canary();
perf_event_init();
profile_init();
call_function_init();
WARN(!irqs_disabled(), "Interrupts were enabled early\n");
early_boot_irqs_disabled = false;
local_irq_enable();
kmem_cache_init_late();
/*
* 黑客警报!现在还为时过早。
* 我们在完成 PCI 设置等之前就启用了控制台,console_init() 必须意识到这一点。
* 但我们确实希望尽早输出,以防出现问题。
*/
console_init();
/* 参数已满,打印原因和参数 */
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later,
panic_param);
lockdep_init();
/*
* 需要在启用 irqs 时运行这个,因为它也想自检 [硬/软]-irqs 开/关锁反转 bug:
*/
locking_selftest();
/* 检查ramfs是否异常 */
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
setup_per_cpu_pageset();
numa_policy_init();
acpi_early_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
arch_cpu_finalize_init();
pid_idr_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode();
#endif
thread_stack_cache_init();
cred_init();
fork_init();
proc_caches_init();
uts_ns_init();
vfs_caches_init();
pagecache_init();
signals_init();
seq_file_init();
proc_root_init();
nsfs_init();
pidfs_init();
// cpuset_init();
// cgroup_init();
// delayacct_init();
// acpi_subsystem_init();
// arch_post_acpi_subsys_init();
// kcsan_init();
/* 把剩下的非 __init__ 的部分完成,我们现在活了 */
rest_init();
/*
* 避免在 gcc-10 及更早版本中调用 boot_init_stack_canary 的代码中使用栈金丝雀。
*/
#if !__has_attribute(__no_stack_protector__)
prevent_tail_call_optimization();
#endif
}
loglevel console_loglevel参数设置
console_loglevel
是内核日志级别的设置,控制内核打印的日志信息的详细程度。它的值范围从0到7,分别表示不同的日志级别。console_loglevel
的值在内核启动时通过命令行参数进行设置,通常在内核启动时传递给内核的命令行参数中可以找到,例如loglevel=8
。
static int __init loglevel(char *str)
{
int newlevel;
/*
* 仅在传递正确设置时更新 loglevel 值,以防止难以调试的盲崩溃(当 loglevel 设置为 0 时)
*/
if (get_option(&str, &newlevel)) {
console_loglevel = newlevel;
return 0;
}
return -EINVAL;
}
early_param("loglevel", loglevel);
[2025/4/1 20:13:46 947] [ 0.000000] doing early options, parsing ARGS: 'console=ttySTM0,115200 root=/dev/ram loglevel=8'
[2025/4/1 20:13:46 951] [ 0.000000] doing early options: console='ttySTM0,115200'
[2025/4/1 20:13:46 957] [ 0.000000] Early parameter console=ttySTM0,115200
[2025/4/1 20:13:46 961] [ 0.000000] doing early options: root='/dev/ram'
[2025/4/1 20:13:46 966] [ 0.000000] doing early options: loglevel='8'
[2025/4/1 20:13:46 969] [ 0.000000] Early parameter loglevel=8
parse_early_param 解析内核参数
parse_args("early options", cmdline, NULL, 0, 0, 0, NULL, do_early_param);
传入num为0,导致不会执行/* Find parameter */
,直接执行handle_unknown
- 所以执行了
do_early_param
函数,解析内核参数 do_early_param
函数会遍历__setup_start
到__setup_end
的所有参数,找到匹配的参数执行对应的函数。- fdt传入了
loglevel
参数,执行了loglevel
函数,设置了console_loglevel
的值。
[2025/4/1 20:13:46 947] [ 0.000000] doing early options, parsing ARGS: 'console=ttySTM0,115200 root=/dev/ram loglevel=8'
[2025/4/1 20:13:46 951] [ 0.000000] doing early options: console='ttySTM0,115200'
[2025/4/1 20:13:46 957] [ 0.000000] Early parameter console=ttySTM0,115200
[2025/4/1 20:13:46 961] [ 0.000000] doing early options: root='/dev/ram'
[2025/4/1 20:13:46 966] [ 0.000000] doing early options: loglevel='8'
[2025/4/1 20:13:46 969] [ 0.000000] Early parameter loglevel=8
/* Check for early params. */
static int __init do_early_param(char *param, char *val,
const char *unused, void *arg)
{
const struct obs_kernel_param *p;
for (p = __setup_start; p < __setup_end; p++) {
if (p->early && parameq(param, p->str)) {
if (p->setup_func(val) != 0)
pr_warn("Malformed early option '%s'\n", param);
}
}
/* We accept everything at this stage. */
return 0;
}
void __init parse_early_options(char *cmdline)
{
parse_args("early options", cmdline, NULL, 0, 0, 0, NULL,
do_early_param);
}
/* Arch code calls this early on, or if not, just before other parsing. */
void __init parse_early_param(void)
{
static int done __initdata;
static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;
if (done)
return;
/* All fall through to do_early_param. */
strscpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_early_options(tmp_cmdline);
done = 1;
}
setup_boot_config 设置boot配置
static void * __init get_boot_config_from_initrd(size_t *_size)
{
u32 size, csum;
char *data;
u32 *hdr;
int i;
if (!initrd_end)
return NULL;
data = (char *)initrd_end - BOOTCONFIG_MAGIC_LEN;
/*
* Since Grub may align the size of initrd to 4, we must
* check the preceding 3 bytes as well.
*/
for (i = 0; i < 4; i++) {
if (!memcmp(data, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN))
goto found;
data--;
}
return NULL;
}
static void __init setup_boot_config(void)
{
/* Remove bootconfig data from initrd */
get_boot_config_from_initrd(NULL);
}
setup_command_line 设置命令行参数
/*
* 我们需要存储未修改的命令行以备将来参考。
* 由于参数解析是就地执行的,我们还需要存储 touched 命令行,
* 我们应该允许组件存储 name/value 的引用以备将来参考。
*/
static void __init setup_command_line(char *command_line)
{
size_t len, xlen = 0, ilen = 0;
//有bootconfig才会配置
if (extra_command_line)
xlen = strlen(extra_command_line);
if (extra_init_args) {
extra_init_args = strim(extra_init_args); /* remove trailing space */
ilen = strlen(extra_init_args) + 4; /* for " -- " */
}
len = xlen + strlen(boot_command_line) + ilen + 1;
saved_command_line = memblock_alloc_or_panic(len, SMP_CACHE_BYTES);
len = xlen + strlen(command_line) + 1;
static_command_line = memblock_alloc_or_panic(len, SMP_CACHE_BYTES);
if (xlen) {
/*
* We have to put extra_command_line before boot command
* lines because there could be dashes (separator of init
* command line) in the command lines.
*/
strcpy(saved_command_line, extra_command_line);
strcpy(static_command_line, extra_command_line);
}
strcpy(saved_command_line + xlen, boot_command_line);
strcpy(static_command_line + xlen, command_line);
if (ilen) {
/*
* Append supplemental init boot args to saved_command_line
* so that user can check what command line options passed
* to init.
* The order should always be
* " -- "[bootconfig init-param][cmdline init-param]
*/
if (initargs_offs) {
len = xlen + initargs_offs;
strcpy(saved_command_line + len, extra_init_args);
len += ilen - 4; /* strlen(extra_init_args) */
strcpy(saved_command_line + len,
boot_command_line + initargs_offs - 1);
} else {
len = strlen(saved_command_line);
strcpy(saved_command_line + len, " -- ");
len += 4;
strcpy(saved_command_line + len, extra_init_args);
}
}
saved_command_line_len = strlen(saved_command_line);
}
obsolete_checksetup 过时的参数检查
- 早期参数在内核启动时解析,通常在
setup_arch
函数中完成。这里不需要执行 - 过时的参数在内核启动后解析,通常在
init/main.c
中完成。
static bool __init obsolete_checksetup(char *line)
{
const struct obs_kernel_param *p;
bool had_early_param = false;
p = __setup_start;
do {
int n = strlen(p->str);
if (parameqn(line, p->str, n)) {
if (p->early) {
/* 已经在 parse_early_param 年完成了?(需要对参数部分进行精确匹配)。
继续迭代,因为我们可以有同名的早期 params 和 __setups 8( */
if (line[n] == '\0' || line[n] == '=')
had_early_param = true;
} else if (!p->setup_func) {
pr_warn("Parameter %s is obsolete, ignored\n",
p->str);
return true;
} else if (p->setup_func(line + n))
return true;
}
p++;
} while (p < __setup_end);
return had_early_param;
}
unknown_bootoption 未知的boot参数
/*
*未知的引导选项会交给 init,除非它们看起来像未使用的参数(modprobe 会在 /proc/cmdline 中找到它们)。
*/
static int __init unknown_bootoption(char *param, char *val,
const char *unused, void *arg)
{
size_t len = strlen(param);
/* 处理别名为 sysctl 参数的参数*/
if (sysctl_is_alias(param))
return 0;
repair_env_string(param, val);
/* 处理过时样式的参数 */
if (obsolete_checksetup(param))
return 0;
/* Unused module parameter. */
if (strnchr(param, len, '.'))
return 0;
if (panic_later)
return 0;
if (val) {
/* Environment option
将未知参数存储起来*/
unsigned int i;
for (i = 0; envp_init[i]; i++) {
/* 存储满了,将会在后面打印原因和参数 */
if (i == MAX_INIT_ENVS) {
panic_later = "env";
panic_param = param;
}
if (!strncmp(param, envp_init[i], len+1))
break;
}
envp_init[i] = param;
} else {
/* Command line option
将未知参数存储起来*/
unsigned int i;
for (i = 0; argv_init[i]; i++) {
/* 存储满了,将会在后面打印原因和参数 */
if (i == MAX_INIT_ARGS) {
panic_later = "init";
panic_param = param;
}
}
argv_init[i] = param;
}
return 0;
}
rest_init 完成系统启动的最后阶段
static noinline void __ref __noreturn rest_init(void)
{
struct task_struct *tsk;
int pid;
/* RCU 调度器启动 */
rcu_scheduler_starting();
/*
* 我们需要首先启动 init 以使其获得 pid 1,但是
* init 任务最终会希望创建 kthreads,如果
* 在我们创建 kthreadd 之前调度它,将会导致 OOPS。
*/
/* 使用 user_mode_thread 创建初始用户空间进程 init,并赋予其进程号 pid=1
* init 是系统中的第一个用户空间进程,负责启动其他用户空间服务
* SCHED_NORMAL 策略 使用fair_sched_class策略 */
pid = user_mode_thread(kernel_init, NULL, CLONE_FS);
/*
* 在启动 CPU 上初始化 Pin。在运行 sched_init_smp() 之前,
* 任务迁移无法正常工作。它将为 init 设置允许的 CPU,
* 即非隔离的 CPU。
*/
rcu_read_lock();
/* 根据任务的 PID 和命名空间(init_pid_ns)获取任务对象 tsk */
tsk = find_task_by_pid_ns(pid, &init_pid_ns);
/* 禁止 init 进程的 CPU 亲和性设置 */
tsk->flags |= PF_NO_SETAFFINITY;
/* 将 init 进程固定到启动 CPU */
set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
rcu_read_unlock();
/* 设置 NUMA(非统一内存访问)默认策略,确保内存分配在 NUMA 系统中正常工作 */
// numa_default_policy();
/* 创建内核线程管理进程 kthreadd
* SCHED_NORMAL 策略 使用fair_sched_class策略
*/
pid = kernel_thread(kthreadd, NULL, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
/*
* 启用 might_sleep() 和 smp_processor_id() 检查。
* 它们不能提前启用,因为在 CONFIG_PREEMPTION=y 的情况下,
* kernel_thread() 会触发 might_sleep() 错误。对于
* CONFIG_PREEMPT_VOLUNTARY=y,初始化任务可能已经调度,
* 但它卡在 kthreadd_done 完成上。
*/
system_state = SYSTEM_SCHEDULING;
/* 通过完成 kthreadd_done,通知 kthreadd 进程已准备好 */
complete(&kthreadd_done);
/*
* 引导空闲线程必须至少执行一次 schedule()
* ,确保系统调度器开始运行
*/
schedule_preempt_disabled();
/* 调用禁用抢占的 cpu_idle
* 最后调用 cpu_startup_entry 进入 CPU 的空闲循环,等待任务调度 */
cpu_startup_entry(CPUHP_ONLINE);
}
initcall_blacklisted 检查初始化调用是否在黑名单中
/*
* __init_or_module: 这是一个特殊的宏,表示这个函数既可能在启动时的.init段,
* 也可能在加载模块时被使用。
*
* initcall_blacklisted - 检查一个initcall函数是否在黑名单中。
* @fn: 指向需要被检查的initcall函数。
*
* 返回值: 如果函数在黑名单中,则返回true;否则返回false。
*/
static bool __init_or_module initcall_blacklisted(initcall_t fn)
{
/* entry: 用于在链表遍历中指向每个黑名单条目。*/
struct blacklist_entry *entry;
/* fn_name: 一个字符数组,用于存储从函数指针解析出的符号名。*/
char fn_name[KSYM_SYMBOL_LEN];
/* addr: 用于存储函数指针fn对应的真实内存地址。*/
unsigned long addr;
/*
* 这是一个快速路径检查。如果blacklisted_initcalls链表为空,
* 说明用户在启动时没有提供initcall_blacklist参数,所以不可能有匹配项。
*/
if (list_empty(&blacklisted_initcalls))
return false;
/*
* 调用dereference_function_descriptor,从函数指针fn中获取其真实的
* 可执行代码地址。在大多数架构上,这直接返回fn本身。
*/
addr = (unsigned long) dereference_function_descriptor(fn);
/*
* 调用sprint_symbol_no_offset,根据地址addr在内核符号表中查找
* 对应的函数名,并将结果(不带偏移量)存入fn_name缓冲区。
*/
sprint_symbol_no_offset(fn_name, addr);
/*
* 注释:fn将会是 "function_name [module_name]" 的格式,其中[module_name]
* 对于内建的init函数不会显示。剥离掉[module_name]。
*
* 原理:调用strreplace,将fn_name中的第一个空格字符替换为字符串结束符'\0'。
* 这可以有效地将 "my_func [my_module]" 截断为 "my_func"。
*/
strreplace(fn_name, ' ', '\0');
/*
* 使用list_for_each_entry宏,遍历全局的blacklisted_initcalls链表。
*/
list_for_each_entry(entry, &blacklisted_initcalls, next) {
/*
* 使用strcmp比较解析出的函数名fn_name和链表条目中存储的黑名单字符串。
*/
if (!strcmp(fn_name, entry->buf)) {
/* 如果字符串完全匹配。*/
/* 打印一条调试信息,表明该initcall已被黑名单命中。*/
pr_debug("initcall %s blacklisted\n", fn_name);
/* 返回true。*/
return true;
}
}
/* 如果遍历完整个黑名单链表都没有找到匹配项,则返回false。*/
return false;
}
do_one_initcall 执行单个初始化调用
- 安全地执行一个给定的初始化函数指针(fn),并在此前后进行必要的追踪、状态校验和错误处理。 它是整个initcall机制的**“原子执行单元”**
/*
* __init_or_module: 这是一个特殊的宏,表示这个函数既可能在启动时的.init段,
* 也可能在加载模块时被使用。
*
* do_one_initcall - 安全地执行一个初始化调用。
* @fn: 指向需要被执行的initcall函数。
*
* 返回值: initcall函数fn自身的返回值。
*/
int __init_or_module do_one_initcall(initcall_t fn)
{
/* @count: 整型变量,用于保存执行initcall之前的抢占计数值,以便事后比较。*/
int count = preempt_count();
/* @msgbuf: 一个字符缓冲区,用于在状态校验失败时,构建需要打印的警告信息。*/
char msgbuf[64];
/* @ret: 整型变量,用于保存initcall函数fn的返回值。*/
int ret;
/* 调用initcall_blacklisted,检查函数指针fn是否在内核启动参数指定的黑名单中。*/
if (initcall_blacklisted(fn))
/* 如果在黑名单中,则拒绝执行,并返回权限错误码 -EPERM。*/
return -EPERM;
/* 调用追踪辅助函数,向ftrace等工具记录一个initcall的开始事件,参数是函数指针fn。*/
// do_trace_initcall_start(fn);
/* 通过函数指针,直接调用传入的初始化函数,并将其返回值存入ret。这是核心的执行步骤。*/
ret = fn();
/* 调用追踪辅助函数,记录一个initcall的结束事件,并附上其返回值ret。*/
// do_trace_initcall_finish(fn, ret);
/* 将警告信息缓冲区msgbuf的第一个字节置为0,即将其初始化为空字符串。*/
msgbuf[0] = 0;
/* 比较执行fn之后和之前的抢占计数值。*/
if (preempt_count() != count) {
/* 如果不相等,说明在fn中存在未配对的preempt_disable/enable,这是一个需要报告的bug。*/
/* 将“preemption imbalance ”(抢占不平衡)的字符串格式化到msgbuf中。*/
sprintf(msgbuf, "preemption imbalance ");
/* 强制将抢占计数恢复到执行fn之前的状态,以避免这个错误影响后续的内核代码执行。*/
preempt_count_set(count);
}
/* 检查fn返回时,本地中断是否处于关闭状态。*/
if (irqs_disabled()) {
/* 如果是,这也是一个需要报告的bug。*/
/* 将“disabled interrupts ”(中断已禁用)的字符串追加到msgbuf中。*/
strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
/* 强制重新开启本地中断,以恢复系统的正常响应能力。*/
local_irq_enable();
}
/*
* 如果msgbuf[0]不为0,说明上面的两个检查中至少有一个失败了。
* WARN宏会向内核日志打印一条包含函数地址(%pS)和错误信息(msgbuf)的警告。
*/
WARN(msgbuf[0], "initcall %pS returned with %s\n", fn, msgbuf);
/* 利用初始化过程中的计时抖动(Jitter),为内核的随机数熵池增加一些熵。*/
// add_latent_entropy();
/* 返回原始的initcall函数的执行结果ret,将其透传给上层调用者。*/
return ret;
}
do_basic_setup 内核从基础的“计算核心”阶段,转向开始与硬件设备进行交互的“系统平台”阶段
- 在它被调用之前,内核已经完成了最基础的设置,包括CPU子系统的启动、内存管理(伙伴系统)的就绪以及进程管理(调度器)的初始化。do_basic_setup标志着内核从基础的“计算核心”阶段,转向开始与硬件设备进行交互的“系统平台”阶段。
/*
* 好的,计算机现在已经初始化完毕。所有的设备都还没有被触及,
* 但是CPU子系统已经启动并运行,内存和进程管理也已工作。
*
* 现在我们终于可以开始做一些真正的工作了……
*/
// `__init`宏指示编译器将此函数放入".init.text"内存段,
// 这段内存在内核启动完成后会被释放,以节约宝贵的RAM。
static void __init do_basic_setup(void)
{
/*
* cpuset_init_smp() 用于初始化CPUseT功能,它允许将进程绑定到特定的CPU和内存节点。
* 函数名中的"smp"表示它主要服务于对称多处理系统。
* 在单核(Uniprocessor)配置下(内核编译时未定义CONFIG_SMP),
* 此函数是一个空存根(stub),编译后为空操作,因为没有多个CPU可以组织成集合。
*/
cpuset_init_smp();
/*
* driver_init() 是一个至关重要的步骤。它初始化了Linux的设备模型核心。
* 这个函数会创建 `/sys` 文件系统(sysfs)的骨架,例如 `/sys/bus` 和 `/sys/class` 目录。
* 它本身不探测或初始化任何具体的设备驱动,而是搭建了让驱动程序能够注册、
* 与设备进行匹配和管理的基础设施。
*/
driver_init();
/*
* init_irq_proc() 用于在/proc文件系统中创建与中断相关的接口。
* 具体来说,它会创建 `/proc/irq` 目录和 `/proc/stat` 文件。
* 在STM32上,这对于调试非常有用,开发者可以通过查看这些文件来监控
* 哪个硬件外设(如UART, SPI, DMA)正在产生中断,以及中断发生的频率。
*/
init_irq_proc();
/*
* do_ctors() 执行全局构造函数。虽然内核主要由C语言编写,但它支持链接
* 使用C++或需要静态构造函数的代码。此函数会遍历一个由链接器生成的
* 函数指针列表(构造函数列表),并依次调用它们。这确保了在任何代码
* 使用这些对象之前,它们都已经被正确初始化。
*/
// do_ctors();
/*
* do_initcalls() 是此函数中最重要的部分,是内核设备初始化的核心引擎。
* 内核的各个子系统和设备驱动程序使用 `*_initcall()` 宏(如 `subsys_initcall`, `device_initcall`)
* 来注册它们的初始化函数。这些宏会将函数指针放入特定的、有序的内存段中。
* `do_initcalls` 会按照预定义的顺序(从early到late)依次执行这些注册的初始化函数。
* 在STM32平台上,正是通过这个机制,GPIO、I2C、SPI、UART、ETH等所有外设的驱动程序的
* `probe` 函数被调用,从而使内核能够识别并控制这些硬件。
*/
do_initcalls();
}
kernel_init_freeable 内核初始化可释放资源
/*
* noinline: 指示编译器不对此函数进行内联优化。
* __init: 将此函数的目标代码和数据放入.init段,以便启动后回收。
*/
static noinline void __init kernel_init_freeable(void)
{
/* 注释:此时调度器已完全建立,可进行阻塞式内存分配。*/
/* 将GFP分配掩码设置为全允许,解除早期启动阶段的分配限制。*/
gfp_allowed_mask = __GFP_BITS_MASK;
/*
* 注释:init进程可在任何NUMA节点上分配页面。
*/
/* 设置当前任务的内存策略,允许在所有可用内存节点上进行分配。*/
// set_mems_allowed(node_states[N_MEMORY]);
/* 获取当前进程(PID 1)的PID结构体指针,用于后续的CAD(Ctrl-Alt-Del)处理。*/
cad_pid = get_pid(task_pid(current));
/* 准备所有CPU,使其达到最大可配置数量,为SMP启动做准备。*/
// smp_prepare_cpus(setup_max_cpus);
/* 初始化内核工作队列(workqueue)子系统。*/
workqueue_init();
/* 初始化内存管理(mm)子系统的内部组件。*/
init_mm_internals();
/* 执行所有标记为 pre-SMP 等级的 initcall。*/
do_pre_smp_initcalls();
/* 初始化内核锁死(lockup)检测器。*/
// lockup_detector_init();
/* 初始化并启动非引导CPU,完成SMP环境的激活。*/
// smp_init();
/* 初始化调度器的SMP相关部分,如负载均衡。*/
sched_init_smp();
/* 初始化与SMP和NUMA拓扑相关的工作队列组件。*/
workqueue_init_topology();
/* 初始化异步函数调用机制。*/
async_init();
/* 初始化并行数据(padata)子系统。*/
// padata_init();
/* 执行页分配器(page allocator)的后期初始化。*/
page_alloc_init_late();
/* 执行基本的系统设置,此函数会触发大部分设备驱动的initcall。*/
do_basic_setup();
/* 如果配置了KUnit,则运行所有内核单元测试。*/
kunit_run_all_tests();
/* 等待初始内存文件系统(initramfs)准备就绪。*/
wait_for_initramfs();
/* 尝试将内核控制台附加到根文件系统上。*/
console_on_rootfs();
/*
* 注释:检查是否存在一个早期的用户空间init程序。如果有,让它完成所有工作。
*/
/* 检查ramdisk_execute_command指定路径的init程序是否可执行。*/
if (init_eaccess(ramdisk_execute_command) != 0) {
/* 若不可执行,则清空该路径,并准备默认的根文件系统命名空间。*/
ramdisk_execute_command = NULL;
prepare_namespace();
}
/*
* 注释:至此,已完成初始引导,系统基本运行。
* 可以释放initmem段,并启动用户模式程序。
*
* 注释:根文件系统现已可用,尝试加载公钥和默认模块。
*/
/* 从文件系统加载用于完整性验证的密钥。*/
integrity_load_keys();
}
kernel_init PID1线程
/*
* 这是一个静态函数,其代码位于__init段,因此在启动后内存会被回收。
* 此函数是内核创建的第一个线程(PID 1)的入口点。
* @unused: 未使用的参数,以匹配kthread_create的函数原型。
*/
static int __ref kernel_init(void *unused)
{
int ret;
/*
* 通过等待完成量kthreadd_done,确保kthreadd内核线程(PID 2)
* 已经完成其初始化设置。
*/
wait_for_completion(&kthreadd_done);
/* 执行其他标记为可释放的初始化函数。*/
kernel_init_freeable();
/* 同步等待所有异步的__init代码执行完毕。*/
async_synchronize_full();
/* 更新全局系统状态,表示即将开始释放初始化内存。*/
system_state = SYSTEM_FREEING_INITMEM;
/* 为各个内核子系统(kprobes, ftrace, kgdb)执行初始化内存的释放。*/
kprobe_free_init_mem();
ftrace_free_init_mem();
kgdb_free_init_mem();
exit_boot_config();
/* 释放所有标记为__init的内存段。*/
free_initmem();
/* 将内核代码等区域设置为只读,以防止被修改。*/
mark_readonly();
/*
* 最终确定内核内存映射,并更新用户空间页表以完成页表隔离(PTI)的设置。
*/
pti_finalize();
/* 更新全局系统状态为SYSTEM_RUNNING,表示内核已完全初始化并进入正常运行状态。*/
system_state = SYSTEM_RUNNING;
/* 设置默认的NUMA(非统一内存访问)策略。*/
numa_default_policy();
/* 结束RCU(读-复制-更新)的内核内引导模式。*/
rcu_end_inkernel_boot();
/* 处理通过/proc/sys/接口传递的内核参数。*/
do_sysctl_args();
/*
* 以下部分按优先级顺序,尝试执行第一个用户空间init进程。
*/
/* 检查是否存在通过"rdinit="内核参数指定的ramdisk init命令。*/
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
/* run_init_process成功则不返回。若返回,则表示执行失败。*/
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/* 检查是否存在通过"init="内核参数指定的init命令。*/
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
/* 如果用户明确指定的init执行失败,这是一个致命错误,触发系统panic。*/
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
/* 检查内核是否在编译时通过CONFIG_DEFAULT_INIT定义了默认init路径。*/
if (CONFIG_DEFAULT_INIT[0] != '\0') {
ret = run_init_process(CONFIG_DEFAULT_INIT);
if (ret)
pr_err("Default init %s failed (error %d)\n",
CONFIG_DEFAULT_INIT, ret);
else
return 0;
}
/* 依次尝试一组标准的init程序路径。
* ||操作符确保只要有一个try_to_run_init_process成功(返回非零值),
* 整个表达式就会短路并返回,而!操作符使其变为0,满足if条件。*/
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
/* 如果所有尝试都失败,触发系统panic。*/
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/admin-guide/init.rst for guidance.");
}
do_pre_smp_initcalls 执行所有被标记为“早期(early)”和“等级0(level 0)”的初始化调用(initcalls)
- 它的核心作用是:执行所有被标记为“早期(early)”和“等级0(level 0)”的初始化调用(initcalls)。 这些是整个内核initcall机制中,最先被执行的一批初始化函数。
/*
0x00000000c02a604c __initcall_start = .
*(.initcallearly.init)
.initcallearly.init
0x00000000c02a604c 0x4 kernel/softirq.o
.initcallearly.init
0x00000000c02a6050 0x4 kernel/signal.o
.initcallearly.init
0x00000000c02a6054 0x4 kernel/umh.o
.initcallearly.init
0x00000000c02a6058 0x4 kernel/kthread.o
.initcallearly.init
0x00000000c02a605c 0x4 kernel/printk/printk.o
.initcallearly.init
0x00000000c02a6060 0x4 kernel/rcu/srcutree.o
.initcallearly.init
0x00000000c02a6064 0xc kernel/rcu/tree.o
.initcallearly.init
0x00000000c02a6070 0x4 kernel/irq_work.o
.initcallearly.init
0x00000000c02a6074 0x4 fs/inode.o
.initcallearly.init
0x00000000c02a6078 0x4 fs/locks.o
.initcallearly.init
0x00000000c02a607c 0x4 fs/sysctls.o
0x00000000c02a6080 __initcall0_start = .
*(.initcall0s.init)
0x00000000c02a6080 __initcall1_start = .
*(.initcall1.init)
.initcall1.init
0x00000000c02a6080 0x4 arch/arm/kernel/ptrace.o
.initcall1.init
0x00000000c02a6084 0x4 kernel/workqueue.o
.initcall1.init
0x00000000c02a6088 0x4 kernel/ksysfs.o
.initcall1.init
0x00000000c02a608c 0x4 kernel/rcu/update.o
.initcall1.init
0x00000000c02a6090 0x4 kernel/dma/coherent.o
.initcall1.init
0x00000000c02a6094 0x4 kernel/time/jiffies.o
.initcall1.init
0x00000000c02a6098 0x4 fs/locks.o
.initcall1.init
0x00000000c02a609c 0x4 fs/binfmt_script.o
.initcall1.init
0x00000000c02a60a0 0x4 fs/binfmt_elf_fdpic.o
.initcall1.init
0x00000000c02a60a4 0x4 fs/binfmt_flat.o
.initcall1.init
0x00000000c02a60a8 0x4 drivers/pinctrl/core.o
.initcall1.init
0x00000000c02a60ac 0x4 drivers/gpio/gpiolib.o
.initcall1.init
0x00000000c02a60b0 0x4 drivers/regulator/core.o
*(.initcall1s.init)
*/
static void __init do_pre_smp_initcalls(void)
{
initcall_entry_t *fn;
do_trace_initcall_level("early");
for (fn = __initcall_start; fn < __initcall0_start; fn++)
/* initcall_from_entry() return *entry; */
do_one_initcall(initcall_from_entry(fn));
}
do_initcalls: 执行内核子系统和驱动的初始化调用
此函数是Linux内核启动过程中最核心的环节之一。它是一个总调度器,负责按照预定义的顺序(称为“初始化级别”),系统性地调用所有内核子系统和设备驱动程序注册的初始化函数(initcall
)。正是这个函数驱动了从核心内存管理到具体硬件驱动的整个初始化过程,是内核从一个最小化的状态“成长”为一个功能完备的操作系统的关键。
/*
* 函数 do_initcalls
* static 关键字表示此函数仅在当前文件中可见.
* __init 关键字告诉编译器将此函数放入特殊的初始化代码段. 在内核启动完成后,
* 该函数所占用的内存会被释放, 以节约系统资源.
*/
static void __init do_initcalls(void)
{
/*
* 定义一个整型变量 level, 用作初始化级别的循环计数器.
*/
int level;
/*
* 定义一个 size_t 类型的变量 len, 用于存储要分配的内存大小.
* 大小等于保存的内核命令行参数的长度(saved_command_line_len)加1,
* 这个额外的1字节用于存储字符串末尾的空终止符 '\0'.
*/
size_t len = saved_command_line_len + 1;
/*
* 定义一个字符指针 command_line, 用于指向动态分配的内存.
*/
char *command_line;
/*
* 调用 kzalloc 分配内存. kzalloc 不仅分配内存, 还会将其内容清零.
* GFP_KERNEL 标志表示如果当前内存不足, 内核的内存分配器可以使当前任务休眠等待.
*/
command_line = kzalloc(len, GFP_KERNEL);
/*
* 检查内存是否分配成功.
*/
if (!command_line)
/*
* 如果分配失败, 这是一个致命错误, 因为内核后续的初始化依赖于命令行参数.
* 调用 panic() 函数会使内核停止运行并打印错误信息, 指出失败的原因和位置.
*/
panic("%s: Failed to allocate %zu bytes\n", __func__, len);
/*
* 使用 for 循环遍历所有的初始化级别.
* initcall_levels 是一个函数指针数组, 每个元素指向一个初始化级别(如 early, core, subsys, device, late)的起始地址.
* ARRAY_SIZE(initcall_levels) - 1 用于获取有效的级别数量.
*/
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
/*
* 注释: 解析器会修改 command_line, 因此每次循环都需要恢复它.
* 调用 strcpy 将原始的、未被修改的内核命令行参数(saved_command_line)复制回
* 我们动态分配的 command_line 缓冲区.
* 这样做是因为 do_initcall_level 内部的某些函数可能会解析并修改这个字符串,
* 恢复操作确保了每个初始化级别都能得到一份干净的命令行.
*/
strcpy(command_line, saved_command_line);
/*
* 调用 do_initcall_level, 执行当前级别(level)的所有初始化函数.
* 这是实际执行初始化工作的地方.
*/
do_initcall_level(level, command_line);
}
/*
* 在所有初始化调用完成后, 释放之前为命令行缓冲区动态分配的内存.
*/
kfree(command_line);
}
do_initcall_level: 执行一个指定级别的初始化调用
该函数是do_initcalls
的核心工作者,负责执行单个“初始化级别”(initcall level)内的所有初始化函数。它的工作流程分为两部分:首先,它解析内核启动命令行,以处理可能影响当前初始化级别或更早级别的参数;然后,它遍历由链接器安排好的、属于当前级别的函数指针列表,并逐一调用这些函数。这个机制确保了内核按照一个严格定义的、从底层到上层的顺序进行初始化。
/*
* 定义一个 initcall_entry_t 指针数组. initcall_entry_t 本质上是一个函数指针.
* __initdata 标记表明这个数组本身也位于初始化数据段, 在启动完成后其内存会被回收.
*
* 数组的每个元素都是由链接器(linker)在编译时定义的特殊符号,
* 它们分别指向对应初始化级别函数列表的起始地址. 这是一个实现有序初始化的核心机制.
*/
static initcall_entry_t *initcall_levels[] __initdata = {
__initcall0_start, // 等级0 (pure): 纯函数, 无依赖
__initcall1_start, // 等级1 (core): 核心子系统初始化, 如中断、定时器
__initcall2_start, // 等级2 (postcore): 核心初始化之后
__initcall3_start, // 等级3 (arch): 架构相关的初始化
__initcall4_start, // 等级4 (subsys): 大多数子系统, 如总线(platform_bus_init)
__initcall5_start, // 等级5 (fs): 文件系统相关, 但在设备驱动之前
__initcall6_start, // 等级6 (device): 设备驱动
__initcall7_start, // 等级7 (late): 最后的初始化调用
__initcall_end, // 最后一个级别的结束地址符号
};
/*
* 定义一个字符串数组, 其内容与 include/linux/init.h 中的 initcall 宏保持同步.
* 这个数组主要用于调试和跟踪, 可以在内核日志中打印出当前正在执行的级别名称.
* __initdata 标记表示它也是临时的, 内存将在启动后被回收.
*/
static const char *initcall_level_names[] __initdata = {
"pure",
"core",
"postcore",
"arch",
"subsys",
"fs",
"device",
"late",
};
/*
* 这是一个简单的回调函数, 当 parse_args 函数遇到一个未知的(即没有通过__param宏注册的)
* 内核启动参数时会被调用.
* @return: 返回0表示成功处理(即, 默默地忽略它), 这是为了防止未识别的启动参数导致启动失败.
*/
static int __init ignore_unknown_bootoption(char *param, char *val,
const char *unused, void *arg)
{
return 0;
}
/*
__param 0x00000000c0272628 0x71c
0x00000000c0272628 __start___param = .
*(__param)
__param 0x00000000c0272628 0x14 init/main.o
bool initcall_debug;
core_param(initcall_debug, initcall_debug, bool, 0644);
__param 0x00000000c027263c 0x64 kernel/panic.o
__param 0x00000000c02726a0 0x50 kernel/workqueue.o
__param 0x00000000c02726f0 0x64 kernel/printk/printk.o
__param 0x00000000c0272754 0x28 kernel/irq/spurious.o
__param 0x00000000c027277c 0xc8 kernel/rcu/update.o
__param 0x00000000c0272844 0xa0 kernel/rcu/srcutree.o
__param 0x00000000c02728e4 0x1e0 kernel/rcu/tree.o
__param 0x00000000c0272ac4 0x14 kernel/time/sched_clock.o
__param 0x00000000c0272ad8 0x28 mm/slab_common.o
__param 0x00000000c0272b00 0x14 block/disk-events.o
__param 0x00000000c0272b14 0x28 drivers/tty/sysrq.o
__param 0x00000000c0272b3c 0x14 drivers/char/random.o
__param 0x00000000c0272b50 0x3c drivers/block/brd.o
__param 0x00000000c0272b8c 0x8c drivers/input/keyboard/atkbd.o
__param 0x00000000c0272c18 0x8c drivers/input/mouse/psmouse-base.o
__param 0x00000000c0272ca4 0x14 drivers/input/mouse/synaptics.o
__param 0x00000000c0272cb8 0x14 drivers/watchdog/watchdog_core.o
__param 0x00000000c0272ccc 0x28 drivers/watchdog/watchdog_dev.o
__param 0x00000000c0272cf4 0x14 drivers/mmc/core/core.o
__param 0x00000000c0272d08 0x14 drivers/mmc/core/block.o
__param 0x00000000c0272d1c 0x14 drivers/mmc/host/mmci.o
__param 0x00000000c0272d30 0x14 drivers/hid/hid-core.o
0x00000000c0272d44 __stop___param = .
*/
/*
0x00000000c02a60b4 __initcall2_start = .
*(.initcall2.init)
.initcall2.init
0x00000000c02a60b4 0x4 kernel/irq/irqdesc.o
.initcall2.init
0x00000000c02a60b8 0x4 mm/backing-dev.o
.initcall2.init
0x00000000c02a60bc 0x4 mm/mm_init.o
.initcall2.init
0x00000000c02a60c0 0x4 mm/page_alloc.o
.initcall2.init
0x00000000c02a60c4 0x4 drivers/amba/bus.o
.initcall2.init
0x00000000c02a60c8 0x4 drivers/tty/tty_io.o
.initcall2.init
0x00000000c02a60cc 0x4 drivers/base/core.o
.initcall2.init
0x00000000c02a60d0 0x4 drivers/base/swnode.o
.initcall2.init
0x00000000c02a60d4 0x4 drivers/base/regmap/regmap.o
.initcall2.init
0x00000000c02a60d8 0x4 drivers/i2c/i2c-core-base.o
*(.initcall2s.init)
0x00000000c02a60dc __initcall3_start = .
*(.initcall3.init)
.initcall3.init
0x00000000c02a60dc 0x4 arch/arm/kernel/setup.o
.initcall3.init
0x00000000c02a60e0 0x4 arch/arm/mm/fault.o
.initcall3.init
0x00000000c02a60e4 0x4 drivers/pinctrl/stm32/pinctrl-stm32f429.o
.initcall3.init
0x00000000c02a60e8 0x4 drivers/pinctrl/stm32/pinctrl-stm32f469.o
.initcall3.init
0x00000000c02a60ec 0x4 drivers/pinctrl/stm32/pinctrl-stm32f746.o
.initcall3.init
0x00000000c02a60f0 0x4 drivers/pinctrl/stm32/pinctrl-stm32f769.o
.initcall3.init
0x00000000c02a60f4 0x4 drivers/pinctrl/stm32/pinctrl-stm32h743.o
.initcall3.init
0x00000000c02a60f8 0x8 drivers/dma/dmaengine.o
.initcall3.init
0x00000000c02a6100 0x4 drivers/dma/stm32/stm32-dmamux.o
.initcall3.init
0x00000000c02a6104 0x4 drivers/tty/serial/serial_base_bus.o
*(.initcall3s.init)
.initcall3s.init
0x00000000c02a6108 0x4 drivers/of/platform.o
0x00000000c02a610c __initcall4_start = .
*(.initcall4.init)
.initcall4.init
0x00000000c02a610c 0x4 kernel/user.o
.initcall4.init
0x00000000c02a6110 0x4 kernel/pid.o
.initcall4.init
0x00000000c02a6114 0x4 kernel/params.o
.initcall4.init
0x00000000c02a6118 0x4 kernel/ucount.o
.initcall4.init
0x00000000c02a611c 0x4 kernel/power/poweroff.o
.initcall4.init
0x00000000c02a6120 0x8 mm/util.o
.initcall4.init
0x00000000c02a6128 0x4 mm/backing-dev.o
.initcall4.init
0x00000000c02a612c 0x4 mm/percpu.o
.initcall4.init
0x00000000c02a6130 0x8 mm/nommu.o
.initcall4.init
0x00000000c02a6138 0x4 block/bio.o
.initcall4.init
0x00000000c02a613c 0x4 block/blk-ioc.o
.initcall4.init
0x00000000c02a6140 0x4 block/blk-mq.o
.initcall4.init
0x00000000c02a6144 0x4 block/genhd.o
.initcall4.init
0x00000000c02a6148 0x4 drivers/gpio/gpio-stmpe.o
.initcall4.init
0x00000000c02a614c 0x4 drivers/leds/led-class.o
.initcall4.init
0x00000000c02a6150 0x4 drivers/dma/stm32/stm32-dma.o
.initcall4.init
0x00000000c02a6154 0x4 drivers/dma/stm32/stm32-mdma.o
.initcall4.init
0x00000000c02a6158 0x4 drivers/regulator/fixed.o
.initcall4.init
0x00000000c02a615c 0x4 drivers/char/misc.o
.initcall4.init
0x00000000c02a6160 0x4 drivers/base/topology.o
.initcall4.init
0x00000000c02a6164 0x4 drivers/mfd/stmpe-i2c.o
.initcall4.init
0x00000000c02a6168 0x4 drivers/dma-buf/dma-buf.o
.initcall4.init
0x00000000c02a616c 0x4 drivers/input/serio/serio.o
.initcall4.init
0x00000000c02a6170 0x4 drivers/input/input.o
.initcall4.init
0x00000000c02a6174 0x4 drivers/rtc/class.o
.initcall4.init
0x00000000c02a6178 0x4 drivers/mmc/core/core.o
.initcall4.init
0x00000000c02a617c 0x4 drivers/iio/industrialio-core.o
.initcall4.init
0x00000000c02a6180 0x4 drivers/nvmem/core.o
.initcall4.init
0x00000000c02a6184 0x4 lib/vsprintf.o
*(.initcall4s.init)
.initcall4s.init
0x00000000c02a6188 0x4 drivers/watchdog/watchdog_core.o
0x00000000c02a618c __initcall5_start = .
*(.initcall5.init)
.initcall5.init
0x00000000c02a618c 0x4 kernel/resource.o
.initcall5.init
0x00000000c02a6190 0x4 kernel/time/clocksource.o
.initcall5.init
0x00000000c02a6194 0x4 fs/file_table.o
.initcall5.init
0x00000000c02a6198 0x4 fs/exec.o
.initcall5.init
0x00000000c02a619c 0x4 fs/pipe.o
.initcall5.init
0x00000000c02a61a0 0x4 fs/namei.o
.initcall5.init
0x00000000c02a61a4 0x4 fs/dcache.o
.initcall5.init
0x00000000c02a61a8 0x4 fs/namespace.o
.initcall5.init
0x00000000c02a61ac 0x4 fs/anon_inodes.o
.initcall5.init
0x00000000c02a61b0 0x4 fs/locks.o
.initcall5.init
0x00000000c02a61b4 0x4 fs/drop_caches.o
.initcall5.init
0x00000000c02a61b8 0x4 fs/iomap/direct-io.o
.initcall5.init
0x00000000c02a61bc 0x4 fs/iomap/ioend.o
.initcall5.init
0x00000000c02a61c0 0x4 fs/proc/nommu.o
.initcall5.init
0x00000000c02a61c4 0x4 fs/proc/cmdline.o
.initcall5.init
0x00000000c02a61c8 0x4 fs/proc/consoles.o
.initcall5.init
0x00000000c02a61cc 0x4 fs/proc/cpuinfo.o
.initcall5.init
0x00000000c02a61d0 0x4 fs/proc/devices.o
.initcall5.init
0x00000000c02a61d4 0x4 fs/proc/interrupts.o
.initcall5.init
0x00000000c02a61d8 0x4 fs/proc/loadavg.o
.initcall5.init
0x00000000c02a61dc 0x4 fs/proc/meminfo.o
.initcall5.init
0x00000000c02a61e0 0x4 fs/proc/stat.o
.initcall5.init
0x00000000c02a61e4 0x4 fs/proc/uptime.o
.initcall5.init
0x00000000c02a61e8 0x4 fs/proc/version.o
.initcall5.init
0x00000000c02a61ec 0x4 fs/proc/softirqs.o
.initcall5.init
0x00000000c02a61f0 0x4 fs/proc/kmsg.o
.initcall5.init
0x00000000c02a61f4 0x4 fs/ramfs/inode.o
.initcall5.init
0x00000000c02a61f8 0x4 drivers/char/mem.o
*(.initcall5s.init)
0x00000000c02a61fc __initcallrootfs_start = .
*(.initcallrootfs.init)
.initcallrootfs.init
0x00000000c02a61fc 0x4 init/initramfs.o
*(.initcallrootfss.init)
0x00000000c02a6200 __initcall6_start = .
*(.initcall6.init)
.initcall6.init
0x00000000c02a6200 0x4 kernel/exec_domain.o
.initcall6.init
0x00000000c02a6204 0x4 kernel/panic.o
.initcall6.init
0x00000000c02a6208 0x4 kernel/resource.o
.initcall6.init
0x00000000c02a620c 0x4 kernel/irq/generic-chip.o
.initcall6.init
0x00000000c02a6210 0x4 kernel/time/timekeeping.o
.initcall6.init
0x00000000c02a6214 0x4 kernel/time/clocksource.o
.initcall6.init
0x00000000c02a6218 0x4 kernel/time/timer_list.o
.initcall6.init
0x00000000c02a621c 0x4 kernel/time/alarmtimer.o
.initcall6.init
0x00000000c02a6220 0x4 kernel/time/clockevents.o
.initcall6.init
0x00000000c02a6224 0x4 kernel/time/sched_clock.o
.initcall6.init
0x00000000c02a6228 0x4 kernel/kallsyms.o
.initcall6.init
0x00000000c02a622c 0x4 kernel/seccomp.o
.initcall6.init
0x00000000c02a6230 0x4 kernel/utsname_sysctl.o
.initcall6.init
0x00000000c02a6234 0x4 mm/vmscan.o
.initcall6.init
0x00000000c02a6238 0x4 mm/workingset.o
.initcall6.init
0x00000000c02a623c 0x4 fs/fcntl.o
.initcall6.init
0x00000000c02a6240 0x4 fs/filesystems.o
.initcall6.init
0x00000000c02a6244 0x4 fs/fs-writeback.o
.initcall6.init
0x00000000c02a6248 0x4 fs/mbcache.o
.initcall6.init
0x00000000c02a624c 0x4 fs/ext4/super.o
.initcall6.init
0x00000000c02a6250 0x4 fs/jbd2/journal.o
.initcall6.init
0x00000000c02a6254 0x4 block/fops.o
.initcall6.init
0x00000000c02a6258 0x4 block/genhd.o
.initcall6.init
0x00000000c02a625c 0x4 block/mq-deadline.o
.initcall6.init
0x00000000c02a6260 0x4 block/kyber-iosched.o
.initcall6.init
0x00000000c02a6264 0x4 block/bfq-iosched.o
.initcall6.init
0x00000000c02a6268 0x4 lib/crypto/blake2s.o
.initcall6.init
0x00000000c02a626c 0x4 drivers/bus/stm32_rifsc.o
.initcall6.init
0x00000000c02a6270 0x4 drivers/bus/stm32_etzpc.o
.initcall6.init
0x00000000c02a6274 0x4 drivers/bus/simple-pm-bus.o
.initcall6.init
0x00000000c02a6278 0x4 drivers/leds/leds-gpio.o
.initcall6.init
0x00000000c02a627c 0x4 drivers/leds/trigger/ledtrig-heartbeat.o
.initcall6.init
0x00000000c02a6280 0x4 drivers/clk/clk-fixed-factor.o
.initcall6.init
0x00000000c02a6284 0x4 drivers/clk/clk-fixed-rate.o
.initcall6.init
0x00000000c02a6288 0x8 drivers/clk/clk-gpio.o
.initcall6.init
0x00000000c02a6290 0x4 drivers/reset/reset-simple.o
.initcall6.init
0x00000000c02a6294 0x4 drivers/tty/n_null.o
.initcall6.init
0x00000000c02a6298 0x4 drivers/tty/sysrq.o
.initcall6.init
0x00000000c02a629c 0x4 drivers/tty/serial/stm32-usart.o
.initcall6.init
0x00000000c02a62a0 0x4 drivers/char/random.o
.initcall6.init
0x00000000c02a62a4 0x4 drivers/base/topology.o
.initcall6.init
0x00000000c02a62a8 0x4 drivers/base/cacheinfo.o
.initcall6.init
0x00000000c02a62ac 0x4 drivers/block/brd.o
.initcall6.init
0x00000000c02a62b0 0x4 drivers/mfd/stm32-timers.o
.initcall6.init
0x00000000c02a62b4 0x4 drivers/input/serio/serport.o
.initcall6.init
0x00000000c02a62b8 0x4 drivers/input/input-leds.o
.initcall6.init
0x00000000c02a62bc 0x4 drivers/input/keyboard/atkbd.o
.initcall6.init
0x00000000c02a62c0 0x4 drivers/input/mouse/psmouse-base.o
.initcall6.init
0x00000000c02a62c4 0x4 drivers/rtc/rtc-stm32.o
.initcall6.init
0x00000000c02a62c8 0x4 drivers/i2c/i2c-smbus.o
.initcall6.init
0x00000000c02a62cc 0x4 drivers/i2c/i2c-dev.o
.initcall6.init
0x00000000c02a62d0 0x4 drivers/i2c/busses/i2c-stm32f4.o
.initcall6.init
0x00000000c02a62d4 0x4 drivers/i2c/busses/i2c-stm32f7.o
.initcall6.init
0x00000000c02a62d8 0x4 drivers/watchdog/stm32_iwdg.o
.initcall6.init
0x00000000c02a62dc 0x4 drivers/mmc/core/pwrseq_simple.o
.initcall6.init
0x00000000c02a62e0 0x4 drivers/mmc/core/pwrseq_emmc.o
.initcall6.init
0x00000000c02a62e4 0x4 drivers/mmc/core/block.o
.initcall6.init
0x00000000c02a62e8 0x4 drivers/mmc/host/mmci.o
.initcall6.init
0x00000000c02a62ec 0x4 drivers/hid/hid-core.o
.initcall6.init
0x00000000c02a62f0 0x4 drivers/hid/hid-generic.o
.initcall6.init
0x00000000c02a62f4 0x4 drivers/iio/adc/stm32-adc-core.o
.initcall6.init
0x00000000c02a62f8 0x4 drivers/iio/adc/stm32-adc.o
.initcall6.init
0x00000000c02a62fc 0x4 drivers/iio/trigger/stm32-timer-trigger.o
*(.initcall6s.init)
0x00000000c02a6300 __initcall7_start = .
*(.initcall7.init)
.initcall7.init
0x00000000c02a6300 0x4 init/do_mounts_initrd.o
.initcall7.init
0x00000000c02a6304 0x4 arch/arm/kernel/setup.o
.initcall7.init
0x00000000c02a6308 0x8 kernel/panic.o
.initcall7.init
0x00000000c02a6310 0x8 kernel/exit.o
.initcall7.init
0x00000000c02a6318 0x4 kernel/params.o
.initcall7.init
0x00000000c02a631c 0x4 kernel/reboot.o
.initcall7.init
0x00000000c02a6320 0x4 kernel/sched/core.o
.initcall7.init
0x00000000c02a6324 0x4 kernel/sched/fair.o
.initcall7.init
0x00000000c02a6328 0x8 kernel/sched/build_policy.o
.initcall7.init
0x00000000c02a6330 0x4 kernel/sched/build_utility.o
.initcall7.init
0x00000000c02a6334 0x4 kernel/printk/printk.o
.initcall7.init
0x00000000c02a6338 0x4 kernel/rcu/tree.o
.initcall7.init
0x00000000c02a633c 0x4 mm/slub.o
.initcall7.init
0x00000000c02a6340 0x4 block/blk-timeout.o
.initcall7.init
0x00000000c02a6344 0x4 drivers/base/core.o
.initcall7.init
0x00000000c02a6348 0x4 drivers/base/dd.o
.initcall7.init
0x00000000c02a634c 0x4 drivers/input/keyboard/gpio_keys.o
.initcall7.init
0x00000000c02a6350 0x4 drivers/of/fdt.o
*(.initcall7s.init)
.initcall7s.init
0x00000000c02a6354 0x4 drivers/amba/bus.o
.initcall7s.init
0x00000000c02a6358 0x4 drivers/clk/clk.o
.initcall7s.init
0x00000000c02a635c 0x4 drivers/regulator/core.o
.initcall7s.init
0x00000000c02a6360 0x4 drivers/of/platform.o
0x00000000c02a6364 __initcall_end = .
*/
/*
* 函数 do_initcall_level
* @level: 要执行的初始化级别的整数索引 (0-7).
* @command_line: 指向内核启动命令行参数字符串的指针.
*/
static void __init do_initcall_level(int level, char *command_line)
{
/*
* 定义一个 initcall_entry_t 指针 fn, 用作循环迭代器.
*/
initcall_entry_t *fn;
/*
* 调用 parse_args 函数来解析内核启动命令行.
* 它会查找并处理与当前初始化级别相关的内核参数.
* initcall_level_names[level]: 用于日志的级别名称.
* command_line: 要解析的字符串.
* __start___param, __stop___param: 由链接器定义的符号, 标志着所有内核参数(__param)结构体的内存区域.
* level, level: 只处理与当前级别匹配的参数.
* NULL, ignore_unknown_bootoption: 处理未知参数的回调函数.
*/
parse_args(initcall_level_names[level],
command_line, __start___param,
__stop___param - __start___param,
level, level,
NULL, ignore_unknown_bootoption);
/*
* 如果内核跟踪(tracing)被启用, 这个函数会记录一条表示进入新初始化级别的跟踪事件.
*/
do_trace_initcall_level(initcall_level_names[level]);
/*
* 这是执行所有初始化函数的核心循环.
* 循环的起始点是当前级别的起始地址 (initcall_levels[level]).
* 循环的结束点是下一个级别的起始地址 (initcall_levels[level+1]).
* 循环会遍历这两个地址之间的所有函数指针.
*/
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
/*
* 对每一个找到的函数指针, 调用 do_one_initcall 来执行它.
* initcall_from_entry 宏用于从条目中提取出真正的函数指针.
* do_one_initcall 负责实际调用函数, 并处理日志记录、错误检查和耗时统计等工作.
initcall_from_entry() return *entry;
*/
do_one_initcall(initcall_from_entry(fn));
}
init/initramfs.c
reserve_initrd_mem 保留initrd内存
void __init reserve_initrd_mem(void)
{
phys_addr_t start;
unsigned long size;
/* 忽略在设备树解析期间计算的 virtul 地址 */
initrd_start = initrd_end = 0;
if (!phys_initrd_size)
return;
/*
* 根据 free_initrd_mem() 将内存区域舍入到页面边界这允许我们检测与 initrd 重叠的页面是否正在使用中,但更重要的是,保留整个页面集,因为我们不希望这些页面分配给其他目的。
*/
start = round_down(phys_initrd_start, PAGE_SIZE);
size = phys_initrd_size + (phys_initrd_start - start);
size = round_up(size, PAGE_SIZE);
if (!memblock_is_region_memory(start, size)) {
pr_err("INITRD: 0x%08llx+0x%08lx is not a memory region",
(u64)start, size);
goto disable;
}
if (memblock_is_region_reserved(start, size)) {
pr_err("INITRD: 0x%08llx+0x%08lx overlaps in-use memory region\n",
(u64)start, size);
goto disable;
}
memblock_reserve(start, size);
/* 现在将initrd转换为虚拟地址 */
initrd_start = (unsigned long)__va(phys_initrd_start);
initrd_end = initrd_start + phys_initrd_size;
initrd_below_start_ok = 1;
return;
disable:
pr_cont(" - disabling initrd\n");
initrd_start = 0;
initrd_end = 0;
}
init/calibrate.c
calibrate_delay 校准延迟
void calibrate_delay(void)
{
unsigned long lpj;
static bool printed;
int this_cpu = smp_processor_id();
if (per_cpu(cpu_loops_per_jiffy, this_cpu)) {
lpj = per_cpu(cpu_loops_per_jiffy, this_cpu);
if (!printed)
pr_info("Calibrating delay loop (skipped) "
"already calibrated this CPU");
} else if (preset_lpj) {
lpj = preset_lpj;
if (!printed)
pr_info("Calibrating delay loop (skipped) "
"preset value.. ");
} else if ((!printed) && lpj_fine) {
/* arch/arm/lib/delay.c
lpj_fine = timer->freq / HZ;
*/
lpj = lpj_fine;
pr_info("Calibrating delay loop (skipped), "
"value calculated using timer frequency.. ");
} else if ((lpj = calibrate_delay_is_known())) {
;
} else if ((lpj = calibrate_delay_direct()) != 0) {
if (!printed)
pr_info("Calibrating delay using timer "
"specific routine.. ");
} else {
if (!printed)
pr_info("Calibrating delay loop... ");
lpj = calibrate_delay_converge();
}
per_cpu(cpu_loops_per_jiffy, this_cpu) = lpj;
if (!printed)
pr_cont("%lu.%02lu BogoMIPS (lpj=%lu)\n",
lpj/(500000/HZ),
(lpj/(5000/HZ)) % 100, lpj);
loops_per_jiffy = lpj;
printed = true;
calibration_delay_done();
}