在分析之前一定要先编译!
因为有一些关键的文件是要经过编译后才有的!比如**./arch/arm/kernel/vmlinux.lds**链接文件。
1. 找到链接文件
链接文件可以在Makefile中找到:
2. 根据链接文件找到入口
OUTPUT_ARCH(arm)
ENTRY(stext)//入口在stext
jiffies = jiffies_64;
SECTIONS
{
/DISCARD/ : {
*(.ARM.exidx.exit.text) *(.ARM.extab.exit.text) *(.ARM.exidx.text.exit) *(.ARM.extab.text.exit) *(.exitcall.exit) *(.discard) *(.discard.*) *(.modinfo) *(.gnu.version*)
}
. = (((0xC0000000))) + 0x00008000;
.head.text : {
_text = .;
KEEP(*(.head.text))
}
. = ALIGN(1<<20);
.text : {
_stext = .;
. = ALIGN(8); __idmap_text_start = .; *(.idmap.text) __idmap_text_end = .; __entry_text_start = .; *(.entry.text) __entry_text_end = .; . = ALIGN(8); __irqentry_text_start = .; *(.irqentry.text) __irqentry_text_end = .; . = ALIGN(8); __softirqentry_text_start = .; *(.softirqentry.text) __softirqentry_text_end = .; . = ALIGN(8); *(.text.hot .text.hot.*) *(.text .text.fixup) *(.text.unlikely .text.unlikely.*) *(.text.unknown .text.unknown.*) . = ALIGN(8); __noinstr_text_start = .; *(.noinstr.text) __noinstr_text_end = .; *(.text..refcount) *(.ref.text) *(.text.asan.* .text.tsan.*) . = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .; . = ALIGN(8); __cpuidle_text_start = .; *(.cpuidle.text) __cpuidle_text_end = .; . = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .; . = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .; *(.gnu.warning) *(.glue_7) *(.glue_7t) *(.vfp11_veneer) *(.v4_bx) . = ALIGN(4); *(.got) . = ALIGN(4); __proc_info_begin = .; *(.proc.info.init) __proc_info_end = .;
}
. = ALIGN(1<<20);
_etext = .;
.....
.....
3. 找到入口函数(grep -r “stext”)查找
一、head.S
发现在arch/arm/kernel/head.S中。
80 ENTRY(stext)
......
91 @ ensure svc mode and all interrupts masked
92 safe_svcmode_maskall r9 //进入SCV,并且关闭IRQ与FIQ
93
94 mrc p15, 0, r9, c0, c0 @ get processor id //获得处理器id
95 bl __lookup_processor_type @ r5=procinfo r9=cpuid//调用函数__lookup_processor_type 检查当前系统是否支持此 CPU如果支持就获取 procinfo 信 息
96 movs r10, r5 @ invalid processor (r5=0)?
97 THUMB( it eq ) @ force fixup-able long branch encoding
98 beq __error_p @ yes, error 'p'
99
......
107
108 #ifndef CONFIG_XIP_KERNEL
......
113 #else
114 ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case
115 #endif
116
117 /*
118 * r1 = machine no, r2 = atags or dtb,
119 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
120 */
121 bl __vet_atags //调用函数__vet_atags 验证 atags 或设备树(dtb)的合法性
......
128 bl __create_page_tables//创建页表
129
130 /*
131 * The following calls CPU specific code in a position independent
132 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of
133 * xxx_proc_info structure selected by __lookup_processor_type
134 * above. On return, the CPU will be ready for the MMU to be
135 * turned on, and r0 will hold the CPU control register value.
136 */
137 ldr r13, =__mmap_switched @ address to jump to after//,__mmap_switched 最终会调用 start_kernel 函数。
138 @ mmu has been enabled
139 adr lr, BSYM(1f) @ return (PIC) address
140 mov r8, r4 @ set TTBR1 to swapper_pg_dir
141 ldr r12, [r10, #PROCINFO_INITFUNC]
142 add r12, r12, r10
143 ret r12
144 1: b __enable_mmu //又会调用__mmap_switched
145 ENDPROC(stext)
__enable_mmu
->__mmap_switched
->start_kernel
81 __mmap_switched:
82 adr r3, __mmap_switched_data
83
84 ldmia r3!, {r4, r5, r6, r7}
85 cmp r4, r5 @ Copy data segment if needed
86 1: cmpne r5, r6
87 ldrne fp, [r4], #4
88 strne fp, [r5], #4
89 bne 1b
90
91 mov fp, #0 @ Clear BSS (and zero fp)
92 1: cmp r6, r7
93 strcc fp, [r6],#4
94 bcc 1b
95
96 ARM( ldmia r3, {r4, r5, r6, r7, sp})
97 THUMB( ldmia r3, {r4, r5, r6, r7} )
98 THUMB( ldr sp, [r3, #16] )
99 str r9, [r4] @ Save processor ID
100 str r1, [r5] @ Save machine type
101 str r2, [r6] @ Save atags pointer
102 cmp r7, #0
103 strne r0, [r7] @ Save control register values
104 b start_kernel//进入start_kernel
105 ENDPROC(__mmap_switched)
二、start_kernel(在init/main.c中)
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
lockdep_init();
/* lockdep 是死锁检测模块,此函数会初始化
* 两个 hash 表。此函数要求尽可能早的执行!
*/
set_task_stack_end_magic(&init_task);
/* 设置任务栈结束魔术数,
*用于栈溢出检测
*/
smp_setup_processor_id();
/* 跟 SMP 有关(多核处理器),设置处理器 ID。
* 有很多资料说 ARM 架构下此函数为空函数,那是因
* 为他们用的老版本 Linux,而那时候 ARM 还没有多
* 核处理器。
*/
debug_objects_early_init();
/* 做一些和 debug 有关的初始化 */
boot_init_stack_canary();
/* 栈溢出检测初始化 */
cgroup_init_early();
/* cgroup 初始化,cgroup 用于控制 Linux 系统资源*/
local_irq_disable();
/* 关闭当前 CPU 中断 */
early_boot_irqs_disabled = true;
page_address_init();
/* 页地址相关的初始化 */
pr_notice("%s", linux_banner);
/* 打印 Linux 版本号、编译时间等信息 */
setup_arch(&command_line);
/* 架构相关的初始化,此函数会解析传递进来的
* ATAGS 或者设备树(DTB)文件。会根据设备树里面
* 的 model 和 compatible 这两个属性值来查找
* Linux 是否支持这个单板。此函数也会获取设备树
* 中 chosen 节点下的 bootargs 属性值来得到命令
* 行参数,也就是 uboot 中的 bootargs 环境变量的
* 值,获取到的命令行参数会保存到
*command_line 中。
*/
mm_init_cpumask(&init_mm);
/* 看名字,应该是和内存有关的初始化 */
setup_command_line(command_line);
/* 好像是存储命令行参数 */
setup_nr_cpu_ids();
/* 如果只是 SMP(多核 CPU)的话,此函数用于获取
* CPU 核心数量,CPU 数量保存在变量
* nr_cpu_ids 中。
*/
setup_per_cpu_areas();
/* 在 SMP 系统中有用,设置每个 CPU 的 per-cpu 数据 */
smp_prepare_boot_cpu();
build_all_zonelists(NULL, NULL);
/* 建立系统内存页区(zone)链表 */
page_alloc_init();
/* 处理用于热插拔 CPU 的页 */
/*
* 中断关闭期间做一些重要的操作,然后打开中断
*/
boot_cpu_init();
/* 跟 CPU 有关的初始化 */
page_address_init();
/* 页地址相关的初始化 */
pr_notice("%s", linux_banner);
/* 打印 Linux 版本号、编译时间等信息 */
setup_arch(&command_line);
/* 架构相关的初始化,此函数会解析传递进来的
* ATAGS 或者设备树(DTB)文件。会根据设备树里面
* 的 model 和 compatible 这两个属性值来查找
* Linux 是否支持这个单板。此函数也会获取设备树
* 中 chosen 节点下的 bootargs 属性值来得到命令
* 行参数,也就是 uboot 中的 bootargs 环境变量的
* 值,获取到的命令行参数会保存到
*command_line 中。
*/
mm_init_cpumask(&init_mm);
/* 看名字,应该是和内存有关的初始化 */
setup_command_line(command_line);
/* 好像是存储命令行参数 */
setup_nr_cpu_ids();
/* 如果只是 SMP(多核 CPU)的话,此函数用于获取
* CPU 核心数量,CPU 数量保存在变量
* nr_cpu_ids 中。
*/
setup_per_cpu_areas();
/* 在 SMP 系统中有用,设置每个 CPU 的 per-cpu 数据 */
smp_prepare_boot_cpu();
build_all_zonelists(NULL, NULL);
/* 建立系统内存页区(zone)链表 */
page_alloc_init();
/* 处理用于热插拔 CPU 的页 */
/* 打印命令行信息 */
pr_notice("Kernel command line: %s\n", boot_command_line);
parse_early_param();
/* 解析命令行中的 console 参数 */
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, &unknown_bootoption);
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
set_init_arg);
jump_label_init();
setup_log_buf(0);
/* 设置 log 使用的缓冲区*/
pidhash_init();
/* 构建 PID 哈希表,Linux 中每个进程都有一个 ID,
* 这个 ID 叫做 PID。通过构建哈希表可以快速搜索进程
* 信息结构体。
*/
vfs_caches_init_early();
/* 预先初始化 vfs(虚拟文件系统)的目录项和
* 索引节点缓存
*/
sort_main_extable();
/* 定义内核异常列表 */
trap_init();
/* 完成对系统保留中断向量的初始化 */
mm_init();
/* 内存管理初始化 */
sched_init();
/* 初始化调度器,主要是初始化一些结构体 */
preempt_disable();
/* 关闭优先级抢占 */
if (WARN(!irqs_disabled(),
/* 检查中断是否关闭,如果没有的话就关闭中断 */
"Interrupts were enabled *very* early, fixing it\n"))
local_irq_disable();
idr_init_cache();
/* IDR 初始化,IDR 是 Linux 内核的整数管理机
* 制,也就是将一个整数 ID 与一个指针关联起来。
*/
rcu_init();
/* 初始化 RCU,RCU 全称为 Read Copy Update(读-拷贝修改) */
trace_init();
/* 跟踪调试相关初始化 */
context_tracking_init();
radix_tree_init();
/* 基数树相关数据结构初始化 */
early_irq_init();
/* 初始中断相关初始化,主要是注册 irq_desc 结构体变
* 量,因为 Linux 内核使用 irq_desc 来描述一个中断。
*/
init_IRQ();
/* 中断初始化 */
tick_init();
/* tick 初始化 */
rcu_init_nohz();
init_timers();
/* 初始化定时器 */
hrtimers_init();
/* 初始化高精度定时器 */
softirq_init();
/* 软中断初始化 */
timekeeping_init();
time_init();
/* 初始化系统时间 */
sched_clock_postinit();
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();
/* slab 初始化,slab 是 Linux 内存分配器 */
console_init();
/* 初始化控制台,之前 printk 打印的信息都存放
* 缓冲区中,并没有打印出来。只有调用此函数
* 初始化控制台以后才能在控制台上打印信息。
*/
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later,
panic_param);
lockdep_info();
/* 如果定义了宏 CONFIG_LOCKDEP,那么此函数打印一些信息。*/
locking_selftest()
/* 锁自测 */
......
page_ext_init();
debug_objects_mem_init();
kmemleak_init();
/* kmemleak 初始化,kmemleak 用于检查内存泄漏 */
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
/* 测定 BogoMIPS 值,可以通过 BogoMIPS 来判断 CPU 的性能
* BogoMIPS 设置越大,说明 CPU 性能越好。
*/
pidmap_init();
/* PID 位图初始化 */
anon_vma_init();
/* 生成 anon_vma slab 缓存 */
acpi_early_init();
......
thread_info_cache_init();
cred_init();
/* 为对象的每个用于赋予资格(凭证) */
fork_init();
/* 初始化一些结构体以使用 fork 函数 */
proc_caches_init();
/* 给各种资源管理结构分配缓存 */
buffer_init();
/* 初始化缓冲缓存 */
key_init();
/* 初始化密钥 */
security_init();
/* 安全相关初始化 */
dbg_late_init();
vfs_caches_init(totalram_pages);
/* 为 VFS 创建缓存 */
signals_init();
/* 初始化信号 */
page_writeback_init();
/* 页回写初始化 */
proc_root_init();
/* 注册并挂载 proc 文件系统 */
nsfs_init();
cpuset_init();
/* 初始化 cpuset,cpuset 是将 CPU 和内存资源以逻辑性
* 和层次性集成的一种机制,是 cgroup 使用的子系统之一
*/
cgroup_init();
/* 初始化 cgroup */
taskstats_init_early();
/* 进程状态初始化 */
delayacct_init();
check_bugs();
/* 检查写缓冲一致性 */
acpi_subsystem_init();
sfi_init_late();
if (efi_enabled(EFI_RUNTIME_SERVICES))
{
efi_late_init();
efi_free_boot_services();
}
ftrace_init();
rest_init();
/* rest_init 函数 */
}
start_kernel主要做的就是各种初始化。
三、rest_init(init/main.c)
noinline void __ref rest_init(void)
{
struct task_struct *tsk;
int pid;
rcu_scheduler_starting();//开启RCU调度
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
pid = user_mode_thread(kernel_init, NULL, CLONE_FS);//创建进程号为1的init内核进程
/*
* Pin init on the boot CPU. Task migration is not properly working
* until sched_init_smp() has been run. It will set the allowed
* CPUs for init to the non isolated CPUs.
*/
rcu_read_lock();
tsk = find_task_by_pid_ns(pid, &init_pid_ns);
tsk->flags |= PF_NO_SETAFFINITY;
set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
rcu_read_unlock();
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);//创建 kthreadd 内核进程,此内核进程的 PID 为 2。kthread进程负责所有内核进程的调度和管理。
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
/*
* Enable might_sleep() and smp_processor_id() checks.
* They cannot be enabled earlier because with CONFIG_PREEMPTION=y
* kernel_thread() would trigger might_sleep() splats. With
* CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
* already, but it's stuck on the kthreadd_done completion.
*/
system_state = SYSTEM_SCHEDULING;
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);//idle进程为rest_init进程也就是主进程,进程号为0
}
创建了init进程与kthread进程和idle进程。
四、init进程
对应kernel_init函数在init/main.c中
static int __ref kernel_init(void *unused)
{
int ret;
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
kernel_init_freeable();//做一些重要的初始化工作
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
system_state = SYSTEM_FREEING_INITMEM;
kprobe_free_init_mem();
ftrace_free_init_mem();
kgdb_free_init_mem();
exit_boot_config();
free_initmem();
mark_readonly();
/*
* Kernel mappings are now finalized - update the userspace page-table
* to finalize PTI.
*/
pti_finalize();
system_state = SYSTEM_RUNNING;
numa_default_policy();
rcu_end_inkernel_boot();
do_sysctl_args();
//获取在 bootargs 中使用“rdinit=xxx”即可,xxx 为具体的 init 程序名字。
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
//获取 bootargs 中使用“init=xxxx”就可以了,比如“init=/linuxrc”表示根文件系统中的 linuxrc 就是要执行的用户空间 init 程序。
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
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都没有找到,就分别找下面的四个
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("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/admin-guide/init.rst for guidance.");
}
kernel_init_freeable:
static noinline void __init kernel_init_freeable(void)
{
/* Now the scheduler is fully set up and can do blocking allocations */
gfp_allowed_mask = __GFP_BITS_MASK;
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_MEMORY]);
cad_pid = get_pid(task_pid(current));
smp_prepare_cpus(setup_max_cpus);
workqueue_init();
init_mm_internals();
rcu_init_tasks_generic();
do_pre_smp_initcalls();
lockup_detector_init();
smp_init();
sched_init_smp();
padata_init();
page_alloc_init_late();
/* Initialize page ext after all struct pages are initialized. */
page_ext_init();
do_basic_setup();//用于完成 Linux 下设备驱动初始化工作!非常重要。do_basic_setup 会调用 driver_init 函数完成 Linux 下驱动模型子系统的初始化。
kunit_run_all_tests();
wait_for_initramfs();
console_on_rootfs();//打开控制台
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
if (init_eaccess(ramdisk_execute_command) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
/*
* Ok, we have completed the initial bootup, and
* we're essentially up and running. Get rid of the
* initmem segments and start the user-mode stuff..
*
* rootfs is available now, try loading the public keys
* and default modules
*/
integrity_load_keys();
}
console_on_rootfs:
void __init console_on_rootfs(void)
{
struct file *file = filp_open("/dev/console", O_RDWR, 0);//控制台由bootargs中传递的参数控制
if (IS_ERR(file)) {
pr_err("Warning: unable to open an initial console.\n");
return;
}
init_dup(file);//1
init_dup(file);//2
init_dup(file);//3
fput(file);
}
一共有4个?
总结kernel_init:(init进程(内核态))
1.调用kernel_init_freeable做一些初始化工作
->do_basic_setup 设备驱动初始化
->console_on_rootfs打开标准输入输出和错误
尝试获取init进程(用户态)
2.尝试获取execute_command = bootargs 中使用“rdinit=xxx”
3.尝试获取execute_command = bootargs 中使用“init=/linuxrc”
4.如果以上都没有依次获取:
"/sbin/init","/etc/init“,"/bin/init","/bin/sh"
ct file *file = filp_open(“/dev/console”, O_RDWR, 0);//控制台由bootargs中传递的参数控制
if (IS_ERR(file)) {
pr_err("Warning: unable to open an initial console.\n");
return;
}
init_dup(file);//1
init_dup(file);//2
init_dup(file);//3
fput(file);
}
一共有4个?
#### 总结kernel_init:(init进程(内核态))
1.调用kernel_init_freeable做一些初始化工作
->do_basic_setup 设备驱动初始化
->console_on_rootfs打开标准输入输出和错误
##### 尝试获取init进程(用户态)
2.尝试获取execute_command = bootargs 中使用“rdinit=xxx”
3.尝试获取execute_command = bootargs 中使用“init=/linuxrc”
4.如果以上都没有依次获取:
“/sbin/init”,“/etc/init“,”/bin/init",“/bin/sh”