以为imx6q Linux4.1.15为切入点,理解分析arm架构下 的Linux内核。发帖的目的主要是为了记录自己的学习过程,如果有错误,尽管可以指出,会虚心接受并改正。
【1】内核源代码裁剪,目的是为了分析源码,也可以不进行这一步,直接跳到第二步进行
》 删除除了arm架构以外的其他架构代码,避免在代码跟踪过程中发生混乱;
linux-4.1.15/arch目录下只保留 arm/ 和Kconfig文件
》 删除除了imx6q以外的其他板级代码,只保留了mach-imx板级源码包
》删除除了imx6q以外的dts文件,为了便于分析,只保留一套合适的dts配置文件
linux-4.1.15/arch/arm/boot/dts目录下保留如下文件:
imx6q-c-sabresd-lvds.dts
imx6q-c-sabresd.dts
imx6qdl-sabresd.dtsi
imx6q.dtsi
imx6qdl.dtsi
skeleton.dtsi
》删除内核多余的配置文件
linux-4.1.15/arch/arm/configs 下只保留imx_v7_defconfig
如果这个时候,想编译内核的话,则需要提示情况修改Kconfig文件
【2】建立source insight工程
注意要修改DoucumentType,添加.S、 Makefile 、 Kconfig的支持
【3】下面就可以开始我们的内核源代码之旅了
内核的汇编部分从arch/arm/kernel/head.S开始到arch/arm/kernel/head-common.S中的
从上图中的b start_kernel跳转到init/main.c中的start_kernel()函数。关于汇编代码的分析计划放在整个分析过程的最后进行。首先从架构无关的C语言源码开始进行分析。即从init/main.c中的
asmlinkage __visible void __init start_kernel(void) 函数开始整个学习和理解过程。
【4】
asmlinkage __visible void __init start_kernel(void)函数内容,删除了与arm无关的宏定义选项
asmlinkage __visible void __init start_kernel(void)
{
char *command_line; //指针,指向内核启动参数
char *after_dashes;
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
/*根据注释来看,初始化整个系统的锁链表,具体作用在后续分析中查看是否有说明
classhash_table
chainhash_table
*/
set_task_stack_end_magic(&init_task);
/*
初始化一个init_task的栈,init_task是内核中的一个特殊进程,pid=0,又称为swapper进程或者idle任务,是所有进程和线程的祖宗。
init_task在init/init_task.c中通过静态方式分配创建。Linux从bootloader启动开始一直到目前为止是没有进程概念的,而init_task则将汇编部分一直到start_kernel的执行纳入到自己的进程上下文件中,直到rest_init()产生真正的进程。当pid=1的init进程产生之后,init_task就会完成使命,将自己置于idle状态。
*/
smp_setup_processor_id();
/*
字面理解,设置多处理器的处理器ID
内核启动过程中的打印信息“Booting Linux on physical CPU”,就是在该函数中执行的
底层需要调用汇编语言,从处理器的相关寄存器中读取处理器信息
/
debug_objects_early_init();
/内核调试模块初始化/
/
* Set up the the initial canary ASAP:
/
boot_init_stack_canary();
/
初始化canary ,用于防止栈溢出攻击。
该函数中使用get_random_bytes获取一个内核随机数,赋值给canary
一般情况下,防止栈缓冲一处的方法有两种:
一是在发生栈缓冲溢出是进行检测,阻止恶意代码更改指令指针
二是不直接检测栈缓冲溢出的情况下,预防恶意代码的供给
这里使用第一种方法,在程序启动的时候,canary保存到函数返回地址之前,栈缓冲溢出攻击
从内存地位向高位覆写内存,在覆写函数返回地址之前,就已经将canary覆写。因此在使用函数的返回地址之前,检查canary的值,就可以确认是否发生了栈缓冲溢出的共计。
/
cgroup_init_early();
/
cgroup进程行为控制的相关初始化
/
local_irq_disable();
early_boot_irqs_disabled = true;
/
关闭系统中断,完成相关设置后,在打开系统中断
/
/
• Interrupts are still disabled. Do necessary setups, then
• enable them
/
boot_cpu_init();
/激活启动CPU/
page_address_init();
/
高端内存的初始化
/
pr_notice("%s", linux_banner);
/
打印内核信息:如我手里开发板上的打印信息
[ 0.000000] Linux version 4.1.15 (root@VM) (gcc version 5.3.0 (GCC) ) #34 SMP PREEMPT Wed Oct 24 15:40:20 CST 2018
*/
setup_arch(&command_line);
/相关架构的设置和初始化函数,这个后面要单独分析这个函数/
mm_init_cpumask(&init_mm);
/*
初始化init_mm结构体,清除CPU屏蔽位
/
setup_command_line(command_line);
/
保存、备份内核启动参数
/
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); / arch-specific boot-cpu hooks /
/多处理器的相关处理,需要时在回过头来研究这里的实现,例如cpu的架构、Machine Model等信息/
build_all_zonelists(NULL, NULL);
page_alloc_init();
/ 内存 页表分配等初始化,如那些内存不使用,内存分配策略等*/
pr_notice(“Kernel command line: %s\n”, boot_command_line);
/*
打印内核启动信息,例如我的开发板内核命令行打印信息如下:
Kernel command line: console=ttymxc0,115200 root=/dev/mmcblk3p2 rootwait rw
/
parse_early_param();
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();
/内核参数解析功能初始化,并解析内核的启动参数/
/• These use large bootmem allocations and must precede
• kmem_cache_init()
*/setup_log_buf(0);
/*
分配记录启动信息的缓冲区,使用bootmem
/
pidhash_init();
/
初始化进程pid的哈希列表,有四个列表
*/
vfs_caches_init_early();
/*虚拟文件系统的缓存初始化
Dentry cache hash table entries: 131072 (order: 7, 524288 bytes)
Inode-cache hash table entries: 65536 (order: 6, 262144 bytes)
dentry_hashtable和inode_hashtable
/
sort_main_extable();
/
对内核的异常向量表进行排序,
/
trap_init();
/对内核陷入异常初始化,在arm中这个函数为空,没有实际的作用/
mm_init();
/
初始化内核中的内存分配器,打印的内存信息来自这里,因为函数较多,后续专门讨论
page_ext_init_flatmem();
mem_init();
kmem_cache_init();
percpu_init_late();
pgtable_init();
vmalloc_init();
ioremap_huge_init();
/
/• Set up the scheduler prior starting any interrupts (such as the
• timer interrupt). Full topology setup happens at smp_init()
• time - but meanwhile we still have a functioning scheduler.
/
sched_init(); 初始化调度器
/• Disable preemption - early bootup scheduling is extremely
• fragile until we cpu_idle() for the first time.
/
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初始化
/
/ trace_printk() and trace points may be used after this */
trace_init();
context_tracking_init();
radix_tree_init();/* init some links before init_ISA_irqs() */
early_irq_init(); //中断初始化,NR_IRQS:16 nr_irqs:16 16
init_IRQ();
/*A9架构相关中断初始化,后面要重点研究这个函数
[ 0.000000] L2C-310 erratum 769419 enabled
[ 0.000000] L2C-310 enabling early BRESP for Cortex-A9
[ 0.000000] L2C-310 full line of zeros enabled for Cortex-A9
[ 0.000000] L2C-310 ID prefetch enabled, offset 16 lines
[ 0.000000] L2C-310 dynamic clock gating enabled, standby mode enabled
[ 0.000000] L2C-310 cache controller enabled, 16 ways, 1024 kB
[ 0.000000] L2C-310: CACHE_ID 0x410000c7, AUX_CTRL 0x76470001
*/
tick_init();rcu_init_nohz();
init_timers();
/*
初始化cpu时钟,
打开软中断
*/
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内核分配机制初始化
/• HACK ALERT! This is early. We’re enabling the console before
• we’ve done PCI setups etc, and console_init() must be aware of
• this. But we do want output early, in case something goes wrong.
/
console_init();
/
控制台初始化,在没有初始化该函数之前,所有的打印信息均存储在缓冲区,
当完成控制台初始化之后,一次性将缓冲区全部打印。
如何映射到串口设备的
*/
if (panic_later)
panic(“Too many boot %s vars at `%s’”, panic_later,
panic_param);lockdep_info();
/*
• Need to run this when irqs are enabled, because it wants
• to self-test [hard/soft]-irqs on/off lock inversion bugs
• too:
*/
locking_selftest();#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
page_ext_init();
debug_objects_mem_init();
kmemleak_init();
//初始化内存泄漏机制的检测
setup_per_cpu_pageset();
//设置每个CPU的页集合
numa_policy_init();
//内存分配一致性检查机制的初始化
if (late_time_init)
late_time_init();
sched_clock_init();
//调度机制的时钟初始化
calibrate_delay();
//cpu延时校正
pidmap_init();
//进程ID 分配映射初始化
anon_vma_init();
acpi_early_init();
//高级配置和电源管理接口初始化
thread_info_cache_init();
//分配线程缓存信息
cred_init();
//分配缓冲区存储进程的credentials信息
fork_init();
//fork机制初始化
proc_caches_init();
buffer_init();
//文件系统缓存机制初始化
key_init();
//内核秘钥系统初始化
security_init();
//内核的安全机制初始化
dbg_late_init();
//内核调试机制初始化
vfs_caches_init(totalram_pages);
//虚拟文件系统的缓存初始化
signals_init();
//信号机制初始化
/* rootfs populating might need page-writeback */
page_writeback_init();
//页回写机制初始化
proc_root_init();
//proc文件系统初始化
nsfs_init();
cpuset_init();
//cpuset初始化
cgroup_init();
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();
/* Do the rest non-__init'ed, we're now alive */
//剩余初始化函数,这个函数需要后续分析
rest_init();
}
以上为start_kernel函数的基本初始化流程,其中有以下几个初始化过程需要重点理解和分析
【1】void __init setup_arch(char **cmdline_p)
【2】static noinline void __init_refok rest_init(void)
【3】void __init init_IRQ(void)