NXP

Linux4.1.15 内核分析

2019-07-12 12:25发布

以为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 ”, 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 ”))
      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 ”);
    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. ”,
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)