• Linux移植之内核启动过程start_kernel函数简析


    Linux移植之内核启动过程引导阶段分析中从arch/arm/kernel/head.S开始分析,最后分析到课start_kernel这个C函数,下面就简单分析下这个函数,因为涉及到Linux的内容较多,这里只是简单介绍下内核启动流程。先看一下内核启动的流程框图,截图来自《嵌入式Linux应用开发完全手册》。内核引导阶段已经分析过,接下来分析一下内核启动的第二阶段。

    1、start_kernel函数全局概览

    2、start_kernel函数调用层次

    1、start_kernel函数全局概览,对start_kernel作一下粗略注释。

    打开initMain.c ,下面主要分析处理UBOOT传入的参数,其中r1是传入的第一个参数存放的地址,里面存放着机器类型ID,已经处理过了;r2是传入的第二个参数,它存放着tag列表数据的地址。先看一下整个start_kernel函数,以下程序参考自http://www.cnblogs.com/lifexy/p/7366782.html

      1 asmlinkage void __init start_kernel(void) 
      2 
      3 { 
      4   char * command_line; 
      5   extern struct kernel_param __start___param[], __stop___param[];  
      6 
      7   smp_setup_processor_id();  //来设置smp process id,当然目前看到的代码里面这里是空的
      8   
      9   unwind_init(); 
     10 
     11 //lockdep是linux内核的一个调试模块,用来检查内核互斥机制尤其是自旋锁潜在的死锁问题。  
     12 //自旋锁由于是查询方式等待,不释放处理器,比一般的互斥机制更容易死锁,  
     13 //故引入lockdep检查以下几种情况可能的死锁(lockdep将有专门的文章详细介绍,在此只是简单列举):  
     14 //  
     15 //·同一个进程递归地加锁同一把锁;  
     16 //  
     17 //·一把锁既在中断(或中断下半部)使能的情况下执行过加锁操作,  
     18 // 又在中断(或中断下半部)里执行过加锁操作。这样该锁有可能在锁定时由于中断发生又试图在同一处理器上加锁;  
     19 //  
     20 //·加锁后导致依赖图产生成闭环,这是典型的死锁现象。  
     21    lockdep_init(); 
     22 //关闭当前CUP中断  
     23 local_irq_disable(); 
     24 
     25 //修改标记early_boot_irqs_enabled;  
     26 //通过一个静态全局变量 early_boot_irqs_enabled来帮助我们调试代码,  
     27 //通过这个标记可以帮助我们知道是否在”early bootup code”,也可以通过这个标志警告是有无效的终端打开  
     28 early_boot_irqs_off(); 
     29 
     30 //每一个中断都有一个IRQ描述符(struct irq_desc)来进行描述。  
     31 //这个函数的主要作用是设置所有的 IRQ描述符(struct irq_desc)的锁是统一的锁,  
     32 //还是每一个IRQ描述符(struct irq_desc)都有一个小锁。  
     33 early_init_irq_lock_class(); 
     34 /*
     35 
     36  * Interrupts are still disabled. Do necessary setups, then
     37  * enable them
     38  */ 
     39 // 大内核锁(BKL--Big Kernel Lock)  
     40 //大内核锁本质上也是自旋锁,但是它又不同于自旋锁,自旋锁是不可以递归获得锁的,因为那样会导致死锁。  
     41 //但大内核锁可以递归获得锁。大内核锁用于保护整个内核,而自旋锁用于保护非常特定的某一共享资源。  
     42 //进程保持大内核锁时可以发生调度,具体实现是:  
     43 //在执行schedule时,schedule将检查进程是否拥有大内核锁,如果有,它将被释放,以致于其它的进程能够获得该锁,  
     44 //而当轮到该进程运行时,再让它重新获得大内核锁。注意在保持自旋锁期间是不运行发生调度的。  
     45 //需要特别指出,整个内核只有一个大内核锁,其实不难理解,内核只有一个,而大内核锁是保护整个内核的,当然有且只有一个就足够了。  
     46 //还需要特别指出的是,大内核锁是历史遗留,内核中用的非常少,一般保持该锁的时间较长,因此不提倡使用它。  
     47 //从2.6.11内核起,大内核锁可以通过配置内核使其变得可抢占(自旋锁是不可抢占的),这时它实质上是一个互斥锁,使用信号量实现。  
     48 //大内核锁的API包括:  
     49 //  
     50 //void lock_kernel(void);  
     51 //  
     52 //该函数用于得到大内核锁。它可以递归调用而不会导致死锁。  
     53 //  
     54 //void unlock_kernel(void);  
     55 //  
     56 //该函数用于释放大内核锁。当然必须与lock_kernel配对使用,调用了多少次lock_kernel,就需要调用多少次unlock_kernel。  
     57 //大内核锁的API使用非常简单,按照以下方式使用就可以了:  
     58 //lock_kernel(); //对被保护的共享资源的访问 … unlock_kernel();  
     59 //http://blog.csdn.net/universus/archive/2010/05/25/5623971.aspx  
     60   lock_kernel(); 
     61 
     62 //初始化time ticket,时钟  
     63   tick_init(); 
     64 
     65 //函数 tick_init() 很简单,调用 clockevents_register_notifier 函数向 clockevents_chain 通知链注册元素:  
     66 // tick_notifier。这个元素的回调函数指明了当时钟事件设备信息发生变化(例如新加入一个时钟事件设备等等)时,  
     67 //应该执行的操作,该回调函数为 tick_notify   
     68 //http://blogold.chinaunix.net/u3/97642/showart_2050200.html  
     69   boot_cpu_init();
     70 
     71 //初始化页地址,当然对于arm这里是个空函数  
     72 //http://book.chinaunix.net/special/ebook/PrenticeHall/PrenticeHallPTRTheLinuxKernelPrimer/0131181637/ch08lev1sec5.html  
     73   page_address_init(); 
     74 
     75 /*打印KER_NOTICE,这里的KER_NOTICE是字符串<5>*/
     76   printk(KERN_NOTICE);
     77 
     78 /*打印以下linux版本信息:       
     79 “Linux version 2.6.22.6 (book@book-desktop) (gcc version 3.4.5) #1 Fri Jun 16 00:55:53 CST 2017” */
     80  printk(linux_banner);
     81       
     82 //系结构相关的内核初始化过程,处理uboot传递进来的atag参数( setup_memory_tags()和setup_commandline _tags() )  
     83 //http://www.cublog.cn/u3/94690/showart_2238008.html  
     84 setup_arch(&command_line); 
     85 
     86 //处理启动命令,这里就是设置的cmd_line,
     87 //保存未改变的comand_line到字符数组static_command_line[] 中。
     88 //保存  boot_command_line到字符数组saved_command_line[]中  
     89 setup_command_line(command_line); 
     90 
     91 unwind_setup();
     92 
     93 //如果没有定义CONFIG_SMP宏,则这个函数为空函数。  
     94 //如果定义了CONFIG_SMP宏,则这个setup_per_cpu_areas()函数给每个CPU分配内存,  
     95 //并拷贝.data.percpu段的数据。为系统中的每个CPU的per_cpu变量申请空间。  
     96 setup_per_cpu_areas();
     97 
     98 //定义在include/asm-x86/smp.h。  
     99 //如果是SMP环境,则设置boot CPU的一些数据。在引导过程中使用的CPU称为boot CPU  
    100 smp_prepare_boot_cpu(); 
    101 
    102 /* arch-specific boot-cpu hooks */ 
    103 /* 进程调度器初始化 */
    104 sched_init(); 
    105 
    106 /* 禁止内核抢占 */  
    107 preempt_disable();
    108       
    109 //设置node 和 zone 数据结构  
    110 //内存管理的讲解:http://blog.chinaunix.net/space.php?uid=361890&do=blog&cuid=2146541  
    111 build_all_zonelists(NULL); 
    112  
    113 //初始化page allocation相关结构  
    114 page_alloc_init();
    115 
    116 /* 打印Linux启动命令行参数 */   
    117 printk(KERN_NOTICE "Kernel command line: %s/n", boot_command_line); 
    118   
    119 //解析内核参数  
    120 //对内核参数的解析:http://hi.baidu.com/yuhuntero/blog/item/654a7411e45ce519b8127ba9.html  
    121 parse_early_param(); 
    122 parse_args("Booting kernel", static_command_line, __start___param, 
    123   __stop___param - __start___param, 
    124   &unknown_bootoption); 
    125 
    126 /*
    127 * These use large bootmem allocations and must precede
    128 * kmem_cache_init()
    129 */ 
    130 //初始化hash表,以便于从进程的PID获得对应的进程描述指针,按照实际的物理内存初始化pid hash表  
    131 //这里涉及到进程管理http://blog.csdn.net/satanwxd/archive/2010/03/27/5422053.aspx  
    132 pidhash_init(); 
    133 
    134 //初始化VFS的两个重要数据结构dcache和inode的缓存。  
    135 //http://blog.csdn.net/yunsongice/archive/2011/02/01/6171324.aspx  
    136 vfs_caches_init_early(); 
    137 
    138 //把编译期间,kbuild设置的异常表,也就是__start___ex_table和__stop___ex_table之中的所有元素进行排序  
    139 sort_main_extable(); 
    140 
    141 //初始化中断向量表  
    142 //http://blog.csdn.net/yunsongice/archive/2011/02/01/6171325.aspx  
    143 trap_init(); 
    144 
    145 //memory map初始化  
    146 //http://blog.csdn.net/huyugv_830913/archive/2010/09/15/5886970.aspx  
    147 mm_init(); 
    148 
    149 /*
    150 * Set up the scheduler prior starting any interrupts (such as the
    151 * timer interrupt). Full topology setup happens at smp_init()
    152 * time - but meanwhile we still have a functioning scheduler.
    153 */ 
    154 //核心进程调度器初始化,调度器的初始化的优先级要高于任何中断的建立,  
    155 //并且初始化进程0,即idle进程,但是并没有设置idle进程的NEED_RESCHED标志,  
    156 //所以还会继续完成内核初始化剩下的事情。  
    157 //这里仅仅为进程调度程序的执行做准备。  
    158 //它所做的具体工作是调用init_bh函数(kernel/softirq.c)把timer,tqueue,immediate三个人物队列加入下半部分的数组  
    159 sched_init(); 
    160 
    161 /*
    162 * Disable preemption - early bootup scheduling is extremely
    163 * fragile until we cpu_idle() for the first time.
    164 */ 
    165 //抢占计数器加1   
    166  preempt_disable();
    167 
    168 //检查中断是否打开,如果已经打开,则关闭中断   
    169   if (!irqs_disabled()) { 
    170   printk(KERN_WARNING "start_kernel(): bug: interrupts were " 
    171   "enabled *very* early, fixing it/n"); 
    172   local_irq_disable(); 
    173   } 
    174 
    175   sort_main_extable(); 
    176  /*
    177  * trap_init函数完成对系统保留中断向量(异常、非屏蔽中断以及系统调用)              
    178  * 的初始化,init_IRQ函数则完成其余中断向量的初始化
    179  */
    180  trap_init();    
    181 
    182 //Read-Copy-Update的初始化  
    183 //RCU机制是Linux2.6之后提供的一种数据一致性访问的机制,  
    184 //从RCU(read-copy-update)的名称上看,我们就能对他的实现机制有一个大概的了解,  
    185 //在修改数据的时候,首先需要读取数据,然后生成一个副本,对副本进行修改,  
    186 //修改完成之后再将老数据update成新的数据,此所谓RCU。  
    187 //http://blog.ednchina.com/tiloog/193361/message.aspx  
    188 //http://blogold.chinaunix.net/u1/51562/showart_1341707.html  
    189 rcu_init();
    190  
    191 //初始化IRQ中断和终端描述符。  
    192 //初始化系统中支持的最大可能的中断描述结构struct irqdesc变量数组irq_desc[NR_IRQS],  
    193 //把每个结构变量irq_desc[n]都初始化为预先定义好的坏中断描述结构变量bad_irq_desc,  
    194 //并初始化该中断的链表表头成员结构变量pend  
    195 init_IRQ();    
    196 
    197 /* 初始化hash表,便于从进程的PID获得对应的进程描述符指针 */
    198 pidhash_init();
    199 
    200 //初始化定时器Timer相关的数据结构  
    201 //http://www.ibm.com/developerworks/cn/linux/l-cn-clocks/index.html  
    202 init_timers();  
    203 
    204 //对高精度时钟进行初始化  
    205 hrtimers_init();
    206  
    207 //软中断初始化  
    208 //http://blogold.chinaunix.net/u1/51562/showart_494363.html  
    209 softirq_init(); 
    210  
    211 //初始化时钟源  
    212 timekeeping_init(); 
    213  
    214 //初始化系统时间,  
    215 //检查系统定时器描述结构struct sys_timer全局变量system_timer是否为空,  
    216 //如果为空将其指向dummy_gettimeoffset()函数。  
    217 //http://www.ibm.com/developerworks/cn/linux/l-cn-clocks/index.html  
    218 time_init();  
    219 
    220 //profile只是内核的一个调试性能的工具,  
    221 //这个可以通过menuconfig中的Instrumentation Support->profile打开。  
    222 //http://www.linuxdiyf.com/bbs//thread-71446-1-1.html  
    223 profile_init();
    224     
    225 /*if判断中断是否打开,如果已经打开,打印数据*/   
    226 if (!irqs_disabled()) 
    227     printk(KERN_CRIT "start_kernel(): bug: interrupts were enabled early/n"); 
    228 
    229 //与开始的early_boot_irqs_off相对应  
    230 early_boot_irqs_on(); 
    231  
    232 //与local_irq_disbale相对应,开CPU中断  
    233 local_irq_enable();
    234 
    235 /*
    236 * HACK ALERT! This is early. We're enabling the console before
    237 * we've done PCI setups etc, and console_init() must be aware of
    238 * this. But we do want output early, in case something goes wrong.
    239 */ 
    240 //初始化控制台以显示printk的内容,在此之前调用的printk,只是把数据存到缓冲区里,  
    241 //只有在这个函数调用后,才会在控制台打印出内容  
    242 //该函数执行后可调用printk()函数将log_buf中符合打印级别要求的系统信息打印到控制台上。  
    243 console_init(); 
    244 
    245 if (panic_later) 
    246 panic(panic_later, panic_param);  
    247 
    248 //如果定义了CONFIG_LOCKDEP宏,那么就打印锁依赖信息,否则什么也不做  
    249 lockdep_info(); 
    250 
    251 /*
    252 * Need to run this when irqs are enabled, because it wants
    253 * to self-test [hard/soft]-irqs on/off lock inversion bugs
    254 * too:
    255 */ 
    256 //如果定义CONFIG_DEBUG_LOCKING_API_SELFTESTS宏  
    257 //则locking_selftest()是一个空函数,否则执行锁自测  
    258  locking_selftest(); 
    259 
    260 #ifdef CONFIG_BLK_DEV_INITRD  
    261 if (initrd_start && !initrd_below_start_ok && 
    262    page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) { 
    263 printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - " 
    264    "disabling it./n", 
    265    page_to_pfn(virt_to_page((void *)initrd_start)), 
    266    min_low_pfn); 
    267 initrd_start = 0; 
    268 } 
    269 #endif
    270 
    271  /* 虚拟文件系统的初始化 */
    272  vfs_caches_init_early();       
    273  cpuset_init_early();
    274  mem_init();
    275 
    276 /* slab初始化 */
    277 kmem_cache_init();  
    278 
    279 //是否是对SMP的支持,单核是否需要??这个要分析  
    280 setup_per_cpu_pageset(); 
    281 
    282 numa_policy_init(); 
    283 
    284 if (late_time_init) 
    285    late_time_init(); 
    286 
    287 //calibrate_delay()函数可以计算出cpu在一秒钟内执行了多少次一个极短的循环,  
    288 //计算出来的值经过处理后得到BogoMIPS 值,  
    289 //Bogo是Bogus(伪)的意思,MIPS是millions of instructions per second(百万条指令每秒)的缩写。  
    290 //这样我们就知道了其实这个函数是linux内核中一个cpu性能测试函数。  
    291 //http://blogold.chinaunix.net/u2/86768/showart_2196664.html  
    292 calibrate_delay();
    293    
    294 //PID是process id的缩写  
    295 //http://blog.csdn.net/satanwxd/archive/2010/03/27/5422053.aspx  
    296 pidmap_init();
    297 
    298  /* 接下来的函数中,大多数都是为有关的管理机制建立专用的slab缓存 */ 
    299 pgtable_cache_init();
    300  
    301 /* 初始化优先级树index_bits_to_maxindex数组 */
    302 prio_tree_init();          
    303 
    304 //来自mm/rmap.c  
    305 //分配一个anon_vma_cachep作为anon_vma的slab缓存。  
    306 //这个技术是PFRA(页框回收算法)技术中的组成部分。  
    307 //这个技术为定位而生——快速的定位指向同一页框的所有页表项。  
    308 anon_vma_init();
    309 
    310 #ifdef CONFIG_X86  
    311 if (efi_enabled) 
    312 efi_enter_virtual_mode(); 
    313 #endif 
    314 
    315 //根据物理内存大小计算允许创建进程的数量  
    316 //http://www.jollen.org/blog/2006/11/jollen_linux_3_fork_init.html  
    317 fork_init(totalram_pages);
    318 
    319 //给进程的各种资源管理结构分配了相应的对象缓存区  
    320 //http://www.shangshuwu.cn/index.php/Linux内核的进程创建  
    321 proc_caches_init(); 
    322 
    323 //创建 buffer_head SLAB 缓存  
    324 buffer_init(); 
    325 
    326 unnamed_dev_init();
    327 
    328 //初始化key的management stuff  
    329 key_init(); 
    330 
    331 //关于系统安全的初始化,主要是访问控制  
    332 //http://blog.csdn.net/nhczp/archive/2008/04/29/2341194.aspx  
    333 security_init();  
    334 
    335 //调用kmem_cache_create()函数来为VFS创建各种SLAB分配器缓存  
    336 //包括:names_cachep、filp_cachep、dquot_cachep和bh_cachep等四个SLAB分配器缓存  
    337 vfs_caches_init(totalram_pages); 
    338  
    339 radix_tree_init(); 
    340 
    341 //创建信号队列  
    342 signals_init();
    343 
    344 /* rootfs populating might need page-writeback */ 
    345 //回写相关的初始化  
    346 //http://blog.csdn.net/yangp01/archive/2010/04/06/5454822.aspx  
    347 page_writeback_init();   
    348 
    349 #ifdef CONFIG_PROC_FS  
    350    proc_root_init(); 
    351 #endif  
    352 
    353 //http://blogold.chinaunix.net/u1/51562/showart_1777937.html  
    354 cpuset_init();
    355  
    356 ////进程状态初始化,实际上就是分配了一个存储线程状态的高速缓存  
    357 taskstats_init_early(); 
    358 
    359 delayacct_init();  
    360 
    361 //测试CPU的各种缺陷,记录检测到的缺陷,以便于内核的其他部分以后可以使用他们工作。  
    362 check_bugs(); 
    363  
    364 //电源相关的初始化  
    365 //http://blogold.chinaunix.net/u/548/showart.php?id=377952  
    366 acpi_early_init(); /* before LAPIC and SMP init */ 
    367 
    368 //接着进入rest_init()创建init进程,创建根文件系统,启动应用程序
    369 rest_init(); 
    370 }

    2、start_kernel调用层次。简略的写出start_kernel函数的调用层次后面一步步分析它。其实可以概括为读取uboot传入的tag参数并且处理它们。

    假设uboot传入的命令行参数为bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0。

    a、其中setup_arch、setup_command_line 、do_early_para、unknown_bootoption这几个函数可以概括为处理uboot传入的tag参数。主要处理ATAG_MEM参数、ATAG_CMDLINE参数部分参数

    b、console_init根据处理后的console=ttySAC0命令行参数选择合适的控制台

    c、其中rest_init是start_kernel调用的最后一个函数主要用来根据 处理后的root=/dev/mtdblock3、init=/linuxrc命令后参数分别挂接合适的根文件系统与第一个init进程的启动。

    start_kernel
        setup_arch                  //解析UBOOT传入的启动参数
        setup_command_line //解析UBOOT传入的启动参数
        do_early_param         //解析early参数,uboot中没传这个参数
        unknown_bootoption//解析到了命令行参数,saved_root_name在这块初始化
        console_init();//控制台初始化
        rest_init
            kernel_thread
                kernel_init
                    prepare_namespace   //解析命令行参数解析成功挂接在哪个分区
                        mount_root//挂接根文件系统
                    init_post
                        //执行应用程序

    以上就是start_kernel函数的简单介绍。

    
    
  • 相关阅读:
    【C语言】23typedef
    C#蓝牙开发之查找设备以及配对
    GridView获取隐藏列的值
    PDA(Windows Mobile)调用远程WebService
    VS2008使用宏记录来实现自动增加注释信息
    CS 系统框架二[部分内容更新]
    GridView里面嵌套RadioButton
    .Net 以报表的形式加载SAP里面的数据
    取GridView的PagerTemplate里面的控件ID
    《深入Ajax架构和最佳实践》读书笔记
  • 原文地址:https://www.cnblogs.com/andyfly/p/9410441.html
Copyright © 2020-2023  润新知