嵌入式 uboot通过CMD_LINE传参给kernel以及kernel启动分析

Linux内核在启动的时候需要一些参数,以获得当前硬件的信息或者启动所需资源在内存中的位置等等。这些信息可以通过bootloader传递给内核,比较常见的就是cmdline。以前我在启动内核的时候习惯性的通过uboot传递一个cmdline给内核,没有具体的分析这个过程。最近在分析内核启动过程的时候,重新看了一下内核启动参数的传递过程,彻底解决一下在这方面的疑惑。

 

一、bootloader与内核的通讯协议

内核的启动参数其实不仅仅包含在了cmdline中,cmdline不过是bootloader传递给内核的信息中的一部分。bootloader和内核的通信方式根据构架的不同而异。对于ARM构架来说,启动相关的信息可以通过内核文档(Documentation/arm/Booting)获得。其中介绍了bootloader与内核的通信协议,我简单总结如下:

1)数据格式:可以是标签列表(tagged list)或设备树(device tree)。

2)存放地址:r2寄存器中存放的数据所指向的内存地址。

 

在我所做过的开发中,都是使用tagged list的,所以下面以标签列表为例来介绍信息从bootloaderU-boot)到内核(Linux-3.0)的传递过程。

 

内核文档对此的说明,翻译摘要如下:

1.    4a. 设置内核标签列表

2.    --------------------------------

3.     

4.    bootloader必须创建和初始化内核标签列表。一个有效的标签列表以ATAG_CORE标签开始,且以ATAG_NONE标签结束。ATAG_CORE标签可以是空的,也可以是非空。一个空ATAG_CORE标签其 size 域设置为 '2' (0x00000002)ATAG_NONE标签的 size 域必须设置为 '0'

5.     

6.    在列表中可以保存任意数量的标签。对于一个重复的标签是追加到之前标签所携带的信息之后,还是覆盖原来整个信息,是未定义的。某些标签的行为是前者,其他是后者。

7.     

8.    bootloader必须传递一个系统内存的位置和最小值,以及根文件系统位置。因此,最小的标签列表如下所示:

9.     

10. 基地址 -> +-----------+

11. | ATAG_CORE | |

12. +-----------+ |

13. | ATAG_MEM | | 地址增长方向

14. +-----------+ |

15. | ATAG_NONE | |

16. +-----------+ v

17.  

18. 标签列表应该保存在系统的RAM中。

19.  

20. 标签列表必须置于内核自解压和initrd'bootp'程序都不会覆盖的内存区。建议放在RAM的头16KiB中。

(内核中关于ARM启动的标准文档为:Documentation/arm/Booting ,我翻译的版本:《Linux内核文档翻译:Documentation/arm/Booting

 

关于tagged list的数据结构和定义在内核与uboot中都存在,连路径都相同:arch/arm/include/asm/setup.huboot的定义是从内核中拷贝过来的,要和内核一致的,以内核为主。要了解标签列表的具体结构认真阅读这个头文件是必须的。

一个独立的标签的结构大致如下:

 

 

1.    struct tag

2.    +------------------------+

3.    | struct tag_header hdr; | |

4.    | 标签头信息 | |

5.    +------------------------+ |

6.    |union { | |

7.    | struct tag_core core; | |

8.    | struct tag_mem32 mem; | |

9.    | ...... | |

10. | } u; | |

11. 标签具体内容 | |

12. 此为联合体 | | 地址增长方向

13. 根据标签类型确定 | |

14. +------------------------+ v

 

 

           

1.    struct tag_header {

2.    __u32 size; //标签总大小(包括tag_header

3.    __u32 tag; //标签标识

4.    };

比如一个ATAG_CORE在内存中的数据为:

 

1.    +----------+

2.    | 00000005 | |

3.    | 54410001 | |

4.    +----------+ |

5.    | 00000000 | |

6.    | 00000000 | |地址增长方向

7.    | 00000000 | |

8.    +----------+ v

 

1.    当前在内核中接受的标签有:

2.    ATAG_CORE 标签列表开始标志

3.    ATAG_NONE 标签列表结束标志

4.    ATAG_MEM 内存信息标签(可以有多个标签,以标识多个内存区块)

5.    ATAG_VIDEOTEXTVGA文本显示参数标签

6.    ATAG_RAMDISK ramdisk参数标签(位置、大小等)

7.    ATAG_INITRD :压缩的ramdisk参数标签(位置为虚拟地址)

8.    ATAG_INITRD2 :压缩的ramdisk参数标签(位置为物理地址)

9.    ATAG_SERIAL :板子串号标签

10. ATAG_REVISION :板子版本号标签

11. ATAG_VIDEOLFB :帧缓冲初始化参数标签

12. ATAG_CMDLINE command line字符串标签(我们平时设置的启动参数cmdline字符串就放在这个标签中)

13.  

14. 特定芯片使用的标签:

15. ATAG_MEMCLK :给footbridge使用的内存时钟标签

16. ATAG_ACORN acorn RiscPC 特定信息

 

二、参数从u-boot到特定内存地址

使用uboot来启动一个Linux内核,通常情况下我们会按照如下步骤执行:

  1. 设置内核启动的command line,也就是设置uboot的环境变量“bootargs”(非必须,如果你要传递给内核cmdline才要设置)
  2. 加载内核映像文到内存指定位置(从SD卡、u盘、网络或flash)
  3. 使用“bootm (内核映像基址)”命令来启动内核

而这个uboot将参数按照协议处理好并放入指定内存地址的过程就发生在“bootm”命令中,下面我们仔细分析下bootm命令的执行。

 

1bootm 命令主体流程

bootm命令的源码位于common/cmd_bootm.c,其中的do_bootm函数就是bootm命令的实现代码。

 

           

1.    /*******************************************************************/

2.    /* bootm - boot application image fromimage in memory */

3.    /*******************************************************************/

4.     

5.    int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])

6.    {

7.    ulong iflag;

8.    ulong load_end = 0;

9.    int ret;

10.  boot_os_fn *boot_fn;

11.  #ifdef CONFIG_NEEDS_MANUAL_RELOC

12.  static int relocated = 0;

13.   

14.  /* 重载启动函数表 */

15.  if (!relocated) {

16.  int i;

17.  for (= 0; i < ARRAY_SIZE(boot_os); i++)

18.  if (boot_os[i] != NULL)

19.  boot_os[i] += gd->reloc_off;

20.  relocated = 1;

21.  }

22.  #endif

23.   

24.  /* 确定我们是否有子命令 */

25.  /* bootm其实是有子命令的,可以自己将bootm的功能手动分步进行,来引导内核 */

26.  if (argc > 1) {

27.  char *endp;

28.   

29.  simple_strtoul(argv[1], &endp, 16);

30.  /* endp pointing to NULL means that argv[1] was just a

31.  * valid number, pass it along to the normal bootm processing

32.  *

33.  * If endp is ':' or '#' assume a FIT identifier so pass

34.  * along for normal processing.

35.  *

36.  * Right now we assume the first arg shouldnever be '-'

37.  */

38.  if ((*endp != 0) && (*endp != ':') && (*endp != '#'))

39.  return do_bootm_subcommand(cmdtp, flag, argc, argv);

40.  }

41.   

42.  if(bootm_start(cmdtp, flag, argc, argv))

43.  return 1;

44.              

1.    这句非常重要,使其这个就是bootm主要功能的开始。

2.     

3.    其主要的目的是从bootm命令指定的内存地址中获取内核uImage的文件头(也就是在用uboot的mkiamge工具处理内核zImage时添加的那64B的数据)。核对并显示出其中包含的信息,并填充一个全局的staticbootm_headers_t images结构体的image_info_t os域:

4.   

           

1.    typedef struct image_info {

2.    ulong start, end; /* start/end of blob */ 

3.    ulongimage_start, image_len; /* start of image within blob, len of image */

4.    ulong load; /* loadaddr for the image */

5.    uint8_tcomp, type, os; /* compression, type of image, ostype */

6.    } image_info_t;

7.    /*这个域保存OS映像的信息,包括uImage的起止地址、所包含内核映像(可能被压缩过)的起始地址和大小、(解压后)内核映像的加载位置以及压缩方式、映像类型和OS类型。*/

平常我们在用bootm驱动内核的时候所看到的如下信息:

8.    ## Booting kernel from Legacy Image at50008000 ...

9.    Image Name: Linux-2.6.37.1

10. Image Type: ARM Linux Kernel Image(uncompressed)

11. Data Size: 3800644 Bytes = 3.6 MiB

12. Load Address: 50008000

13. Entry Point: 50008040

14. Verifying Checksum ... OK

就是这个函数所调用的 boot_get_kernel函数及其子函数根据uImage的文件头打印出来的。

45.

46.  /*

47.  * 我们已经到达了不可返回的位置: 我们正要覆盖所有的异常向量代码。

48.  * 所以我们再也无法简单地从任何失败中恢复

49.  (突然让我想到张信哲的歌词:我们再也回不去了,对不对?)...

50.  */

51.  iflag = disable_interrupts();

52.   

53.  #if defined(CONFIG_CMD_USB)

54.  /*

55.  * turn off USB to prevent the host controller fromwriting to the

56.  * SDRAM while Linux is booting. This could happen (at least for OHCI

57.  * controller), because the HCCA (Host Controller Communication Area)

58.  * lies within the SDRAM and the host controller writescontinously to

59.  * this area (as The HccaFrameNumber is for example

60.  * updated every 1 ms within theHCCA structure in For more

61.  * details see the OpenHCIspecification.

62.  */

63.  usb_stop();

64.  #endif

65.  

66. ret =bootm_load_os(images.os, &load_end, 1);

67.   

           

1.    这里是第二个重要的启动过程节点,这个函数的作用是通过获取的文件头信息,将文件头后面所跟的内核映像放置到文件头信息规定的地址(如果是压缩内核,还在此函数中解压。但这个和zImage压缩内核不是一个概念,不要混淆)。

2.     

3.    平常我们在用bootm驱动内核的时候所看到的如下信息:

4.    XIP Kernel Image ... OK

5.    OK

6.    就是这个函数打印出来的。

 

68.  if (ret < 0) {

69.  if (ret == BOOTM_ERR_RESET)

70.  do_reset (cmdtp, flag, argc, argv);

71.  if (ret == BOOTM_ERR_OVERLAP) {

72.  if (images.legacy_hdr_valid) {

73.  if (image_get_type (&images.legacy_hdr_os_copy) == IH_TYPE_MULTI)

74.  puts ("WARNING: legacy format multicomponent "

75.  "image overwritten\n");

76.  } else {

77.  puts ("ERROR: new format imageoverwritten - "

78.  "must RESET the board torecover\n");

79.  show_boot_progress (-113);

80.  do_reset (cmdtp, flag, argc, argv);

81.  }

82.  }

83.  if (ret == BOOTM_ERR_UNIMPLEMENTED) {

84.  if (iflag)

85.  enable_interrupts();

86.  show_boot_progress (-7);

87.  return 1;

88.  }

89.  }

90.   

91.  lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load));

92.   

93.  if (images.os.type == IH_TYPE_STANDALONE) {

94.  if (iflag)

95.  enable_interrupts();

96.  /* This may return when 'autostart' is 'no' */

97.  bootm_start_standalone(iflag, argc, argv);

98.  return 0;

99.  }

100.               

101.              show_boot_progress (8);

102.               

103.              #ifdef CONFIG_SILENT_CONSOLE

104.              if (images.os.os == IH_OS_LINUX)

105.              fixup_silent_linux();

106.              #endif

107.              

108.             boot_fn = boot_os[images.os.os];

109.               

           

1.    这个语句是有是一个比较重要的节点,其功能是根据全局staticbootm_headers_t images结构体的image_info_tos域中记录的os类型来将一个特定OS的内核引导函数入口赋给boot_fn变量。比如我引导的是Linux内核,那么boot_fn就是do_bootm_linux

 

110.              if (boot_fn == NULL) {

111.              if (iflag)

112.              enable_interrupts();

113.              printf ("ERROR: booting os '%s' (%d) is notsupported\n",

114.              genimg_get_os_name(images.os.os), images.os.os);

115.              show_boot_progress (-8);

116.              return 1;

117.              }

118.               

119.              arch_preboot_os();

120.              

121.             boot_fn(0, argc, argv, &images);

122.              

           

1.    如果不出错的话,这个函数应该是不会在返回了,因为在这个函数中会将控制权交由OS的内核。对于引导Linux内核来说,这里其实就是调用do_bootm_linux

 

123.              show_boot_progress (-9);

124.              #ifdef DEBUG

125.              puts ("\n## Control returned to monitor -resetting...\n");

126.              #endif

127.              do_reset (cmdtp, flag, argc, argv);

128.               

129.              return 1;

130.              }

2、分析do_bootm_linux

对我们来说非常重要的do_bootm_linux函数位于:arch/arm/lib/bootm.c

 

Bootm.c(arch\arm\lib):

           

1.    int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)

2.    {

3.    bd_t *bd = gd->bd;

4.    char *s;

5.    int machid = bd->bi_arch_number;

6.    void (*kernel_entry)(int zero, int arch, uint params);

7.     

8.    #ifdef CONFIG_CMDLINE_TAG

9.    char*commandline = getenv ("bootargs");

10.  #endif

11.   

1.    这里获取了生成cmdline标签所需要的字符串

 

12.  if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))

13.  return 1;

14.  

15. s = getenv("machid");

16.  if (s) {

17.  machid = simple_strtoul (s, NULL, 16);

18.  printf ("Usingmachid 0x%x from environment\n", machid);

19.  }

20.   

1.    注意:这里设备ID号可以从环境变量中获得!如果环境变量中有,就会覆盖之前赋值过的设备ID(最终通过r1传递给内核)。

 

21.  show_boot_progress (15);

22.   

23.  #ifdef CONFIG_OF_LIBFDT

24.  if (images->ft_len)

25.  return bootm_linux_fdt(machid, images);

26.  #endif

27.  

28. kernel_entry =(void (*)(int, int, uint))images->ep;

29.   

1.    这里让函数指针指向内核映像的入口地址

 

30.  debug ("## Transferring control to Linux(at address %08lx) ...\n",

31.  (ulong) kernel_entry);

32.  

1.    以下就是我们一直在找的内核标签列表生成代码,从这里看出:U-boot原生只支持部分标签。当然,如果要加的话也很简单。

33.  #if defined (CONFIG_SETUP_MEMORY_TAGS) || \

34.  defined (CONFIG_CMDLINE_TAG) || \

35.  defined (CONFIG_INITRD_TAG) || \

36.  defined (CONFIG_SERIAL_TAG) || \

37.  defined (CONFIG_REVISION_TAG)

38.  setup_start_tag(bd); //设置ATAG_CORE

39.  #ifdefCONFIG_SERIAL_TAG

40.  setup_serial_tag (&params); //设置ATAG_SERIAL依赖板级是否实现了get_board_serial函数

41.  #endif

42.  #ifdefCONFIG_REVISION_TAG

43.  setup_revision_tag (&params);//设置ATAG_REVISION依赖板级是否实现了get_board_rev函数

44.  #endif

45.  #ifdefCONFIG_SETUP_MEMORY_TAGS

46.  setup_memory_tags (bd);//设置ATAG_MEM,依赖于uboot的全局变量bd->bi_dram[i]中的数据

47.  #endif

48.  #ifdefCONFIG_CMDLINE_TAG

49.  setup_commandline_tag (bd, commandline);//设置ATAG_CMDLINE,依赖上面的字符串commandline中的数据

50.  #endif

51.  #ifdefCONFIG_INITRD_TAG

52.  if (images->rd_start && images->rd_end)

53.  setup_initrd_tag (bd, images->rd_start, images->rd_end);//设置ATAG_INITRD

54.  #endif

55.  setup_end_tag(bd);//设置ATAG_NONE

56.  #endif

57.  

58. announce_and_cleanup();

59.   

1.    在进入内核前配置好芯片状态,以符合内核启动要求。

2.    主要是关闭和清理缓存

 

60.  kernel_entry(0,machid, bd->bi_boot_params);

61. /* 不会再返回了 */

62.   

1.    跳入内核入口地址:r1=0r1=machidr2=启动参数指针

 

63.  return 1;

64.  }

 

           

1.    static voidannounce_and_cleanup(void)

2.    {

3.    printf("\nStarting kernel...\n\n");

4.     

5.    #ifdefCONFIG_USB_DEVICE

6.    {

7.    extern voidudc_disconnect(void);

8.    udc_disconnect();

9.    }

10.  #endif

11.  cleanup_before_linux();

12.  }

Cpu.c (arch\arm\cpu\armv7)1958

2011-4-1

 

           

1.    int cleanup_before_linux(void)

2.    {

3.    unsigned int i;

4.     

5.    /*

6.    * this function is calledjust before we call linux

7.    * itprepares the processor for linux

8.    *

9.    * we turnoff caches etc ...

10.  */

11.  disable_interrupts();

12.   

13.  /* turn offI/D-cache */

14.  icache_disable();

15.  dcache_disable();

16.   

17.  /* invalidateI-cache */

18.  cache_flush();

19.   

20.  #ifndefCONFIG_L2_OFF

21.  /* turn offL2 cache */

22.  l2_cache_disable();

23.  /* invalidateL2 cache also */

24.  invalidate_dcache(get_device_type());

25.  #endif

26.  = 0;

27.  /* membarrier to sync up things */

28.  asm("mcr p15,0, %0, c7, c10, 4": :"r"(i));

29.   

30.  #ifndefCONFIG_L2_OFF

31.  l2_cache_enable();

32.  #endif

33.   

34.  return 0;

对于上面启动环境的设定,可参考Documentation/arm/Booting节选Booting中文翻译

 

1.   5. 调用内核映像

2.   ---------------------------

3.    

4.   现有的引导加载程序: 强制

5.   新开发的引导加载程序: 强制

6.    

7.   调用内核映像zImage有两个选择。如果zImge是保存在flash中的,且其为了在flash中直接运行而被正确链接。这样引导加载程序就可以在flash中直接调用zImage

8.   zImage也可以被放在系统RAM(任意位置)中被调用。注意:内核使用映像基地址的前16KB RAM空间来保存页表。建议将映像置于RAM32KB处。

9.    

10.  对于以上任意一种情况,都必须符合以下启动状态:

11.   

12.  - 停止所有DMA设备,这样内存数据就不会因为虚假网络包或磁盘数据而被破坏。这可能可以节省你许多的调试时间。

13.   

14.  - CPU 寄存器配置

15.  r0 = 0,

16.  r1 = (在上面 (3) 中获取的)机器类型码.

17.  r2 = 标签列表在系统RAM中的物理地址,或

18.  设备树块(dtb)在系统RAM中的物理地址

19.   

20.  - CPU 模式

21.  所有形式的中断必须被禁止 (IRQs FIQs)

22.  CPU 必须处于 SVC 模式。 (对于 Angel 调试有特例存在)

23.   

24.  - 缓存, MMUs

25.  MMU 必须关闭。

26.  指令缓存开启或关闭都可以。

27.  数据缓存必须关闭。

28.   

29.  - 引导加载程序应该通过直接跳转到内核映像的第一条指令来调用内核映像。

3、标签生成的函数举例分析:

所有标签生成函数都在arch/arm/lib/bootm.c文件中,其实原理很简单,就是直接往指定的内存地址中写入标签信息。以下以setup_start_tagsetup_memory_tags为例分析:

           

1.   static void setup_start_tag (bd_t *bd)

2.   130 {

3.   131 params = (struct tag *) bd->bi_boot_params//params指向内存中标签列表中的基地址

4.   132 //直接往内存中按照内核定义的标签结构写入信息

5.   133 params->hdr.tag = ATAG_CORE;

6.   134 params->hdr.size = tag_size (tag_core);

7.   135 

8.   136 params->u.core.flags = 0;

9.   137 params->u.core.pagesize = 0;

10.  138 params->u.core.rootdev = 0;

11.  139 //根据本标签的大小数据,params跳到下一标签的起始地址

12.  140params = tag_next (params);

13.  141 }

           

1.   static void setup_memory_tags (bd_t *bd)

2.   146 {

3.   147 int i;

4.   148 //上一个标签已经将params指向了下一标签的基地址,所以这里可以直接使用

5.   149 for (= 0; i < CONFIG_NR_DRAM_BANKS; i++) {

6.   150 params->hdr.tag = ATAG_MEM;

7.   151 params->hdr.size = tag_size (tag_mem32);

8.   152 //根据配置信息和uboot全局变量中的信息创建标签数据

9.   153 params->u.mem.start = bd->bi_dram[i].start;

10.  154 params->u.mem.size = bd->bi_dram[i].size;

11.  155 

12.  156params = tag_next (params);//根据本标签的大小数据,params跳到下一标签的起始地址

13.  157 }

14.  158 }

bootloader完成了引导Linux内核所需要的准备之后将通过直接跳转,将控制权交由内核zImage

 

 

三、内核从特定内存获取参数

在内核zImage开始运行后,首先是进行内核自解压,其过程在之前的博客中有详细介绍:《Linux内核源码分析--内核启动之(1)zImage自解压过程(Linux-3.0 ARMv7》。其中对于内核标签列表的没有处理。在完成内核自解压之后,系统又恢复了bootloader设定的启动状态,将控制权交由解压后的内核。也就是说解压前后,系统启动环境不变。

解压后的内核开始运行后,首先是构架相关的汇编代码,其过程在之前的博客中有详细介绍:《Linux内核源码分析--内核启动之(2)Image内核启动(汇编部分)Linux-3.0ARMv7》。其中对于内核标签列表的处理就是判断r2(内核启动参数)指针的有效性:验证指针指向的数据是否是有效的tagged list或者device tree,如果不是r2清零。

在运行完汇编代码后,就跳入了构架无关的C语言启动代码:init/main.c中的start_kernel函数。在这个函数中开始了对内核启动参数的真正处理。

首先内核必须先要解析tagged list,而它的处理位于:

 

1.   start_kernel-->setup_arch(&command_line);-->mdesc= setup_machine_tags(machine_arch_type);

           

1.   static struct machine_desc * __init setup_machine_tags(unsigned int nr)

2.   {

3.   struct tag *tags = (struct tag *)&init_tags;

           

1.   先让tags指针指向内核默认tagged list(init_tags)

2.    

           

1.   /*

2.   * This holds our defaults.

3.   */

4.   static struct init_tags {

5.   struct tag_header hdr1;

6.   struct tag_core core;

7.   struct tag_header hdr2;

8.   struct tag_mem32 mem;

9.   struct tag_header hdr3;

10.  } init_tags__initdata = {

11.  { tag_size(tag_core), ATAG_CORE },

12.  { 1, PAGE_SIZE, 0xff },

13.  { tag_size(tag_mem32), ATAG_MEM },

14.  { MEM_SIZE },

15.  { 0, ATAG_NONE }

16.  };

这个默认的tagged list实质上只定义了内存的参数

 

4.   struct machine_desc *mdesc = NULL, *p;

5.   char *from = default_command_line;

           

1.   注意这个from的赋值,指向default_command_line,它是默认的内核cmdline,在内核配置的时候可指定。

2.    

3.   Boot options --->

4.   () Default kernel command string

 

6.    

7.   init_tags.mem.start = PHYS_OFFSET;

           

1.   对上面的内核默认的tagged list中的内存起始地址进行初始化。

2.   个人感觉这句有点奇怪,这个赋值为什么不直接放在变量定义的地方一起初始化呢?

 

8.    

9.   /*

10.  在支持的设备列表中找到当前的设备。

11.  */

12.  for_each_machine_desc(p)

13.  if (nr == p->nr) {

14.  printk("Machine:%s\n", p->name);

15.  mdesc = p;

16.  break;

17.  }

           

1.   内核编译的时候可能编译进了多个设备的支持,所以可能存在多个设备的描述结构体。这个通过bootloader传递进来的设备ID来匹配一个设备描述结构体。

 

18.   

19.  if (!mdesc) {

20.  early_print("\nError:unrecognized/unsupported machine ID"

21.  " (r1 =0x%08x).\n\n", nr);

22.  dump_machine_table(); /* does not return */

23.  }

           

1.   如果上面没有找到匹配的设备描述结构体,则打印出错信息,并死循环。

 

24.   

25.  if (__atags_pointer)

26.  tags = phys_to_virt(__atags_pointer);

27.  else if (mdesc->boot_params) {

28.  #ifdefCONFIG_MMU

29.  /*

30.  * 我们依然运行在最小的MMU映射上,

31.  * 这假设设备默认将标签列表放在头1MBRAM中。

32.  任何其他的位置将可能失败,

33.  * 并在此处静静地挂起内核。

34.  */

35.  if (mdesc->boot_params < PHYS_OFFSET ||

36.  mdesc->boot_params >= PHYS_OFFSET + SZ_1M) {

37.  printk(KERN_WARNING

38.  "Defaultboot params at physical 0x%08lx out of reach\n",

39.  mdesc->boot_params);

40.  } else

41.  #endif

42.  {

43.  tags = phys_to_virt(mdesc->boot_params);

44.  }

45.  }

           

1.   如果bootloader传递过来的tagged list有效,则将地址转换成虚拟地址,赋给tags

2.   否则使用设备描述结构体中的数据。例如:

3.   从这里也可以知道,设备描述结构体中的.boot_params数据是可选的,如果bootloader传入的地址没有问题,这里就不会用到。(其他地方是否用的,有待确定)

           

1.   MACHINE_START(MINI6410, "MINI6410")

2.   /* Maintainer: Darius Augulis<augulis.darius@gmail.com> */

3.   .boot_params = S3C64XX_PA_SDRAM + 0x100,

4.   .init_irq = s3c6410_init_irq,

5.   .map_io = mini6410_map_io,

6.   .init_machine = mini6410_machine_init,

7.   .timer = &s3c24xx_timer,

8.   MACHINE_END

 

46.   

47.  #if defined(CONFIG_DEPRECATED_PARAM_STRUCT)

48.  /*

49.  如果传递进来的是一个旧格式的参数, 将他们转换为

50.  * 一个tag list.

51.  */

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

53.  convert_to_tag_list(tags);

54.  #endif

55.   

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

57.  #if defined(CONFIG_OF)

58.  /*

59.  如果定义了 CONFIG_OF , 那么就假假设一个合理的

60.  * 现代系统应该传入一个启动参数

61.  */

62.  early_print("Warning:Neither atags nor dtb found\n");

63.  #endif

64.  tags = (structtag *)&init_tags;

65.  }

           

1.   如果tagged list的第一个tag不是 ATAG_CORE,说明tagged list 不存在或者有问题,打印错误信息并使用默认tagged list

 

66.   

67.  if (mdesc->fixup)

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

           

1.   如果此设备描述结构体中定义了fixup函数,就执行。从这里看出似乎这个函数是用于处理tagged listcmdlinememinfo数据的。

 

69.   

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

71.  if (meminfo.nr_banks != 0)

72.  squash_mem_tags(tags);

           

1.   如果meminfo(其中保存了内存的bank信息)中已经初始化过了,就清除tagged listmem_tags的信息(可导致跟在mem_tags之后的信息也一并失效)

 

73.  save_atags(tags);

           

1.   备份tagged list信息到全局atags_copy

 

74.  parse_tags(tags);

           

1.   逐个解析tag,主要功能是将每个tag的信息保存到内核全局变量中

2.    

3.    

4.   每个tag有对应的内核结构体:

5.               

1.   struct tagtable {

2.   __u32 tag; //tag标识编号

3.   int (*parse)(const struct tag *); //tag信息处理函数(一般是将其中的信息保存到内核全局变量中)

4.   };

 

6.   内核一般通过以下宏来定义一个tagtable结构体:

7.    

           

1.   #define __tag __used__attribute__((__section__(".taglist.init")))

2.   #define __tagtable(tag, fn) \

3.   static struct tagtable __tagtable_##fn __tag = { tag, fn}

 

8.   也就是将所有定义好的tagtable结构体放入一个独立的".taglist.init"段中,使用时用一个for循环就可以遍历了。

 

75.  }

           

1.   如果tagged list中的ATAG_CORE验证通过,就保存并解析tag

 

76.   

77.  /* parse_early_param函数需要 boot_command_line */

78.  strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

           

1.   form指向的字符串拷贝到boot_command_line字符数组中。

 

79.   

80.  return mdesc;

           

1.   返回匹配的设备描述结构体指针。

 

81.  }

tag分析函数重点举例

  • 对内存信息的处理

 

           

1.   static int __init parse_tag_mem32(const structtag *tag)

2.   {

3.   return arm_add_memory(tag->u.mem.start, tag->u.mem.size);

           

1.   tag中的信息添加到全局的meminfo中去:arch/arm/include/asm/setup.h

           

1.   /*

2.   * Memory map description

3.   */

4.   #define NR_BANKS 8

5.    

6.   struct membank {

7.   phys_addr_t start;

8.   unsigned long size;

9.   unsigned int highmem;

10.  };

11.   

12.  structmeminfo {

13.  int nr_banks;

14.  struct membankbank[NR_BANKS];

15.  };

16.   

17.  extern structmeminfo meminfo;

这些信息在内存子系统初始化的时候是会用到的,比如确定高低端内存的分界线。

 

4.   }

5.    

6.   __tagtable(ATAG_MEM, parse_tag_mem32);

  • cmdline的保存

 

           

1.    static int __init parse_tag_cmdline(const struct tag *tag)

2.    {

3.    #if defined(CONFIG_CMDLINE_EXTEND)

4.    strlcat(default_command_line, " ", COMMAND_LINE_SIZE);

5.    strlcat(default_command_line, tag->u.cmdline.cmdline,

6.    COMMAND_LINE_SIZE);

            

1.    如果定义了“CONFIG_CMDLINE_EXTEND”cmdline扩展),内核会将tagcmdline和配置内核时定义的cmdline合并到default_command_line字符数组中。

 

7.    #elif defined(CONFIG_CMDLINE_FORCE)

8.    pr_warning("Ignoring tag cmdline (using thedefault kernel command line)\n");

           

1.    如果定义了“CONFIG_CMDLINE_FORCE”(强制使用配置内核时定义的cmdline),内核会忽略tagcmdline

 

9.    #else

10. strlcpy(default_command_line, tag->u.cmdline.cmdline,

11. COMMAND_LINE_SIZE);

           

1.    如果以上两个配置都没有定义,则使用tagcmdline覆盖到default_command_line字符数组中。

 

12. #endif

13. return 0;

14. }

15.  

16. __tagtable(ATAG_CMDLINE, parse_tag_cmdline);

 

  • 其他相关信息

其他所有的tag解析函数都是大同小异,都是将tag中的信息保存到各内核全局变量结构体中,以备后用。

 

 

四、内核处理cmdline

对于所有的tag中,我们最常用的就是cmdine,所以这里详细解析一下。

从上面的setup_machine_tags函数中我们知道,对于从tag传递到default_command_line中的cmdline字符串,内核又将其复制了一份到boot_command_line中。

在回到了setup_arch函数中之后,内核又把boot_command_line复制了一份到cmd_line字符数组中,并用cmdline_p指针指向这个cmd_line字符数组。

在完成了上面的工作后,cmdline已经从tag中到了多个全局字符数组中,也就是在内存中了,可以开始处理了。

这个cmdline的处理和tag的处理方法是一样的,每个cmdline中的参数都有对应的内核结构体:include/linux/init.h

 

           

1.    struct obs_kernel_param {

2.    const char *str//参数标识字符串指针

3.    int (*setup_func)(char *); //解析函数

4.    intearly; //早期解析标志

5.    };

6.     

7.    /*

8.    * 仅用于真正的核心代码. 正常情况下详见 moduleparam.h.

9.    *

10. * 强制对齐,使得编译器不会将obs_kernel_param "数组"中的元素放置在离

11. * .init.setup较远的地方.

12. */

13. #define __setup_param(str, unique_id, fn, early) \

14. static const char __setup_str_##unique_id[] __initconst \

15. __aligned(1) = str; \

16. static struct obs_kernel_param__setup_##unique_id \

17. __used __section(.init.setup) \

18. __attribute__((aligned((sizeof(long))))) \

19. = { __setup_str_##unique_id, fn, early }

20.  

21. #define __setup(str, fn) \

22. __setup_param(str, fn, fn, 0)

23.  

24. /* 注意: fn 是作为 module_param, 不是 

25. * 当返回非零的时候发出警告!*/

26. #define early_param(str, fn) \

27. __setup_param(str, fn, fn, 1)

28.  

29. /* 依赖 boot_command_line 被设置 */

30. void __init parse_early_param(void);

31. void __init parse_early_options(char *cmdline);

所有需要解析的参数都是通过__setup(str, fn)early_param(str, fn)宏定义的,他们的差别仅在于是否为early参数。

解析函数的作用是根据cmdline中的参数值设置全局变量。例如对“init=”的定义如下:

init/main.c

           

1.    static int __init init_setup(char *str)

2.    {

3.    unsigned int i;

4.     

5.    execute_command = str;

6.    /*

7.    * In case LILO is going to boot us with default commandline,

8.    * it prepends "auto" before the whole cmdline whichmakes

9.    * the shell think it shouldexecute a script with such name.

10. * So we ignore all argumentsentered _before_ init=... [MJ]

11. */

12. for (= 1; i < MAX_INIT_ARGS; i++)

13. argv_init[i] = NULL;

14. return 1;

15. }

16. __setup("init=", init_setup);

其这样目的是为了将已经解析出的“init=”后的字符串指针赋给全局变量execute_command。而这个execute_command就是内核初始化到最后执行的用户空间初始化程序。

 

内核对于cmdline的处理分为两个步骤:早期处理和后期处理。

 

1cmdline的早期处理

对于ARM构架,cmdline的早期处理是在setup_arch函数中的 parse_early_param();,但这个函数定义在init/main.c

 

           

1.    /* 检查早期参数. */

2.    static int __init do_early_param(char *param, char *val)

3.    {

4.    const struct obs_kernel_param *p;

5.     

6.    for (= __setup_start; p < __setup_end; p++) {

7.    if ((p->early && strcmp(param, p->str) == 0) ||

8.    (strcmp(param, "console") == 0 &&

9.    strcmp(p->str, "earlycon") == 0)

10. ) {

11. if (p->setup_func(val) != 0)

12. printk(KERN_WARNING

13. "Malformed early option'%s'\n", param);

14. }

15. }

16. /* 这个阶段我们接受任何异常. */

17. return 0;

18. }

           

1.    此函数通过解析好的参数名及参数值,在上面介绍的“.init.setup”段中搜索匹配的“struct obs_kernel_param”结构体(必须标志为early,也就是用early_param(str, fn)宏定义的结构体),并调用参数处理函数。

 

19.  

20. void __init parse_early_options(char *cmdline)

21. {

22. parse_args("earlyoptions", cmdline, NULL, 0, do_early_param);

           

1.    这里通过统一的parse_args函数处理,此函数原型如下:

2.     

           

1.    int parse_args(const char *name,

2.    char *args,

3.    const struct kernel_param *params,

4.    unsigned num, 

5.    int (*unknown)(char *param, char *val))

 

3.    这个函数的处理方法主要是分离出每个类似“foo=bar,bar2”的形式,再给 next_arg分离出参数名和参数值,并通过参数名在“ const struct kernel_param *params”指向的地址中搜索对应的数据结构,并调用其参数处理函数。如果没有找到就调用最后一个参数“unknown”传递进来的未知参数处理函数。

4.     

5.    由于此处paramsNULL,必然找不到对应的数据结构,所有分离好的参数及参数名都由最后一个函数指针参数指定的函数 do_early_param来处理。也就是上面那个函数。

 

23. }

24.  

25. /* 构架相关代码在早期调用这个函数, 如果没有, 会在解析其他参数前再次调用这个函数。 */

26. void __initparse_early_param(void)

27. {

28. static__initdata int done = 0;

29. static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];

30.  

31. if (done)

32. return;

33.  

34. /* 最终调用 do_early_param. */

35. strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);

           

1.    再次将boot_command_line复制到一个临时变量,并在下面的函数中使用

 

36. parse_early_options(tmp_cmdline);

37. done = 1;

           

1.    对这个静态变量置1,标志着这个函数已经执行过。不需要再次执行。

 

38. }

一个典型的早期参数就是“mem=”,之所以会放在前期处理,是因为内存参数对于系统初始化很重要,在这里处理完后,下面马上就要用到这些数据了。

处理函数如下:

 

           

1.    /*

2.    * Pick out the memory size. We look for mem=size@start,

3.    * where start and size are "size[KkMm]"

4.    */

5.    static int __init early_mem(char *p)

6.    {

7.    static int usermem __initdata = 0;

8.    unsigned long size;

9.    phys_addr_t start;

10. char *endp;

11.  

12. /*

13. * 如果此处指定内存大小, 

14. * 我们会丢弃任何自动生成的大小

15. * 

16. */

17. if (usermem == 0) {

18. usermem = 1;

19. meminfo.nr_banks = 0;

20. }

21.  

           

1.    这里自动情况原有的内存配置信息,如果tagged list中有设置,这里就会清除并覆盖原来的信息。

 

22. start = PHYS_OFFSET;

23. size = memparse(p, &endp);

24. if (*endp == '@')

25. start = memparse(endp + 1, NULL);

26.  

27. arm_add_memory(start,size);

           

1.    这个函数上面介绍过了,就是把获取的内存大小和基地址添加到全局的meminfo结构体中。

 

28.  

29. return 0;

30. }

31. early_param("mem", early_mem);

 

2cmdline的后期分类处理

在上面的早期处理完成之后,系统就继续初始化。在从setup_arch(&command_line);返回不久就将cmdline又进行了一次备份,使用的是bootmem内存分配系统:

           

1.    setup_command_line(command_line);

           

1.    cmdline进行备份和保存:

2.     

3.    /* 为处理的command line备份 (例如eg. 用于 /proc) */

4.     

5.    char*saved_command_line;

6.     

7.    /* 用于参数处理的command line */

8.     

9.    static char*static_command_line;

之后就打印出内核cmdline并解析后期参数和模块参数。源码如下:

 

           

1.    printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);

2.     

           

1.    打印出完整的内核cmdline

3.    parse_early_param();

           

1.    解析内核早期参数,但是对于ARM构架来说,在setup_arch函数中已经调用过了。所以这里什么都不做。

 

4.    parse_args("Booting kernel", static_command_line, __start___param,

5.    __stop___param - __start___param,

6.    &unknown_bootoption);

           

1.    这里调用的parse_args就比较复杂了,我这里简单地分析一下:

2.     

3.    在这个函数主要是一个循环,逐一分析完整的cmdline中的每个参数:

4.     

1.    使用next_arg函数解析出类似“foo=bar,bar2”的形式中的参数名(foo)和参数值(barbar2

2.    使用parse_one根据参数名在内核内建模块的参数处理段(__param)中搜索每一个“struct kernel_param”,是否为某个内核内建模块的参数:

5.     

§ 如果是,则使用搜索到的那个“struct kernel_param”结构体中的参数设置函数“.ops->set”来设置模块的参数

§ 如果不是,就使用unknown_bootoption函数处理,就是到内核的“.init.setup”段搜索,看是不是非早期内核启动参数(使用__setup(str, fn)宏定义的参数)。如果是的话,就用相应的函数来处理,这个和早期参数处理是一样的。如果不是,可能会打印错误信息。

 

到了这里,内核的cmdline处理就到此结束了。只有内置模块才会获取到cmdline中的参数,因为内建模块无法通过其他形式获取参数,不像.ok模块可以在挂载的时候从命令行获取参数。

如果你自己的外置模块(.ok)中需要参数,就算是你在内核启动cmdline中加了参数,模块挂载的时候也是没法自动获取。你必须在使用insmod挂载模块的时候,在最后加上你要的设置的参数信息。或者通过/proc/cmdline获取启动参数,然后用shell命令过滤出需要的参数字符串,并加到insmod命令的最后。

 

Linux内核在启动的时候需要一些参数,以获得当前硬件的信息或者启动所需资源在内存中的位置等等。这些信息可以通过bootloader传递给内核,比较常见的就是cmdline。以前我在启动内核的时候习惯性的通过uboot传递一个cmdline给内核,没有具体的分析这个过程。最近在分析内核启动过程的时候,重新看了一下内核启动参数的传递过程,彻底解决一下在这方面的疑惑。

 

一、bootloader与内核的通讯协议

内核的启动参数其实不仅仅包含在了cmdline中,cmdline不过是bootloader传递给内核的信息中的一部分。bootloader和内核的通信方式根据构架的不同而异。对于ARM构架来说,启动相关的信息可以通过内核文档(Documentation/arm/Booting)获得。其中介绍了bootloader与内核的通信协议,我简单总结如下:

1)数据格式:可以是标签列表(tagged list)或设备树(device tree)。

2)存放地址:r2寄存器中存放的数据所指向的内存地址。

 

在我所做过的开发中,都是使用tagged list的,所以下面以标签列表为例来介绍信息从bootloaderU-boot)到内核(Linux-3.0)的传递过程。

 

内核文档对此的说明,翻译摘要如下:

1.   4a. 设置内核标签列表

2.   --------------------------------

3.    

4.   bootloader必须创建和初始化内核标签列表。一个有效的标签列表以ATAG_CORE标签开始,且以ATAG_NONE标签结束。ATAG_CORE标签可以是空的,也可以是非空。一个空ATAG_CORE标签其 size 域设置为 '2' (0x00000002)ATAG_NONE标签的 size 域必须设置为 '0'

5.    

6.   在列表中可以保存任意数量的标签。对于一个重复的标签是追加到之前标签所携带的信息之后,还是覆盖原来整个信息,是未定义的。某些标签的行为是前者,其他是后者。

7.    

8.   bootloader必须传递一个系统内存的位置和最小值,以及根文件系统位置。因此,最小的标签列表如下所示:

9.    

10.基地址 -> +-----------+

11.| ATAG_CORE | |

12.+-----------+ |

13.| ATAG_MEM | | 地址增长方向

14.+-----------+ |

15.| ATAG_NONE | |

16.+-----------+ v

17. 

18.标签列表应该保存在系统的RAM中。

19. 

20.标签列表必须置于内核自解压和initrd'bootp'程序都不会覆盖的内存区。建议放在RAM的头16KiB中。

(内核中关于ARM启动的标准文档为:Documentation/arm/Booting ,我翻译的版本:《Linux内核文档翻译:Documentation/arm/Booting

 

关于tagged list的数据结构和定义在内核与uboot中都存在,连路径都相同:arch/arm/include/asm/setup.huboot的定义是从内核中拷贝过来的,要和内核一致的,以内核为主。要了解标签列表的具体结构认真阅读这个头文件是必须的。

一个独立的标签的结构大致如下:

 

 

1.   struct tag

2.   +------------------------+

3.   | struct tag_header hdr; | |

4.   | 标签头信息 | |

5.   +------------------------+ |

6.   |union { | |

7.   | struct tag_core core; | |

8.   | struct tag_mem32 mem; | |

9.   | ...... | |

10.| } u; | |

11.标签具体内容 | |

12.此为联合体 | | 地址增长方向

13.根据标签类型确定 | |

14.+------------------------+ v

 

 

           

1.   struct tag_header {

2.   __u32 size; //标签总大小(包括tag_header

3.   __u32 tag; //标签标识

4.   };

比如一个ATAG_CORE在内存中的数据为:

 

1.   +----------+

2.   | 00000005 | |

3.   | 54410001 | |

4.   +----------+ |

5.   | 00000000 | |

6.   | 00000000 | |地址增长方向

7.   | 00000000 | |

8.   +----------+ v

 

1.   当前在内核中接受的标签有:

2.   ATAG_CORE 标签列表开始标志

3.   ATAG_NONE 标签列表结束标志

4.   ATAG_MEM 内存信息标签(可以有多个标签,以标识多个内存区块)

5.   ATAG_VIDEOTEXTVGA文本显示参数标签

6.   ATAG_RAMDISK ramdisk参数标签(位置、大小等)

7.   ATAG_INITRD :压缩的ramdisk参数标签(位置为虚拟地址)

8.   ATAG_INITRD2 :压缩的ramdisk参数标签(位置为物理地址)

9.   ATAG_SERIAL :板子串号标签

10.ATAG_REVISION :板子版本号标签

11.ATAG_VIDEOLFB :帧缓冲初始化参数标签

12.ATAG_CMDLINE command line字符串标签(我们平时设置的启动参数cmdline字符串就放在这个标签中)

13. 

14.特定芯片使用的标签:

15.ATAG_MEMCLK :给footbridge使用的内存时钟标签

16.ATAG_ACORN acorn RiscPC 特定信息

 

二、参数从u-boot到特定内存地址

使用uboot来启动一个Linux内核,通常情况下我们会按照如下步骤执行:

  1. 设置内核启动的command line,也就是设置uboot的环境变量“bootargs”(非必须,如果你要传递给内核cmdline才要设置)
  2. 加载内核映像文到内存指定位置(从SD卡、u盘、网络或flash)
  3. 使用“bootm (内核映像基址)”命令来启动内核

而这个uboot将参数按照协议处理好并放入指定内存地址的过程就发生在“bootm”命令中,下面我们仔细分析下bootm命令的执行。

 

1bootm 命令主体流程

bootm命令的源码位于common/cmd_bootm.c,其中的do_bootm函数就是bootm命令的实现代码。

 

           

1.   /*******************************************************************/

2.   /* bootm - boot application image fromimage in memory */

3.   /*******************************************************************/

4.    

5.   int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])

6.   {

7.   ulong iflag;

8.   ulong load_end = 0;

9.   int ret;

10.  boot_os_fn *boot_fn;

11.  #ifdef CONFIG_NEEDS_MANUAL_RELOC

12.  static int relocated = 0;

13.   

14.  /* 重载启动函数表 */

15.  if (!relocated) {

16.  int i;

17.  for (= 0; i < ARRAY_SIZE(boot_os); i++)

18.  if (boot_os[i] != NULL)

19.  boot_os[i] += gd->reloc_off;

20.  relocated = 1;

21.  }

22.  #endif

23.   

24.  /* 确定我们是否有子命令 */

25.  /* bootm其实是有子命令的,可以自己将bootm的功能手动分步进行,来引导内核 */

26.  if (argc > 1) {

27.  char *endp;

28.   

29.  simple_strtoul(argv[1], &endp, 16);

30.  /* endp pointing to NULL means that argv[1] was just a

31.  * valid number, pass it along to the normal bootm processing

32.  *

33.  * If endp is ':' or '#' assume a FIT identifier so pass

34.  * along for normal processing.

35.  *

36.  * Right now we assume the first arg should never be '-'

37.  */

38.  if ((*endp != 0) && (*endp != ':') && (*endp != '#'))

39.  return do_bootm_subcommand(cmdtp, flag, argc, argv);

40.  }

41.   

42.  if(bootm_start(cmdtp, flag, argc, argv))

43.  return 1;

44.              

1.   这句非常重要,使其这个就是bootm主要功能的开始。

2.    

3.   其主要的目的是从bootm命令指定的内存地址中获取内核uImage的文件头(也就是在用uboot的mkiamge工具处理内核zImage时添加的那64B的数据)。核对并显示出其中包含的信息,并填充一个全局的static bootm_headers_t images结构体的image_info_t os域:

4.   

           

1.    typedef struct image_info {

2.    ulong start, end; /* start/end of blob */ 

3.    ulongimage_start, image_len; /* start of image within blob, len of image */

4.    ulong load; /* load addr for the image */

5.    uint8_tcomp, type, os; /* compression, type of image, ostype */

6.    } image_info_t;

7.    /*这个域保存OS映像的信息,包括uImage的起止地址、所包含内核映像(可能被压缩过)的起始地址和大小、(解压后)内核映像的加载位置以及压缩方式、映像类型和OS类型。*/

平常我们在用bootm驱动内核的时候所看到的如下信息:

8.    ## Booting kernel from Legacy Image at50008000 ...

9.    Image Name: Linux-2.6.37.1

10. Image Type: ARM Linux Kernel Image(uncompressed)

11. Data Size: 3800644 Bytes = 3.6 MiB

12. Load Address: 50008000

13. Entry Point: 50008040

14. Verifying Checksum ... OK

就是这个函数所调用的 boot_get_kernel函数及其子函数根据uImage的文件头打印出来的。

45.

46.  /*

47.  * 我们已经到达了不可返回的位置: 我们正要覆盖所有的异常向量代码。

48.  * 所以我们再也无法简单地从任何失败中恢复

49.  (突然让我想到张信哲的歌词:我们再也回不去了,对不对?)...

50.  */

51.  iflag = disable_interrupts();

52.   

53.  #if defined(CONFIG_CMD_USB)

54.  /*

55.  * turn off USB to prevent the host controller from writing to the

56.  * SDRAM while Linux is booting. This could happen (at least for OHCI

57.  * controller), because the HCCA (Host Controller Communication Area)

58.  * lies within the SDRAM and the host controller writescontinously to

59.  * this area (as The HccaFrameNumber is for example

60.  * updated every 1 ms within the HCCAstructure in For more

61.  * details see the OpenHCIspecification.

62.  */

63.  usb_stop();

64.  #endif

65.  

66. ret = bootm_load_os(images.os,&load_end, 1);

67.   

           

1.    这里是第二个重要的启动过程节点,这个函数的作用是通过获取的文件头信息,将文件头后面所跟的内核映像放置到文件头信息规定的地址(如果是压缩内核,还在此函数中解压。但这个和zImage压缩内核不是一个概念,不要混淆)。

2.     

3.    平常我们在用bootm驱动内核的时候所看到的如下信息:

4.    XIP Kernel Image ... OK

5.    OK

6.    就是这个函数打印出来的。

 

68.  if (ret < 0) {

69.  if (ret == BOOTM_ERR_RESET)

70.  do_reset (cmdtp, flag, argc, argv);

71.  if (ret == BOOTM_ERR_OVERLAP) {

72.  if (images.legacy_hdr_valid) {

73.  if (image_get_type (&images.legacy_hdr_os_copy) == IH_TYPE_MULTI)

74.  puts ("WARNING: legacy format multicomponent "

75.  "image overwritten\n");

76.  } else {

77.  puts ("ERROR: new format imageoverwritten - "

78.  "must RESET the board torecover\n");

79.  show_boot_progress (-113);

80.  do_reset (cmdtp, flag, argc, argv);

81.  }

82.  }

83.  if (ret == BOOTM_ERR_UNIMPLEMENTED) {

84.  if (iflag)

85.  enable_interrupts();

86.  show_boot_progress (-7);

87.  return 1;

88.  }

89.  }

90.   

91.  lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load));

92.   

93.  if (images.os.type == IH_TYPE_STANDALONE) {

94.  if (iflag)

95.  enable_interrupts();

96.  /* This may return when 'autostart' is 'no' */

97.  bootm_start_standalone(iflag, argc, argv);

98.  return 0;

99.  }

100.               

101.              show_boot_progress (8);

102.               

103.              #ifdef CONFIG_SILENT_CONSOLE

104.              if (images.os.os == IH_OS_LINUX)

105.              fixup_silent_linux();

106.              #endif

107.              

108.             boot_fn = boot_os[images.os.os];

109.               

           

1.    这个语句是有是一个比较重要的节点,其功能是根据全局static bootm_headers_t images结构体的image_info_t os域中记录的os类型来将一个特定OS的内核引导函数入口赋给boot_fn变量。比如我引导的是Linux内核,那么boot_fn就是do_bootm_linux

 

110.              if (boot_fn == NULL) {

111.              if (iflag)

112.              enable_interrupts();

113.              printf ("ERROR: booting os '%s' (%d) is notsupported\n",

114.              genimg_get_os_name(images.os.os), images.os.os);

115.              show_boot_progress (-8);

116.              return 1;

117.              }

118.               

119.              arch_preboot_os();

120.              

121.             boot_fn(0, argc, argv, &images);

122.              

           

1.    如果不出错的话,这个函数应该是不会在返回了,因为在这个函数中会将控制权交由OS的内核。对于引导Linux内核来说,这里其实就是调用do_bootm_linux

 

123.              show_boot_progress (-9);

124.              #ifdef DEBUG

125.              puts ("\n## Control returned to monitor -resetting...\n");

126.              #endif

127.              do_reset (cmdtp, flag, argc, argv);

128.               

129.              return 1;

130.              }

2、分析do_bootm_linux

对我们来说非常重要的do_bootm_linux函数位于:arch/arm/lib/bootm.c

 

Bootm.c (arch\arm\lib):

           

1.   int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)

2.   {

3.   bd_t *bd = gd->bd;

4.   char *s;

5.   int machid = bd->bi_arch_number;

6.   void (*kernel_entry)(int zero, int arch, uint params);

7.    

8.   #ifdef CONFIG_CMDLINE_TAG

9.    char *commandline = getenv("bootargs");

10.  #endif

11.   

1.   这里获取了生成cmdline标签所需要的字符串

 

12.  if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))

13.  return 1;

14.  

15. s = getenv ("machid");

16.  if (s) {

17.  machid = simple_strtoul (s, NULL, 16);

18.  printf ("Usingmachid 0x%x from environment\n", machid);

19.  }

20.   

1.   注意:这里设备ID号可以从环境变量中获得!如果环境变量中有,就会覆盖之前赋值过的设备ID(最终通过r1传递给内核)。

 

21.  show_boot_progress (15);

22.   

23.  #ifdef CONFIG_OF_LIBFDT

24.  if (images->ft_len)

25.  return bootm_linux_fdt(machid, images);

26.  #endif

27.  

28. kernel_entry = (void (*)(int, int,uint))images->ep;

29.   

1.   这里让函数指针指向内核映像的入口地址

 

30.  debug ("## Transferring control to Linux(at address %08lx) ...\n",

31.  (ulong) kernel_entry);

32.  

1.    以下就是我们一直在找的内核标签列表生成代码,从这里看出:U-boot原生只支持部分标签。当然,如果要加的话也很简单。

33.  #if defined (CONFIG_SETUP_MEMORY_TAGS) || \

34.  defined (CONFIG_CMDLINE_TAG) || \

35.  defined (CONFIG_INITRD_TAG) || \

36.  defined (CONFIG_SERIAL_TAG) || \

37.  defined (CONFIG_REVISION_TAG)

38.  setup_start_tag(bd); //设置ATAG_CORE

39.  #ifdefCONFIG_SERIAL_TAG

40.  setup_serial_tag (&params); //设置ATAG_SERIAL依赖板级是否实现了get_board_serial函数

41.  #endif

42.  #ifdefCONFIG_REVISION_TAG

43.  setup_revision_tag (&params);//设置ATAG_REVISION依赖板级是否实现了get_board_rev函数

44.  #endif

45.  #ifdefCONFIG_SETUP_MEMORY_TAGS

46.  setup_memory_tags (bd);//设置ATAG_MEM,依赖于uboot的全局变量bd->bi_dram[i]中的数据

47.  #endif

48.  #ifdefCONFIG_CMDLINE_TAG

49.  setup_commandline_tag (bd, commandline);//设置ATAG_CMDLINE,依赖上面的字符串commandline中的数据

50.  #endif

51.  #ifdefCONFIG_INITRD_TAG

52.  if (images->rd_start && images->rd_end)

53.  setup_initrd_tag (bd, images->rd_start, images->rd_end);//设置ATAG_INITRD

54.  #endif

55.  setup_end_tag(bd);//设置ATAG_NONE

56.  #endif

57.  

58. announce_and_cleanup();

59.   

1.   在进入内核前配置好芯片状态,以符合内核启动要求。

2.   主要是关闭和清理缓存

 

60.  kernel_entry(0, machid,bd->bi_boot_params);

61. /* 不会再返回了 */

62.   

1.   跳入内核入口地址:r1=0r1=machidr2=启动参数指针

 

63.  return 1;

64.  }

 

           

1.   static void announce_and_cleanup(void)

2.   {

3.   printf("\nStartingkernel ...\n\n");

4.    

5.   #ifdef CONFIG_USB_DEVICE

6.   {

7.   extern void udc_disconnect(void);

8.   udc_disconnect();

9.   }

10.  #endif

11.  cleanup_before_linux();

12.  }

Cpu.c (arch\arm\cpu\armv7)1958

2011-4-1

 

           

1.   int cleanup_before_linux(void)

2.   {

3.   unsigned int i;

4.    

5.   /*

6.   * this function is called just before we call linux

7.   * it prepares the processor for linux

8.   *

9.   * we turn off caches etc ...

10.  */

11.  disable_interrupts();

12.   

13.  /* turn off I/D-cache */

14.  icache_disable();

15.  dcache_disable();

16.   

17.  /* invalidateI-cache */

18.  cache_flush();

19.   

20.  #ifndefCONFIG_L2_OFF

21.  /* turn offL2 cache */

22.  l2_cache_disable();

23.  /* invalidateL2 cache also */

24.  invalidate_dcache(get_device_type());

25.  #endif

26.  = 0;

27.  /* membarrier to sync up things */

28.  asm("mcr p15,0, %0, c7, c10, 4": :"r"(i));

29.   

30.  #ifndefCONFIG_L2_OFF

31.  l2_cache_enable();

32.  #endif

33.   

34.  return 0;

对于上面启动环境的设定,可参考Documentation/arm/Booting节选Booting中文翻译

 

1.    5. 调用内核映像

2.    ---------------------------

3.     

4.    现有的引导加载程序: 强制

5.    新开发的引导加载程序: 强制

6.     

7.    调用内核映像zImage有两个选择。如果zImge是保存在flash中的,且其为了在flash中直接运行而被正确链接。这样引导加载程序就可以在flash中直接调用zImage

8.    zImage也可以被放在系统RAM(任意位置)中被调用。注意:内核使用映像基地址的前16KB RAM空间来保存页表。建议将映像置于RAM32KB处。

9.     

10.  对于以上任意一种情况,都必须符合以下启动状态:

11.   

12.  - 停止所有DMA设备,这样内存数据就不会因为虚假网络包或磁盘数据而被破坏。这可能可以节省你许多的调试时间。

13.   

14.  - CPU 寄存器配置

15.  r0 = 0,

16.  r1 = (在上面 (3) 中获取的)机器类型码.

17.  r2 = 标签列表在系统RAM中的物理地址,或

18.  设备树块(dtb)在系统RAM中的物理地址

19.   

20.  - CPU 模式

21.  所有形式的中断必须被禁止 (IRQs FIQs)

22.  CPU 必须处于 SVC 模式。 (对于 Angel 调试有特例存在)

23.   

24.  - 缓存, MMUs

25.  MMU 必须关闭。

26.  指令缓存开启或关闭都可以。

27.  数据缓存必须关闭。

28.   

29.  - 引导加载程序应该通过直接跳转到内核映像的第一条指令来调用内核映像。

3、标签生成的函数举例分析:

所有标签生成函数都在arch/arm/lib/bootm.c文件中,其实原理很简单,就是直接往指定的内存地址中写入标签信息。以下以setup_start_tagsetup_memory_tags为例分析:

           

1.    static voidsetup_start_tag (bd_t *bd)

2.    130 {

3.    131 params = (struct tag *) bd->bi_boot_params//params指向内存中标签列表中的基地址

4.    132 //直接往内存中按照内核定义的标签结构写入信息

5.    133 params->hdr.tag = ATAG_CORE;

6.    134 params->hdr.size = tag_size (tag_core);

7.    135 

8.    136 params->u.core.flags = 0;

9.    137 params->u.core.pagesize = 0;

10.  138 params->u.core.rootdev = 0;

11.  139 //根据本标签的大小数据,params跳到下一标签的起始地址

12.  140 params = tag_next (params);

13.  141 }

           

1.    static voidsetup_memory_tags (bd_t *bd)

2.    146 {

3.    147 int i;

4.    148 //上一个标签已经将params指向了下一标签的基地址,所以这里可以直接使用

5.    149 for (= 0; i < CONFIG_NR_DRAM_BANKS; i++) {

6.    150 params->hdr.tag = ATAG_MEM;

7.    151 params->hdr.size = tag_size (tag_mem32);

8.    152 //根据配置信息和uboot全局变量中的信息创建标签数据

9.    153 params->u.mem.start = bd->bi_dram[i].start;

10.  154 params->u.mem.size = bd->bi_dram[i].size;

11.  155 

12.  156 params = tag_next (params);//根据本标签的大小数据,params跳到下一标签的起始地址

13.  157 }

14.  158 }

bootloader完成了引导Linux内核所需要的准备之后将通过直接跳转,将控制权交由内核zImage

 

 

三、内核从特定内存获取参数

在内核zImage开始运行后,首先是进行内核自解压,其过程在之前的博客中有详细介绍:《Linux内核源码分析--内核启动之(1)zImage自解压过程(Linux-3.0 ARMv7》。其中对于内核标签列表的没有处理。在完成内核自解压之后,系统又恢复了bootloader设定的启动状态,将控制权交由解压后的内核。也就是说解压前后,系统启动环境不变。

解压后的内核开始运行后,首先是构架相关的汇编代码,其过程在之前的博客中有详细介绍:《Linux内核源码分析--内核启动之(2)Image内核启动(汇编部分)Linux-3.0 ARMv7》。其中对于内核标签列表的处理就是判断r2(内核启动参数)指针的有效性:验证指针指向的数据是否是有效的tagged list或者device tree,如果不是r2清零。

在运行完汇编代码后,就跳入了构架无关的C语言启动代码:init/main.c中的start_kernel函数。在这个函数中开始了对内核启动参数的真正处理。

首先内核必须先要解析tagged list,而它的处理位于:

 

1.    start_kernel-->setup_arch(&command_line);-->mdesc= setup_machine_tags(machine_arch_type);

           

1.    static structmachine_desc * __init setup_machine_tags(unsigned int nr)

2.    {

3.    struct tag *tags = (struct tag *)&init_tags;

           

1.    先让tags指针指向内核默认taggedlist(init_tags)

2.     

           

1.    /*

2.    * This holdsour defaults.

3.    */

4.    static structinit_tags {

5.    structtag_header hdr1;

6.    struct tag_corecore;

7.    structtag_header hdr2;

8.    struct tag_mem32mem;

9.    structtag_header hdr3;

10.  } init_tags__initdata = {

11.  { tag_size(tag_core), ATAG_CORE },

12.  { 1, PAGE_SIZE, 0xff },

13.  { tag_size(tag_mem32), ATAG_MEM },

14.  { MEM_SIZE },

15.  { 0, ATAG_NONE }

16.  };

这个默认的tagged list实质上只定义了内存的参数

 

4.    structmachine_desc *mdesc = NULL, *p;

5.    char *from = default_command_line;

           

1.    注意这个from的赋值,指向default_command_line,它是默认的内核cmdline,在内核配置的时候可指定。

2.     

3.    Boot options--->

4.    () Defaultkernel command string

 

6.     

7.    init_tags.mem.start = PHYS_OFFSET;

           

1.    对上面的内核默认的tagged list中的内存起始地址进行初始化。

2.    个人感觉这句有点奇怪,这个赋值为什么不直接放在变量定义的地方一起初始化呢?

 

8.     

9.    /*

10.  在支持的设备列表中找到当前的设备。

11.  */

12.  for_each_machine_desc(p)

13.  if (nr == p->nr) {

14.  printk("Machine:%s\n", p->name);

15.  mdesc = p;

16.  break;

17.  }

           

1.    内核编译的时候可能编译进了多个设备的支持,所以可能存在多个设备的描述结构体。这个通过bootloader传递进来的设备ID来匹配一个设备描述结构体。

 

18.   

19.  if (!mdesc) {

20.  early_print("\nError:unrecognized/unsupported machine ID"

21.  " (r1 =0x%08x).\n\n", nr);

22.  dump_machine_table(); /* does not return */

23.  }

           

1.    如果上面没有找到匹配的设备描述结构体,则打印出错信息,并死循环。

 

24.   

25.  if (__atags_pointer)

26.  tags = phys_to_virt(__atags_pointer);

27.  else if (mdesc->boot_params) {

28.  #ifdefCONFIG_MMU

29.  /*

30.  * 我们依然运行在最小的MMU映射上,

31.  * 这假设设备默认将标签列表放在头1MBRAM中。

32.  任何其他的位置将可能失败,

33.  * 并在此处静静地挂起内核。

34.  */

35.  if (mdesc->boot_params < PHYS_OFFSET ||

36.  mdesc->boot_params >= PHYS_OFFSET + SZ_1M) {

37.  printk(KERN_WARNING

38.  "Defaultboot params at physical 0x%08lx out of reach\n",

39.  mdesc->boot_params);

40.  } else

41.  #endif

42.  {

43.  tags = phys_to_virt(mdesc->boot_params);

44.  }

45.  }

           

1.    如果bootloader传递过来的tagged list有效,则将地址转换成虚拟地址,赋给tags

2.    否则使用设备描述结构体中的数据。例如:

3.    从这里也可以知道,设备描述结构体中的.boot_params数据是可选的,如果bootloader传入的地址没有问题,这里就不会用到。(其他地方是否用的,有待确定)

           

1.    MACHINE_START(MINI6410,"MINI6410")

2.    /* Maintainer:Darius Augulis <augulis.darius@gmail.com> */

3.    .boot_params =S3C64XX_PA_SDRAM + 0x100,

4.    .init_irq =s3c6410_init_irq,

5.    .map_io =mini6410_map_io,

6.    .init_machine =mini6410_machine_init,

7.    .timer =&s3c24xx_timer,

8.    MACHINE_END

 

46.   

47.  #if defined(CONFIG_DEPRECATED_PARAM_STRUCT)

48.  /*

49.  如果传递进来的是一个旧格式的参数, 将他们转换为

50.  * 一个tag list.

51.  */

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

53.  convert_to_tag_list(tags);

54.  #endif

55.   

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

57.  #if defined(CONFIG_OF)

58.  /*

59.  如果定义了 CONFIG_OF , 那么就假假设一个合理的

60.  * 现代系统应该传入一个启动参数

61.  */

62.  early_print("Warning:Neither atags nor dtb found\n");

63.  #endif

64.  tags = (struct tag *)&init_tags;

65.  }

           

1.    如果tagged list的第一个tag不是 ATAG_CORE,说明tagged list 不存在或者有问题,打印错误信息并使用默认tagged list

 

66.   

67.  if (mdesc->fixup)

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

           

1.    如果此设备描述结构体中定义了fixup函数,就执行。从这里看出似乎这个函数是用于处理tagged listcmdlinememinfo数据的。

 

69.   

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

71.  if (meminfo.nr_banks != 0)

72.  squash_mem_tags(tags);

           

1.    如果meminfo(其中保存了内存的bank信息)中已经初始化过了,就清除tagged listmem_tags的信息(可导致跟在mem_tags之后的信息也一并失效)

 

73.  save_atags(tags);

           

1.    备份tagged list信息到全局atags_copy

 

74.  parse_tags(tags);

           

1.    逐个解析tag,主要功能是将每个tag的信息保存到内核全局变量中

2.     

3.     

4.    每个tag有对应的内核结构体:

5.                

1.    struct tagtable{

2.    __u32 tag; //tag标识编号

3.    int(*parse)(const struct tag *); //tag信息处理函数(一般是将其中的信息保存到内核全局变量中)

4.    };

 

6.    内核一般通过以下宏来定义一个tagtable结构体:

7.     

           

1.    #define __tag__used __attribute__((__section__(".taglist.init")))

2.    #define__tagtable(tag, fn) \

3.    static structtagtable __tagtable_##fn __tag = { tag, fn }

 

8.    也就是将所有定义好的tagtable结构体放入一个独立的".taglist.init"段中,使用时用一个for循环就可以遍历了。

 

75.  }

           

1.    如果tagged list中的ATAG_CORE验证通过,就保存并解析tag

 

76.   

77.  /* parse_early_param函数需要 boot_command_line */

78.  strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

           

1.    form指向的字符串拷贝到boot_command_line字符数组中。

 

79.   

80.  return mdesc;

           

1.    返回匹配的设备描述结构体指针。

 

81.  }

tag分析函数重点举例

  • 对内存信息的处理

 

           

1.    static int __initparse_tag_mem32(const struct tag *tag)

2.    {

3.    returnarm_add_memory(tag->u.mem.start, tag->u.mem.size);

           

1.    tag中的信息添加到全局的meminfo中去:arch/arm/include/asm/setup.h

           

1.    /*

2.    * Memory mapdescription

3.    */

4.    #define NR_BANKS8

5.     

6.    structmembank {

7.    phys_addr_tstart;

8.    unsigned longsize;

9.    unsigned int highmem;

10.  };

11.   

12.  structmeminfo {

13.  int nr_banks;

14.  struct membankbank[NR_BANKS];

15.  };

16.   

17.  extern structmeminfo meminfo;

这些信息在内存子系统初始化的时候是会用到的,比如确定高低端内存的分界线。

 

4.    }

5.     

6.    __tagtable(ATAG_MEM, parse_tag_mem32);

  • cmdline的保存

 

           

1.   static int __init parse_tag_cmdline(const struct tag *tag)

2.   {

3.   #if defined(CONFIG_CMDLINE_EXTEND)

4.   strlcat(default_command_line, " ", COMMAND_LINE_SIZE);

5.   strlcat(default_command_line, tag->u.cmdline.cmdline,

6.   COMMAND_LINE_SIZE);

           

1.   如果定义了“CONFIG_CMDLINE_EXTEND”cmdline扩展),内核会将tagcmdline和配置内核时定义的cmdline合并到default_command_line字符数组中。

 

7.   #elif defined(CONFIG_CMDLINE_FORCE)

8.   pr_warning("Ignoring tag cmdline (using the default kernelcommand line)\n");

           

1.   如果定义了“CONFIG_CMDLINE_FORCE”(强制使用配置内核时定义的cmdline),内核会忽略tagcmdline

 

9.   #else

10.strlcpy(default_command_line, tag->u.cmdline.cmdline,

11.COMMAND_LINE_SIZE);

           

1.   如果以上两个配置都没有定义,则使用tagcmdline覆盖到default_command_line字符数组中。

 

12.#endif

13.return 0;

14.}

15. 

16.__tagtable(ATAG_CMDLINE, parse_tag_cmdline);

 

  • 其他相关信息

其他所有的tag解析函数都是大同小异,都是将tag中的信息保存到各内核全局变量结构体中,以备后用。

 

 

四、内核处理cmdline

对于所有的tag中,我们最常用的就是cmdine,所以这里详细解析一下。

从上面的setup_machine_tags函数中我们知道,对于从tag传递到default_command_line中的cmdline字符串,内核又将其复制了一份到boot_command_line中。

在回到了setup_arch函数中之后,内核又把boot_command_line复制了一份到cmd_line字符数组中,并用cmdline_p指针指向这个cmd_line字符数组。

在完成了上面的工作后,cmdline已经从tag中到了多个全局字符数组中,也就是在内存中了,可以开始处理了。

这个cmdline的处理和tag的处理方法是一样的,每个cmdline中的参数都有对应的内核结构体:include/linux/init.h

 

           

1.   struct obs_kernel_param {

2.   const char *str//参数标识字符串指针

3.   int (*setup_func)(char *); //解析函数

4.   int early; //早期解析标志

5.   };

6.    

7.   /*

8.   * 仅用于真正的核心代码. 正常情况下详见 moduleparam.h.

9.   *

10.* 强制对齐,使得编译器不会将obs_kernel_param "数组"中的元素放置在离

11.* .init.setup较远的地方.

12.*/

13.#define __setup_param(str, unique_id, fn, early) \

14.static const char __setup_str_##unique_id[] __initconst \

15.__aligned(1) = str; \

16.static struct obs_kernel_param __setup_##unique_id \

17.__used __section(.init.setup) \

18.__attribute__((aligned((sizeof(long))))) \

19.= { __setup_str_##unique_id, fn, early }

20. 

21.#define __setup(str, fn) \

22.__setup_param(str, fn, fn, 0)

23. 

24./* 注意: fn 是作为 module_param, 不是 

25.* 当返回非零的时候发出警告!*/

26.#define early_param(str, fn) \

27.__setup_param(str, fn, fn, 1)

28. 

29./* 依赖 boot_command_line 被设置 */

30.void __init parse_early_param(void);

31.void __init parse_early_options(char *cmdline);

所有需要解析的参数都是通过__setup(str, fn)early_param(str, fn)宏定义的,他们的差别仅在于是否为early参数。

解析函数的作用是根据cmdline中的参数值设置全局变量。例如对“init=”的定义如下:

init/main.c

           

1.    static int __init init_setup(char *str)

2.    {

3.    unsigned int i;

4.     

5.    execute_command = str;

6.    /*

7.    * In case LILO is going to boot us with default command line,

8.    * it prepends "auto" before the whole cmdline whichmakes

9.    * the shell think it should executea script with such name.

10. * So we ignore all arguments entered_before_ init=... [MJ]

11. */

12. for (= 1; i < MAX_INIT_ARGS; i++)

13. argv_init[i] = NULL;

14. return 1;

15. }

16. __setup("init=", init_setup);

其这样目的是为了将已经解析出的“init=”后的字符串指针赋给全局变量execute_command。而这个execute_command就是内核初始化到最后执行的用户空间初始化程序。

 

内核对于cmdline的处理分为两个步骤:早期处理和后期处理。

 

1cmdline的早期处理

对于ARM构架,cmdline的早期处理是在setup_arch函数中的 parse_early_param();,但这个函数定义在init/main.c

 

           

1.   /* 检查早期参数. */

2.   static int __init do_early_param(char *param, char *val)

3.   {

4.   const struct obs_kernel_param *p;

5.    

6.   for (= __setup_start; p < __setup_end; p++) {

7.   if ((p->early && strcmp(param, p->str) == 0) ||

8.   (strcmp(param, "console") == 0 &&

9.   strcmp(p->str, "earlycon") == 0)

10.) {

11.if (p->setup_func(val) != 0)

12.printk(KERN_WARNING

13."Malformed early option '%s'\n", param);

14.}

15.}

16./* 这个阶段我们接受任何异常. */

17.return 0;

18.}

           

1.   此函数通过解析好的参数名及参数值,在上面介绍的“.init.setup”段中搜索匹配的“struct obs_kernel_param”结构体(必须标志为early,也就是用early_param(str, fn)宏定义的结构体),并调用参数处理函数。

 

19. 

20.void __init parse_early_options(char *cmdline)

21.{

22.parse_args("early options", cmdline, NULL, 0,do_early_param);

           

1.   这里通过统一的parse_args函数处理,此函数原型如下:

2.    

           

1.   int parse_args(const char *name,

2.   char *args,

3.   const struct kernel_param *params,

4.   unsigned num, 

5.   int (*unknown)(char *param, char *val))

 

3.   这个函数的处理方法主要是分离出每个类似“foo=bar,bar2”的形式,再给 next_arg分离出参数名和参数值,并通过参数名在“ const struct kernel_param *params”指向的地址中搜索对应的数据结构,并调用其参数处理函数。如果没有找到就调用最后一个参数“unknown”传递进来的未知参数处理函数。

4.    

5.   由于此处paramsNULL,必然找不到对应的数据结构,所有分离好的参数及参数名都由最后一个函数指针参数指定的函数 do_early_param来处理。也就是上面那个函数。

 

23.}

24. 

25./* 构架相关代码在早期调用这个函数, 如果没有, 会在解析其他参数前再次调用这个函数。 */

26.void __init parse_early_param(void)

27.{

28.static __initdata int done = 0;

29.static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];

30. 

31.if (done)

32.return;

33. 

34./* 最终调用 do_early_param. */

35.strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);

            

1.   再次将boot_command_line复制到一个临时变量,并在下面的函数中使用

 

36.parse_early_options(tmp_cmdline);

37.done = 1;

           

1.   对这个静态变量置1,标志着这个函数已经执行过。不需要再次执行。

 

38.}

一个典型的早期参数就是“mem=”,之所以会放在前期处理,是因为内存参数对于系统初始化很重要,在这里处理完后,下面马上就要用到这些数据了。

处理函数如下:

 

           

1.   /*

2.   * Pick out the memory size. We look for mem=size@start,

3.   * where start and size are "size[KkMm]"

4.   */

5.   static int __init early_mem(char *p)

6.   {

7.   static int usermem __initdata = 0;

8.   unsigned long size;

9.   phys_addr_t start;

10.char *endp;

11. 

12./*

13.* 如果此处指定内存大小, 

14.* 我们会丢弃任何自动生成的大小

15.* 

16.*/

17.if (usermem == 0) {

18.usermem = 1;

19.meminfo.nr_banks = 0;

20.}

21. 

           

1.   这里自动情况原有的内存配置信息,如果tagged list中有设置,这里就会清除并覆盖原来的信息。

 

22.start = PHYS_OFFSET;

23.size = memparse(p, &endp);

24.if (*endp == '@')

25.start = memparse(endp + 1, NULL);

26. 

27.arm_add_memory(start, size);

           

1.   这个函数上面介绍过了,就是把获取的内存大小和基地址添加到全局的meminfo结构体中。

 

28. 

29.return 0;

30.}

31.early_param("mem", early_mem);

 

2cmdline的后期分类处理

在上面的早期处理完成之后,系统就继续初始化。在从setup_arch(&command_line);返回不久就将cmdline又进行了一次备份,使用的是bootmem内存分配系统:

           

1.   setup_command_line(command_line);

           

1.   cmdline进行备份和保存:

2.    

3.   /* 为处理的command line备份 (例如eg. 用于 /proc) */

4.    

5.   char *saved_command_line;

6.    

7.   /* 用于参数处理的command line */

8.    

9.   static char *static_command_line;

之后就打印出内核cmdline并解析后期参数和模块参数。源码如下:

 

           

1.   printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);

2.    

           

1.   打印出完整的内核cmdline

3.   parse_early_param();

           

1.   解析内核早期参数,但是对于ARM构架来说,在setup_arch函数中已经调用过了。所以这里什么都不做。

 

4.   parse_args("Booting kernel", static_command_line, __start___param,

5.   __stop___param - __start___param,

6.   &unknown_bootoption);

           

1.   这里调用的parse_args就比较复杂了,我这里简单地分析一下:

2.    

3.   在这个函数主要是一个循环,逐一分析完整的cmdline中的每个参数:

4.    

1.   使用next_arg函数解析出类似“foo=bar,bar2”的形式中的参数名(foo)和参数值(barbar2

2.   使用parse_one根据参数名在内核内建模块的参数处理段(__param)中搜索每一个“struct kernel_param”,是否为某个内核内建模块的参数:

5.    

§  如果是,则使用搜索到的那个“struct kernel_param”结构体中的参数设置函数“.ops->set”来设置模块的参数

§  如果不是,就使用unknown_bootoption函数处理,就是到内核的“.init.setup”段搜索,看是不是非早期内核启动参数(使用__setup(str, fn)宏定义的参数)。如果是的话,就用相应的函数来处理,这个和早期参数处理是一样的。如果不是,可能会打印错误信息。

 

到了这里,内核的cmdline处理就到此结束了。只有内置模块才会获取到cmdline中的参数,因为内建模块无法通过其他形式获取参数,不像.ok模块可以在挂载的时候从命令行获取参数。

如果你自己的外置模块(.ok)中需要参数,就算是你在内核启动cmdline中加了参数,模块挂载的时候也是没法自动获取。你必须在使用insmod挂载模块的时候,在最后加上你要的设置的参数信息。或者通过/proc/cmdline获取启动参数,然后用shell命令过滤出需要的参数字符串,并加到insmod命令的最后。

 

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值