以为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中的

Linux 内核mutex_destroy_缓存

从上图中的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)