zoukankan      html  css  js  c++  java
  • arm linux kernel启动之start_kernel

    了解完kernel启动以前的汇编之后我们来看看正式的c语言启动代码,也就是我们的start_kernel函数了。start_kernel相当大,里面每一个调用到的函数都足够我们伤脑筋了,我这里只是浅尝辄止的描述一下函数的功能,从而对kernel启动的过程有一个比较直观的了解。很多函数真正理解需要对linux相关体系有很深的了解,暂时没有时间深入,留待以后了。

           说实话启动的代码看到现在唯一的感觉就是kernel的全局变量实在太多了,要了解一个过程跟踪一个变量的值的变化相当痛苦啊,不过耐心看下来,收获还是比较丰富的,对很多概念都有了一个比较直观的理解。闲话就不多说了,直接来上代码~~


           smp_setup_processor_id();

           //这个函数现在是空的;


            lockdep_init();

            //Runtime  locking correctness validator, see Documentation/lockdep_design.txt

            debug_objects_early_init();

            cgroup_init_early();

            //Control group, read Documentation/cgroup.txt

            local_irq_disable();

            //使用arm cpsid i指令来禁止IRQ

            early_boot_irqs_off();

            early_init_irq_lock_class();

            /* 基本上面几个函数就是初始化lockdep和cgroup,然后禁止IRQ,为后续的运行创造条件 */


           lock_kernel();

           /* 看这个函数的之前我们首先来了解一段知识,linux kernel默认是支持preemption(抢占)的。在SMP环境下为了实现kernel的锁定,kernel使用了一个BKL(big kernel lock)的概念,在初始化的过程中先锁定这个BKL,然后再继续进行其他启动或者初始化过程,这样就可以防止启动过程中被中断,执行到res_init以后,kernel会释放这个锁,这样就确保了整个start_kernel过程都不会被抢占或者中断。由此我们可以看到这主要是针对多处理器而言的,实际上如果只有一个处理器的话它也不会被中断或者被抢占。 */

           /* 下面我们来看看这个函数的执行过程,在这里面能学到很多东西的:

                int depth = current->lock_depth+1;

                这个current实际上是一个宏,定义在arch/arm/include/asm/current.h里面,它实际是一个task_struct的结构体,这个结构体描述了这个task的一系列信息(task应该是linux进程调度的一个基本单位?)。linux下每个进程都有一个相对应的task_struct,这个task_struct有几个我们经常能看到的信息,一个就是PID,然后就是comm进程的名字,然后就是mm_struct,它定义了跟这个进程相关的所有申请的memory的管理。这个curren_thread_info()也是个很有意思的函数,直接读取SP来获得当前线程的结构体信息thread_info。thread_info和task_struct这两个结构体应该就代表了当前线程的所有信息。

                 初始化的lock_depth是-1,只有init task的lock_depth是0。

                  if (likely(!depth))

                      __lock_kernel();

                  这里判断是不是init task,如果是就会执行__lock_kernel();这个__lock_kernel首先禁止抢占,然后试着获得BKL,如果成功则直接返回,如果不成功首先判断是否禁止抢占成功了,如果成功了就用自旋锁去锁BKL。如果禁止抢占没有成功则在抢占有效的情况下去等待BKL,直到获得BKL。因为QC的片子不是SMP,所有这里第一次try的时候就直接成功了。

                  current->lock_depth = depth;

                  这个就没什么好说的了 */

            /* 基本上来说这个lock_kernel就是禁止抢占,然后获得BKL,干了这么件事 */

     


             tick_init();

             //和时钟相关的初始化,好像是注册notify事件,没有仔细研究过


             boot_cpu_init();

             //这个实际上是在SMP环境下选择CPU,这里直接CPUID选择的是0号cpu


             page_address_init();

             //初始化high memory,在arm环境下实际上这个函数是空的,也就是说arm不支持high memory 


             printk(KERN_NOTICE);

             printk(linux_banner);

             //这里的KER_NOTICE是字符串<5>,不太明白它的意思。。。后面的linux_banner定义在kernel/init/version.c里面,这里的printk是门高深的学问,以后看console的时候会仔细分析


            setup_arch(&command_line);

             /* 这是一个重量级的函数了,会比较仔细地分析一下,主要完成了4个方面的工作,一个就是取得MACHINE和PROCESSOR的信息然或将他们赋值给kernel相应的全局变量,然后呢是对boot_command_line和tags接行解析,再然后呢就是memory、cach的初始化,最后是为kernel的后续运行请求资源。 */

             /* 我们来仔细看看这个函数的实现:

                  setup_processor();

                  这个函数首先从arm寄存器里面取得cpu ID,然后调用lookup_processor_type来取得proc_info_list这个结构体。这个过程实际上和我们在head-common.S里面的过程是一样的,不知道这里为什么不直接去读switch_data里面已经保存好的数据反而又查询一遍是为什么?取得proc_info_list以后,将里面的内容一个个赋值给相应的全局变量,然后将CPU的信息打印出来。然后它会从arm寄存器里面获得cache的信息,并将cache的信息打印出来。最后它会调用cpu_proc_init()的函数,这个函数实际上定义在proc-v6.S里面,没有做任何事情。

                   mdesc = setup_machine(machine_arch_type);

                   首先这个machine_arch_type定义在生成的./include/asm-arm/mach-types.h里面,这个setup_macine实际上也是和上面的processor类似,都是调用head-common.S里面的函数,根据machine ID来获得Machine的信息,并将Machine name打印出来。

                   if (mdesc->soft_reboot)

                        reboot_setup("s");

                    设置reboot方式,默认是硬启动;

                   if (__atags_pointer)                                                               

                          tags = phys_to_virt(__atags_pointer);                                           

                   else if (mdesc->boot_params)                                                       

                           tags = phys_to_virt(mdesc->boot_params);

                  这里首先判断head-common.S里面定义的__atags_pointer是不是为空,不为空的话说明bootloader设置了初始化参数,将参数的物理地址转化为虚拟地址,这里有一个覆盖,就是说可以在Machine desc里面对初始化参数的物理地址重新定位。

                   if (tags->hdr.tag != ATAG_CORE)                                                     

                                convert_to_tag_list(tags);

                   if (tags->hdr.tag != ATAG_CORE)                                                     

                                 tags = (struct tag *)&init_tags; 

                   首先判断是不是正确的atag格式,如果是以前老版本的param_struct格式会首先将其转换成tag格式,如果转换以后还是不对,则使用默认的init_tags,这里判断的过程都是根据结构体第一个值是不是ATAG_CORE.

                   if (mdesc->fixup)

                          mdesc->fixup(mdesc, tags, &from, &meminfo);                                     

                    if (tags->hdr.tag == ATAG_CORE) {

                           if (meminfo.nr_banks != 0)                                                      

                                 squash_mem_tags(tags);

                     save_atags(tags);                                                               

                     parse_tags(tags);

                     这里首先判断fixup函数指针,这里一般为空,如果不为空就会地用fixup来重新修改memory map,meminfo这个结构体定义在arch/arm/include/asm/setup.h里面,描述了内存块的信息,内存块的个数,每个内存块的起始地址和大小,如果修改了memory map则需要从atag参数里面去掉bootloader传过来的的memory map信息,然后是保存一下atag,这个保存函数在这里实际上是空的,没有做任何操作,最后是对atag参数进行解析。这里要说明一下这里的tags实际上是一个tag的数组或者说队列,里面有多个tag结构体,每一个结构体都是一个header加一个参数,具体的结构我们可以看看setup.h。对ATAG参数的解析全部定义在arch/arm/kernel/setup.c里面,首先在setup.c里面定义了一个类似于这样__tagtable(ATAG_CORE, parse_tag_core)的宏,这个宏实际上是声明了一个放在__tagtable_begin和__tagtable_end段之间结构体,这个结构体定义了这个一个参数类型,和对这个参数类型进行解析的函数。所有的参数解析我们都可以从setup.c里面找到相对应的函数,比如说对boot_commad_line的解析,从config文件得到的default_commad_line就会被ATAG里面获得commad_line给替换掉;再比如ramdisk,就会将ATAG里面的ramdisk的信息赋值给rd_image_start, rd_size等系统的全局变量。

                     init_mm.start_code = (unsigned long) _text;               

                     init_mm.end_code   = (unsigned long) _etext;                      

                     init_mm.end_data   = (unsigned long) _edata;                      

                     init_mm.brk    = (unsigned long) _end;

                     这就就是对init_mm结构体进行赋值,具体不了解这些东西咋用的,但是就是将text和data段给赋值了。

                     memcpy(boot_command_line, from, COMMAND_LINE_SIZE);                                 

                     boot_command_line[COMMAND_LINE_SIZE-1] = '/0';                                      

                     parse_cmdline(cmdline_p, from);

                     这里的boot_command_line来自于与config文件里面的CONFIG_CMDLINE,也有可能被ATAG里面的boot参数给覆盖,获得command_line以后对command_line进行解析。这个解析的过程也是在setup.c里面进行的,它首先查找.early_param.init段里面注册的结构体,通过__early_param将early_param结构体注册到这些段里面,实际这个early_param很简单一个就是类似于"initrd="的字符串,用来与command_line里面的字符进行匹配,如果匹配到了就执行early_param里面的那个函数,并将匹配到的字符作为参数传给函数。举个例子比如说我们现在的commadline里面有一句initrd=0x11000000,然后我们首先在early_param.init段里面搜索时候有没有early_param->arg和这个initrd=匹配,找到了就执行它里面的func然后将这个initrd=的值作为参数传进去。

                     paging_init(mdesc);

                     这个函数是个大函数,具体内容没有仔细看,需要对arm MMU了解比较深,这里只贴一下source里面关于这个函数的注释:

     

                     /*

                      * paging_init() sets up the page tables, initialises the zone memory

                      * maps, and sets up the zero page, bad page and bad page tables.

                      */

                     request_standard_resources(&meminfo, mdesc);

                     这个函数用来申请一些应该是内存资源,具体的内容没有仔细研究,看不大懂。。

                     cpu_init();

                     初始化CPU,这里主要是对arm寄存器cpsr的操作

                     init_arch_irq = mdesc->init_irq;

                     system_timer = mdesc->timer;

                     init_machine = mdesc->init_machine;

                     这里将体系结构相关的几个函数,中断,初始化,定时器之类的赋值给kernel全局变量;

                     conswitchp = &vga_con;

                     这里设置了关于console的一个变量,具体不知道怎么用的,以后看console的时候再仔细分析

                     early_trap_init();

                     不知道这个函数具体做什么用的。。。 */

             /* 基本上我们可以总结出setup_arch主要将一些体系结构的相关信息来赋值给kernel的全局变量,包括cpu啊,machine啊,memory,cahce啊,然后kernel再根据这些函数或者变量来做相应的工作,而且很明显地可以看出来这个setup_arch和前面的head.S,head-common.S,proc-v6.S,board-msm7x27.c是紧密联系在一起的 */

     


     

     

            差不多这边就讲到这里,下一篇将start_kernel的剩余部分讲完~~

     

    http://blog.csdn.net/yili_xie/article/details/5750664

  • 相关阅读:
    ubuntu 安装 redis desktop manager
    ubuntu 升级内核
    Ubuntu 内核升级,导致无法正常启动
    spring mvc 上传文件,但是接收到文件后发现文件变大,且文件打不开(multipartfile)
    angular5 open modal
    POJ 1426 Find the Multiple(二维DP)
    POJ 3093 Margritas
    POJ 3260 The Fewest Coins
    POJ 1837 Balance(二维DP)
    POJ 1337 A Lazy Worker
  • 原文地址:https://www.cnblogs.com/0822vaj/p/3539838.html
Copyright © 2011-2022 走看看