Chinaunix首页 | 论坛 | 博客
  • 博客访问: 353636
  • 博文数量: 570
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2015-11-17 10:38
文章分类

全部博文(570)

文章存档

2015年(570)

我的朋友

分类: LINUX

2015-11-17 15:17:42


  1. 一、概述
  2. 本文针对arm linux的启动过程。以后陆续会更新start_kernel()中每个函数做的事情,这样就清楚linux启动过程中都做了哪些工作。本文及以后的文章代码均采用目前linux最新版本V3.17.
  3. 为了启动ARM Linux,你需要在内核之前运行一个引导程序(Boot loader).Boot loader的作用是初始化各种设备,调用Linux内核并给内核传递相关的消息。
  4. 依赖于CPU体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。
  5. 1、Stage1
  6. start.S代码结构 u-boot的stage1代码通常放在start.S文件中,用汇编语言写成,其主要代码部分如下
  7. (1)定义入口:该工作通过修改连接器脚本来完成。
  8. (2)设置异常向量(Exception Vector)。
  9. (3)设置CPU的速度、时钟频率及终端控制寄存器。
  10. (4)初始化内存控制器。
  11. (5)将ROM中的程序(即uboot的stage2的代码)复制到RAM中。
  12. (6)初始化堆栈。
  13. (7)转到RAM中执行stage2,该工作可使用指令ldr pc来完成。
  14. 2、Stage2
  15.  C语言代码部分 lib_arm/board.c中的start arm boot是C语言开始的函数也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,该函数只要完成如下操作:
  16. (1)调用一系列的初始化函数。
  17. (2)初始化Flash设备。
  18. (3)初始化系统内存分配函数。
  19. (4)如果目标系统拥有NAND设备,则初始化NAND设备。
  20. (5)如果目标系统有显示设备,则初始化该类设备。
  21. (6)初始化相关网络设备,填写IP、MAC地址等。
  22. (7)将kernl和跟文件系统映射从flash读到ram中。
  23. (8)设定内核启动参数和调用内核。

  24. 二、Linux内核入口
  25. Linux 非压缩内核的入口位于文件/arch/arm/kernel/head.S 中的stext段。该段的基地址就是压缩内核解压后的跳转地址。如果系统中加载的内核是非压缩的 Image,那么bootloader将内核从 Flash中拷贝到 RAM 后将直接跳到该地址处,从而启动 Linux 内核。
  26. stext函数定义在arch/arm/kernel/head.S,它的功能是获取处理器类型和机器类型信息,并创建临时的页表,然后开启MMU功能,并跳进第一个C语言函数start_kernel。
  27. stext函数的在前置条件是:MMU, D-cache, 关闭; r0 = 0, r1 = machine nr, r2 = atags prointer.

  28. ENTRY(stext)
  29.     ARM_BE8(setend be ) //设置 CPSR 中的端标记位,大端存储。
  30.     /*
  31.     分析如下宏定义可知,内核在GCC版本大于4.0后,可通过配置宏CONFIG_THUMB2_KERNEL来选择编译成THUMB指令集内核或ARM指令集内核。在编译成ARM指令集内核时,THUMB宏定义的代码行是不可见的。同理编译成THUMB指令集内核时,ARM宏定义的代码行无效。BSYM宏是为在THUMB指令集时访问标号处理而定义的宏。#define BSYM(sym) sym。所以在arm指令集编译时,下边THUMB代码是不可见的。
  32.     */     
  33.     THUMB( adr r9, BSYM(1f) ) //Kernel is always entered in ARM.
  34.     THUMB( bx r9 ) //If this is a Thumb-2 kernel,
  35.     THUMB( .thumb ) //switch to Thumb now.
  36.     THUMB(1: )
  37.  
  38. #ifdef CONFIG_ARM_VIRT_EXT
  39.     bl __hyp_stub_install
  40. #endif
  41.     //确保kernel运行在SVC模式下,并且IRQ和FIRQ中断已经关闭
  42.     safe_svcmode_maskall r9
  43.     
  44.     //从arm协处理器里面读到CPU ID存储到r9,这里的CPU主要是指arm架构相关的CPU型号,比如ARM9,ARM11等等。
  45.     mrc p15, 0, r9, c0, c0
  46.     //通过从__lookup_processor_type_data中获取处理器信息位置,通过轮询查找该内核是否支持该本处理器。其中r9是通过processor id。
  47.     bl __lookup_processor_type
  48.     //r5保存处理器信息,若为空则出错__error_p
  49.     movs r10, r5
  50.     THUMB( it eq )             
  51.     beq __error_p
  52.     
  53. #ifdef CONFIG_ARM_LPAE
  54.     mrc p15, 0, r3, c0, c1, 4 @ read ID_MMFR0
  55.     and r3, r3, #0xf @ extract VMSA support
  56.     cmp r3, #5 @ long-descriptor translation table format?
  57.     THUMB( it lo ) @ force fixup-able long branch encoding
  58.     blo __error_lpae @ only classic page table format
  59. #endif
  60.     
  61. #ifndef CONFIG_XIP_KERNEL
  62.     adr r3, 2f
  63.     ldmia r3, {r4, r8}
  64.     sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)
  65.     add r8, r8, r4 @ PHYS_OFFSET
  66. #else
  67.     ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case
  68. #endif
  69.         
  70.     /*
  71.     * r1 = machine no, r2 = atags or dtb,
  72.     * r8 = phys_offset, r9 = cpuid, r10 = procinfo
  73.     */
  74.     //检查atags parameter的有效性
  75.     bl __vet_atags
  76. #ifdef CONFIG_SMP_ON_UP
  77.     bl __fixup_smp
  78. #endif
  79. #ifdef CONFIG_ARM_PATCH_PHYS_VIRT
  80.     bl __fixup_pv_table
  81. #endif
  82.     //创建临时页表,它所要做的工作就是将RAM基地址开始的4M空间的物理地址映射到0xC0000000开始的虚拟地址处
  83.     bl __create_page_tables    
  84.     
  85.     //将__mmap_switched这个symbol的链接地址放在sp里面
  86.     ldr r13, =__mmap_switched //address to jump to after
  87.     
  88.     //把标号1处地址赋给lr //mmu has been enabled
  89.     adr lr, BSYM(1f) //return (PIC) address
  90.     mov r8, r4 //set TTBR1 to swapper_pg_dir
  91.     ARM( add pc, r10, #PROCINFO_INITFUNC )
  92.     THUMB( add r12, r10, #PROCINFO_INITFUNC )
  93.     THUMB( mov pc, r12 )
  94. 1:
  95.     b __enable_mmu
  96. ENDPROC(stext)

  97. //从__lookup_processor_type_data中获取处理器信息位置
  98. __lookup_processor_type:
  99.     adr r3, __lookup_processor_type_data    //R3指向类型数据指针
  100.     ////读取R3指向地址三个WORD数据到R4,R5,R6,R4=标号3处的虚拟地址,r5=__arch_info_begin,r6=__arch_info_end。
  101.     ldmia r3, {r4 - r6}    
  102.     sub r3, r3, r4 //物理地址-虚拟地址get offset between virt&phys
  103.     add r5, r5, r3 //将R5的虚拟地址转换成物理地址,R5为类型数据起始地址
  104.     add r6, r6, r3 //将R6的虚拟地址转换成物理地址,R6为类型数据结束地址
  105. 1:
  106.     ldmia r5, {r3, r4}    //从R5指向的地址读取两个WORD到R3,R4
  107.     and r4, r4, r9        //获取判断位
  108.     teq r3, r4            //判断本处理器类型是否已找到
  109.     beq 2f                //若已找到,跳转到2:执行
  110.     add r5, r5, #PROC_INFO_SZ    //若未找到,R5指针增到一个类型数据长度sizeof(proc_info_list)
  111.     cmp r5, r6            //是否已查找完所有处理器类型
  112.     blo 1b                //未查找完返回1标号处继续循环
  113.     mov r5, #0            //若未找到本处理器类型信息,返回值R5设置为NULL
  114. 2:
  115.     mov pc, lr
  116. ENDPROC(__lookup_processor_type)

  117. //__lookup_processor_type_data数据结构定义位于文件\linux-xlnx\arch\arm\kernel\head-common.S,参见汇编代码可知,该数据结构有三个数据,数据内容参见代码注释。
  118.     .align 2
  119.     .type __lookup_processor_type_data, %object
  120. __lookup_processor_type_data:
  121.     .long .                    //当前内存地址(此处为虚拟地址)
  122.     .long __proc_info_begin    //处理器类型信息起始地址
  123.     .long __proc_info_end        //处理器类型信息结束地址
  124.     .size __lookup_processor_type_data, . - __lookup_processor_type_data

  125. //_proc_info_begin,__proc_info_end定义位于\linux-xlnx\arch\arm\kernel\vmlinux.lds.S,它用于存储.proc.info.init段的起始地址及结束地址。
  126.     VMLINUX_SYMBOL(__proc_info_begin) = .; \
  127.     *(.proc.info.init) \
  128.     VMLINUX_SYMBOL(__proc_info_end) = .;
  129. //.proc.info.init段定义位于\linux-xlnx\arch\arm\mm\proc-v7.S,该文件定义了本内核版本支持的ARM v7架构下处理器类型等。

  130. //检查bootloader传入的参数列表atags的合法性
  131. __vet_atags:
  132.     tst r2, #0x3 //r2指向该参数链表的起始位置,此处判断它是否字对齐
  133.     bne 1f
  134.     
  135.     ldr r5, [r2, #0] //获取第一个tag结构的size
  136. #ifdef CONFIG_OF_FLATTREE
  137.     ldr r6, =OF_DT_MAGIC //is it a DTB?
  138.     cmp r5, r6
  139.     beq 2f
  140. #endif
  141.     //#define??ATAG_CORE_SIZE??((2*4??+??3*4)??>>??2)??判断该tag的长度是否合法
  142.     cmp r5, #ATAG_CORE_SIZE
  143.     cmpne r5, #ATAG_CORE_SIZE_EMPTY
  144.     bne 1f
  145.     ldr r5, [r2, #4]    //??获取第一个tag结构体的标记
  146.     ldr r6, =ATAG_CORE    //判断第一个tag结构体的标记是不是ATAG_CORE
  147.     cmp r5, r6
  148.     bne 1f    
  149. 2:
  150.     mov pc, lr //正常退出
  151. 1:
  152.     mov r2, #0
  153.     mov pc, lr        //参数链表不正确
  154. ENDPROC(__vet_atags)

  155. __mmap_switched:
  156.     adr r3, __mmap_switched_data
  157.          
  158.     ldmia {r4, r5, r6, r7}
  159.     cmp r4, r5 @ Copy data segment if needed
  160.     1: cmpne r5, r6
  161.     ldrne fp, [r4], #4
  162.     strne fp, [r5], #4
  163.     bne 1b
  164.     
  165.     mov fp, #0 @ Clear BSS (and zero fp)
  166.     1: cmp r6, r7
  167.     strcc fp, [r6],#4
  168.     bcc 1b
  169.     
  170.     ARM( ldmia r3, {r4, r5, r6, r7, sp})
  171.     THUMB( ldmia r3, {r4, r5, r6, r7} )
  172.     THUMB( ldr sp, [r3, #16] )
  173.     str r9, [r4] @ Save processor ID
  174.     str r1, [r5] @ Save machine type
  175.     str r2, [r6] @ Save atags pointer
  176.     cmp r7, #0
  177.     bicne r4, r0, #CR_A @ Clear 'A' bit
  178.     stmneia r7, {r0, r4} @ Save control register values
  179.     b start_kernel        //跳转到start_kernel继续初始化
  180. ENDPROC(__mmap_switched)

  181. 二、start kernel初始化
  182. asmlinkage __visible void __init start_kernel(void)
  183. {
  184.     char * command_line;
  185.     //地址指针,指向内核启动参数在内存中的位置(虚拟地址)
  186.     extern const struct kernel_param __start___param[], __stop___param[];
  187.         
  188.     //对记录各锁之间依赖关系的结构体进行初始化???!!!
  189.     lockdep_init();

  190.     //指定当前的cpu的逻辑号,这个函数对应于对称多处理器的设置,当系统中只有一个cpu的情况,此函数为空
  191.     smp_setup_processor_id();

  192.     //用于内核的对象调试。依赖配置CONFIG_DEBUG_OBJECTS
  193.     debug_objects_early_init();
  194.         
  195.     //初始化防止栈溢出攻击保护的堆栈。
  196.     boot_init_stack_canary();
  197.     
  198.     //一组进程的行为控制,数据结构和其中链表的初始化
  199.     cgroup_init_early();
  200.     
  201.     //关闭本地中断,因为尚未对中断代码和向量表、中断处理器进行初始化,所以必须关中断
  202.     local_irq_disable();

  203.     //用来协助调试,执行当前初始启动代码时,对无效的中断激活事件输出警告
  204.     early_boot_irqs_disabled = true;
  205.     
  206.     //激活当前CPU
  207.     boot_cpu_init();

  208.     //如果有高端内存,则初始化高端内存page_address_htable数组
  209.     page_address_init();
  210.     pr_notice("%s", linux_banner);

  211.     //架构初始化相关
  212.     setup_arch(&command_line);

  213.     //初始化init_mm结构体,owner指向init_task,即init_mm->owner = init_task;
  214.     mm_init_owner(&init_mm, &init_task);

  215.     //设置init_mm的cpu_mask
  216.     mm_init_cpumask(&init_mm);

  217.     //对command_line进行备份与保存
  218.     setup_command_line(command_line);

  219.     //设置nr_cpu_ids变量,即cpu id最大值
  220.     setup_nr_cpu_ids();

  221.     //用于为每个cpu的per-cpu变量副本分配空间,注意这时alloc内存分配器还没建立起来,通过memblock为初始化期间的这些变量副本分配物理空间。
  222.     setup_per_cpu_areas();//参考per-cpu.c文档

  223.     //设置arm的TPIDRPRW寄存器
  224.     smp_prepare_boot_cpu();
  225.     
  226.     //因为前边在setup_arch中已将节点描述符、管理区描述符和页描述符做了初始化,这里进一步的建立系统内存页区(zone)链表    
  227.     build_all_zonelists(NULL, NULL);

  228.     //会调用hotcpu_notifier函数。在编译选项CONFIG_HOTPLUG_CPU起作用时,这个函数才有效。这个编译选项就热插拔技术,而且是CPU的热插拔
  229.     page_alloc_init();
  230.         
  231.     pr_notice("Kernel command line: %s\n", boot_command_line);

  232.     //再次处理early_param定义的参数,在setup_arch调用过一次,可参考
  233.     parse_early_param();

  234.     //解析Booting kernel传进来的参数
  235.     parse_args("Booting kernel", static_command_line, __start___param,__stop___param - __start___param,-1, -1, &unknown_bootoption);

  236.     //处理静态定义的jump label,执行跳转指令或是nop指令
  237.     jump_label_init();
  238.         
  239.     //使用memblock分配一个启动信息的缓冲区
  240.     setup_log_buf(0);

  241.     //使用bootmem分配并初始化PID散列表
  242.     pidhash_init();

  243.     //虚拟文件系统的早期初始化有函数vfs_caches_init_early()实现,主要负责dentry和inode的hashtable的初始化工作。
  244.     vfs_caches_init_early();

  245.     //对内核异常表进行排序
  246.     sort_main_extable();

  247.     //对内核陷阱异常经行初始化,ARM架构中位空函数
  248.     trap_init();

  249.     //初始化内核内存分配器,过度到伙伴系统,启动slab机制,初始化非连续内存区
  250.     //以后分配内存不能使用memblock来分配了
  251.     mm_init();
  252.         
  253.     //初始化调度器数据结构并创建运行队列???
  254.     sched_init();
  255.     
  256.     //禁止内核抢占,关中断
  257.     preempt_disable();
  258.     if (WARN(!irqs_disabled(), "Interrupts were enabled *very* early, fixing it\n"))
  259.         local_irq_disable();

  260.     //创建idr layer的高速缓存cache,参考idr机制文档
  261.     idr_init_cache();

  262.     //初始化系统中"读-写-拷贝"同步机制???
  263.     rcu_init();

  264.     //cpu处在idle状态时关闭tick中断的优化初始化
  265.     tick_nohz_init();

  266.     //没有设置CONFIG_CONTEXT_TRACKING_FORCE宏,不会调用该函数
  267.     context_tracking_init();

  268.     //radix树的初始化,供页面查找
  269.     radix_tree_init();

  270.     //中断初始化,初始化irq_desc数组
  271.     early_irq_init();

  272.     //中断初始化,调用特定平台的中断初始化的init_irq()函数,参考early_irq_init.c
  273.     init_IRQ();

  274.     //时钟滴答的初始化,见timer_init.c
  275.     tick_init();

  276.     //初始化本CPU上的软件时钟相关的数据结构,注册时钟软中断TIMER_SOFTIRQ,见timer_init.c
  277.     init_timers();

  278.     //高分辨率定时器框架初始化,见timer_init.c
  279.     hrtimers_init();

  280.     //软中断初始化,参考软中断文档
  281.     softirq_init();

  282.     //初始化需要和时钟代码共同管理的时间相关值
  283.     timekeeping_init();

  284.     //调用平台设置的time初始化函数
  285.     time_init();

  286.     //进程调度时钟初始化
  287.     sched_clock_postinit();

  288.     //CPU性能监视机制初始化,此机制包括CPU同一时间执行指令数,cache??miss数,分支预测失败次数等性能参数???
  289.     perf_event_init();

  290.     //对内核的profile(一个内核性能调式工具)功能进行初始化,分配好存放profile信息的内存
  291.     profile_init();

  292.     //smp下跨cpu的函数传递初始化,参考smp文档
  293.     call_function_init();
  294.     WARN(!irqs_disabled(), "Interrupts were enabled early\n");
  295.     early_boot_irqs_disabled = false;

  296.     //使能本地中断
  297.     local_irq_enable();

  298.     //slab分配器后期初始化
  299.     kmem_cache_init_late();
  300.         
  301.     //初始化控制台,参考console驱动文档
  302.     console_init();

  303.     //检查内核恐慌标准,如果有问题,打印信息
  304.     if (panic_later)
  305.         panic("Too many boot %s vars at `%s'", panic_later,panic_param);
  306.     
  307.     //打印lockdep调试模块信息
  308.     lockdep_info();

  309.     //锁测试
  310.     locking_selftest();
  311.     
  312.     //检查initrd的位置是否符合要求
  313. #ifdef CONFIG_BLK_DEV_INITRD
  314.     if (initrd_start && !initrd_below_start_ok &&
  315.         page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
  316.         pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
  317.                 page_to_pfn(virt_to_page((void *)initrd_start)),min_low_pfn);
  318.         initrd_start = 0;
  319.     }
  320. #endif

  321.     //容器组的页面内存分配
  322.     page_cgroup_init();

  323.     //调试相关,参考debug_objects_early_init文档
  324.     debug_objects_mem_init();

  325.     //内存泄露检测机制的初始化
  326.     kmemleak_init();

  327.     //为zone中用于实现单一页框的特殊高速缓存设置单一页面数量
  328.     setup_per_cpu_pageset();

  329.     //一致性内存访问(NUMA)初始化,设置NUMA系统中的内存策略。arm非NUMA系统,不考虑
  330.     numa_policy_init();

  331.     //arm中为NULL,不掉用
  332.     if (late_time_init)
  333.         late_time_init();

  334.     //初始化调度时钟
  335.     sched_clock_init();

  336.     //用于BogoMIPS,该值显示1jiffy内消耗多少cpu
  337.     calibrate_delay();

  338.     //进程PID位图分配映射初始化
  339.     pidmap_init();

  340.     //创建anon_vma的slab缓存
  341.     anon_vma_init();

  342.     //acpi相关初始化,忽略
  343.     acpi_early_init();

  344. #ifdef CONFIG_X86
  345.     if (efi_enabled(EFI_RUNTIME_SERVICES))
  346.         efi_enter_virtual_mode();
  347. #endif
  348. #ifdef CONFIG_X86_ESPFIX64
  349.     /* Should be run before the first non-init thread is created */
  350.     init_espfix_bsp();
  351. #endif

  352.     //创建进程thread_info的slab高速缓存
  353.     thread_info_cache_init();

  354.     //为对象的每个用户赋予资格
  355.     cred_init();

  356.     //进程创建机制初始化
  357.     fork_init(totalram_pages);

  358.     //创建进程所需的各结构体slab缓存
  359.     proc_caches_init();

  360.     //为buffer_head结构体创建slab高速缓存
  361.     buffer_init();

  362.     //准备密钥
  363.     key_init();

  364.     //内核安全框架初始化
  365.     security_init();

  366.     //内核调试系统后期初始化,kgdb初始化
  367.     dbg_late_init();

  368.     //初始化VFS中使用的多种缓存
  369.     vfs_caches_init(totalram_pages);

  370.     //创建sigqueue结构高速缓存
  371.     signals_init();

  372.     //页回写机制初始化
  373.     page_writeback_init();

  374.     //注册并初始化proc文件系统
  375.     proc_root_init();

  376.     //注册未能初始化的cgroup子系统
  377.     cgroup_init();

  378.     //注册cpuset文件系统
  379.     cpuset_init();

  380.     //初始化任务统计信息接口
  381.     taskstats_init_early();

  382.     //为管理延迟信息做准备
  383.     delayacct_init();

  384.     //检查写缓冲一致性
  385.     check_bugs();
  386.     
  387.     //simple firmware interface
  388.     sfi_init_late();
  389.     
  390.     if (efi_enabled(EFI_RUNTIME_SERVICES)) {
  391.         efi_late_init();
  392.         efi_free_boot_services();
  393.     }
  394.     
  395.     //trace初始化
  396.     ftrace_init();
  397.     
  398.     //剩余的初始化
  399.     rest_init();
  400. }


  401. static inline void mm_init_cpumask(struct mm_struct *mm)
  402. {
  403. #ifdef CONFIG_CPUMASK_OFFSTACK
  404.     mm->cpu_vm_mask_var = &mm->cpumask_allocation;
  405. #endif
  406. }

  407. static void __init setup_command_line(char *command_line)
  408. {
  409.     //把刚才在setup_arch()中拷贝进来的command_line,拷贝到全局变量saved_command_line和static_command_line所指向的内存单元中。
  410.     //通过memblock分配内存空间,参考memblock文档
  411.     saved_command_line = memblock_virt_alloc(strlen(boot_command_line) + 1, 0);
  412.     initcall_command_line = memblock_virt_alloc(strlen(boot_command_line) + 1, 0);
  413.     static_command_line = memblock_virt_alloc(strlen(command_line) + 1, 0);

  414.     strcpy (saved_command_line, boot_command_line);
  415.     strcpy (static_command_line, command_line);
  416. }

  417. //nr_cpu_ids是一个特殊的值,在单CPU情况下是1;而SMP情况下,又是一个全局变量,被find_last_bit函数设置.    
  418. void __init setup_nr_cpu_ids(void)
  419. {
  420.     nr_cpu_ids = find_last_bit(cpumask_bits(cpu_possible_mask),NR_CPUS) + 1;
  421. }

  422. //根据smp和cpu架构
  423. void __init smp_prepare_boot_cpu(void)
  424. {
  425.     set_my_cpu_offset(per_cpu_offset(smp_processor_id()));
  426. }

  427. #if defined(CONFIG_SMP) && !defined(CONFIG_CPU_V6)
  428. static inline void set_my_cpu_offset(unsigned long off)
  429. {
  430.     /* Set TPIDRPRW */
  431.     asm volatile("mcr p15, 0, %0, c13, c0, 4" : : "r" (off) : "memory");
  432. }
  433. #else
  434. #define set_my_cpu_offset(x) do {} while(0)
  435. #endif /* CONFIG_SMP */

阅读(1540) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~