<sup id="oewps"><pre id="oewps"><sub id="oewps"></sub></pre></sup>
    <div id="oewps"></div>

      <div id="oewps"><ol id="oewps"></ol></div>
      查看: 2217|回复: 0
      打印 上一主题 下一主题

      [提问] 嵌入式ARM Linux kernel启动过程之浅尝辄止分析start_kernel函数 [复制链接]

      xyd2018 (离线)
      积分
      1499
      帖子
      300
      跳转到指定楼层
      楼主
      发表于 2019-3-14 10:05:30 |只看该作者 |倒序浏览
      关键词: 嵌入式ARM , Linux , kernel启动过程
      了解了kernel启动以前的汇编之后我们来看看正式的c语言启动代码,也就是我们的start_kernel函数了。start_kernel相当大,里面每一个调用到的函数都足够我们伤脑筋了,这里只是浅尝辄止的描述一下函数的功能,从而对kernel启动的过程有一个比较直观的了解。很多函数真正理解需要对linux相关体系有很深的了解。

      说实话启动的代码看到现在唯一的感觉就是kernel的全?#30452;?#37327;实在太多了,要了解一个过程跟踪一个变量的值的变化相当痛苦啊,不过耐心看下来,收获还是比较丰富的,对很多概念都有了一个比较直观的理解。闲话就?#27426;?#35828;了,直接来上代码~~
      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();
      /* 基本上面几个函数就?#27973;?#22987;化lockdep和cgroup,然后禁止IRQ,为后续的运行创造条件 */

      lock_kernel();

      /* 看这个函数的之前我们首先来了解一段知识,linux kernel默认是支持preemption(抢占)的。在SMP环境下为了实现kernel的锁定,kernel使用了一个BKL(big kernel lock)的概念,在初始化的过程中?#20154;?#23450;这个BKL,然后再继续进行其他启动或者初始化过程,这样就可以防止启动过程中被中断,执行到res_init以后,kernel会?#22836;?#36825;个锁,这样就确保了整个start_kernel过程都不会被抢占或者中断。由此我们可以看到这主要是针对多处理器而言的,实际上如果只有一个处理器的话它也不会被中断或者被抢占。 */
      /* 下面我们来看看这个函数的执行过程,在这里面能学到很多东西的:
      int depth = current->lock_depth+1;
      这个current实际上是一个宏,定义在arch/arm/include/asm/current.h里面,它实际是一个task_struct的结构体,这个结构体描述了这个task的一系列信息(task应该是linux进程调度的一个基本单位?)。linux下每个进程都有一个相?#26434;?#30340;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。如果禁止抢占没有成功则在抢?#21152;行?#30340;情况下去等待BKL,直到获得BKL。因为QC的片子不是SMP,所有这里第一次try的时候就直接成功了。
      current->lock_depth = depth;
      这个就没什么好说的了 */
      /* 基本上来说这个lock_kernel就是禁止抢占,然后获得BKL,干了这么件事 */
      tick_init();
      //和时钟相关的初始化,好像是注册notify?#24405;?#27809;有仔细研究过
      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是?#22336;?#20018;<5>,不太明白它的意思。。。后面的linux_banner定义在kernel/init/version.c里面,这里的printk是门高深的学问,以后看console的时候会仔细分析
      setup_arch(&command_line);
      /* 这是一个重量级的函数了,会比较仔细地分析一下,主要完成了4个方面的工作,一个就是取得MACHINE和PROCESSOR的信息然或将他们赋值给kernel相应的全?#30452;?#37327;,然后呢?#23884;詁oot_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以后,将里面的内容一个个赋值给相应的全?#30452;?#37327;,然后将CPU的信息打印出来。然后它会从arm寄存器里面获得cache的信息,并将cache的信息打印出来。最后它会调用cpu_proc_init()的函数,这个函数实际上定义在proc-v6.S里面,没有做任?#38382;?#24773;。

      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是不是为空,不为空的话?#24471;鱞ootloader设置了初始化参数,将参数的物理地址转化为虚拟地址,这里有一个覆盖,就是说可以在Machine desc里面对初始化参数的物理地?#20998;?#26032;定位。

      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格式,如果转换以后还是?#27426;裕?#21017;使用默认的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,这个保存函数在这里实际上是空的,没有做任何操作,最后?#23884;詀tag参数进行解析。这里要?#24471;?#19968;下这里的tags实际上是一个tag的数组或者说队列,里面有多个tag结构体,每一个结构体都是一个header加一个参数,具体的结构我们可以看看setup.h。对ATAG参数的解析全部定义在arch/arm/kernel/setup.c里面,首先在setup.c里面定义了一个类似于这样__tagtable(ATAG_CORE, parse_tag_core)的宏,这个宏实际上是声明了一个放在__tagtable_begin和__tagtable_end段之间结构体,这个结构体定义了这个一个参数类型,和对这个参数类型进行解析的函数。所有的参数解析我?#23884;?#21487;以从setup.c里面找到相?#26434;?#30340;函数,比如说对boot_commad_line的解析,从config文件得到的default_commad_line就会被ATAG里面获得commad_line给替换掉;再比如ramdisk,就会将ATAG里面的ramdisk的信息赋值给rd_image_start, rd_size等系统的全?#30452;?#37327;。

      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;

      这就就?#23884;詉nit_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来?#26434;?#19982;config文件里面的CONFIG_CMDLINE,也有可能被ATAG里面的boot参数给覆盖,获得command_line以后对command_line进行解析。这个解析的过程也是在setup.c里面进行的,它首先查找.early_param.init段里面注册的结构体,通过__early_param将early_param结构体注册到这些段里面,实际这个early_param很简单一个就是类似于"initrd="的?#22336;?#20018;,用来与command_line里面的?#22336;?#36827;行匹配,如果匹配到了就执行early_param里面的那个函数,并将匹配到的?#22336;?#20316;为参数传给函数。举个例子比如说我们现在的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,这里主要?#23884;詀rm寄存器cpsr的操作
      init_arch_irq = mdesc->init_irq;
      system_timer = mdesc->timer;
      init_machine = mdesc->init_machine;
      这里将体?#21040;?#26500;相关的几个函数,中断,初始化,定时器之类的赋值给kernel全?#30452;?#37327;;
      conswitchp = &vga_con;
      这里设置了关于console的一个变量,具体不知道怎么用的,以后看console的时候再仔细分析
      early_trap_init();
      不知道这个函数具体做什么用的。。。 */
      /* 基本上我们可以总结出setup_arch主要将一些体?#21040;?#26500;的相关信息来赋值给kernel的全?#30452;?#37327;,包括cpu啊,machine啊,memory,cahce啊,然后kernel再根据这些函数或者变量来做相应的工作,而且很明显地可以看出来这个setup_arch和前面的head.S,head-common.S,proc-v6.S,board-msm7x27.c是紧密联系在一起的 */


      信盈达靠?#38469;?#25171;天下
      以下课程可免费试听C语言电子PCBSTM32LinuxFPGA、JAVA、安卓?#21462;?br /> 想学习的你和我联系预约就可以免费听课了。
      宋工企鹅号:35--24-65--90-88   Tel/WX:173--17--95--19--08


      您需要登录后才可以发表评论 登录 | 立即注册

      关于我们  -  服务条款  -  使用指南  -  站点地图  -  友情链接  -  联系我们
      电子工程网 © 版权所有   京ICP备16069177号 | 京公网安备11010502021702
      回顶部 11选5出号精准规律
      <sup id="oewps"><pre id="oewps"><sub id="oewps"></sub></pre></sup>
      <div id="oewps"></div>

        <div id="oewps"><ol id="oewps"></ol></div>
        <sup id="oewps"><pre id="oewps"><sub id="oewps"></sub></pre></sup>
        <div id="oewps"></div>

          <div id="oewps"><ol id="oewps"></ol></div>