300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > uboot 引导linux内核 参数mem=size 内核启动参数机制学习笔记

uboot 引导linux内核 参数mem=size 内核启动参数机制学习笔记

时间:2020-09-08 00:43:07

相关推荐

uboot 引导linux内核 参数mem=size 内核启动参数机制学习笔记

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

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

1、cmdline的早期处理

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

点击(此处)折叠或打开

/*检查早期参数.

*/

static int __init do_early_param(char

*param, char

*val)

{

const struct obs_kernel_param

*p;

for (p

= __setup_start; p

< __setup_end; p++)

{

if ((p->early

&& strcmp(param, p->str)

== 0)

||

(strcmp(param,

"console")

== 0 &&

strcmp(p->str,

"earlycon")

== 0)

) {

if

(p->setup_func(val)

!= 0)

printk(KERN_WARNING

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

}

}

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

*/

return 0;

}

点击(此处)折叠或打开

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

void __init parse_early_options(char

*cmdline)

{

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

点击(此处)折叠或打开

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

点击(此处)折叠或打开

int parse_args(const char

*name,

char *args,

const struct kernel_param

*params,

unsigned num,

int

(*unknown)(char

*param, char

*val))

这个函数的处理方法主要是分离出每个类似“foo=bar,bar2”的形式,再给next_arg分离出参数名和参数值,并通过参数名在“const struct kernel_param *params”指向的地址中搜索对应的数据结构,并调用其参数处理函数。如果没有找到就调用最后一个参数“unknown”传递进来的未知参数处理函数。由于此处params为NULL,必然找不到对应的数据结构,所有分离好的参数及参数名都由最后一个函数指针参数指定的函数do_early_param来处理。也就是上面那个函数。

}

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

void __init parse_early_param(void)

{

static __initdata int done = 0;

static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];

if (done)

return;

/* 最终调用do_early_param.

*/

strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);

点击(此处)折叠或打开

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

parse_early_options(tmp_cmdline);

done = 1;

点击(此处)折叠或打开

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

}

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

处理函数如下:

点击(此处)折叠或打开

/*

* Pick out the memory size. We look

for mem=size@start,

* where start

and size are "size[KkMm]"

*/

static int __init early_mem(char

*p)

{

static int usermem __initdata

= 0;

unsigned long size;

phys_addr_t start;

char *endp;

/*

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

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

*

*/

if (usermem

== 0)

{

usermem = 1;

meminfo.nr_banks

= 0;

}

点击(此处)折叠或打开

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

start = PHYS_OFFSET;

size = memparse(p,

&endp);

if (*endp

==

'@')

start = memparse(endp

+ 1,

NULL);

arm_add_memory(start, size);

点击(此处)折叠或打开

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

return 0;

}

early_param("mem", early_mem);

2、cmdline的后期分类处理

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

点击(此处)折叠或打开

setup_command_line(command_line);

点击(此处)折叠或打开

对cmdline进行备份和保存:

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

char *saved_command_line;

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

static char *static_command_line;

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

点击(此处)折叠或打开

printk(KERN_NOTICE

"Kernel command line: %sn", boot_command_line);

点击(此处)折叠或打开

打印出完整的内核cmdline

parse_early_param();

点击(此处)折叠或打开

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

parse_args("Booting kernel", static_command_line, __start___param,

__stop___param - __start___param,

&unknown_bootoption);

点击(此处)折叠或打开

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

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

使用next_arg函数解析出类似“foo=bar,bar2”的形式中的参数名(foo)和参数值(bar和bar2)使用parse_one根据参数名在内核内建模块的参数处理段(__param)中搜索每一个“struct kernel_param”,是否为某个内核内建模块的参数:

如果是,则使用搜索到的那个“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的,所以下面以标签列表为例来介绍信息从bootloader(U-boot)到内核(Linux-3.0)的传递过程。

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

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

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

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

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

基地址 ->+-----------+ | ATAG_CORE | | +-----------+ | | ATAG_MEM | | 地址增长方向 +-----------+ | | ATAG_NONE | | +-----------+ v

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

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

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

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

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

struct tag+------------------------+| struct tag_header hdr; | || 标签头信息 | |+------------------------+ ||union { | || struct tag_core core; | || struct tag_mem32 mem; | || ...... | || } u; | || 标签具体内容 | || 此为联合体 | | 地址增长方向| 根据标签类型确定 | |+------------------------+ v

点击(此处)折叠或打开

struct tag_header {

__u32 size;

//标签总大小(包括tag_header)

__u32 tag; //标签标识

};

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

+----------+| 00000005 | || 54410001 | |+----------+ || 00000000 | || 00000000 | |地址增长方向| 00000000 | |+----------+ v

当前在内核中接受的标签有:ATAG_CORE : 标签列表开始标志ATAG_NONE : 标签列表结束标志ATAG_MEM : 内存信息标签(可以有多个标签,以标识多个内存区块)ATAG_VIDEOTEXT:VGA文本显示参数标签ATAG_RAMDISK :ramdisk参数标签(位置、大小等)ATAG_INITRD :压缩的ramdisk参数标签(位置为虚拟地址)ATAG_INITRD2 :压缩的ramdisk参数标签(位置为物理地址)ATAG_SERIAL :板子串号标签ATAG_REVISION :板子版本号标签ATAG_VIDEOLFB :帧缓冲初始化参数标签ATAG_CMDLINE :command line字符串标签(我们平时设置的启动参数cmdline字符串就放在这个标签中)

特定芯片使用的标签:ATAG_MEMCLK :给footbridge使用的内存时钟标签ATAG_ACORN :acorn RiscPC 特定信息

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

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

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

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

1、bootm 命令主体流程

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

点击(此处)折叠或打开

/*******************************************************************/

/* bootm

- boot application image from image

in memory */

/*******************************************************************/

int do_bootm

(cmd_tbl_t *cmdtp,

int flag,

int argc, char

* const argv[])

{

ulongiflag;

ulongload_end

= 0;

intret;

boot_os_fn*boot_fn;

#ifdef CONFIG_NEEDS_MANUAL_RELOC

static

int relocated = 0;

/* 重载启动函数表 */if

(!relocated)

{

int i;

for

(i = 0; i

< ARRAY_SIZE(boot_os); i++)

if

(boot_os[i]

!=

NULL)

boot_os[i]

+= gd->reloc_off;

relocated

= 1;

}

#endif

/* 确定我们是否有子命令 *//* bootm其实是有子命令的,可以自己将bootm的功能手动分步进行,来引导内核 */if

(argc > 1)

{

char

*endp;

simple_strtoul(argv[1],

&endp, 16);

/* endp pointing

to NULL means that argv[1] was just a

* valid number, pass it along

to the normal bootm processing

*

* If endp is

':'

or '#' assume a FIT identifier so pass

* along for normal processing.

*

* Right now we assume the first arg should never be

'-'

*/

if

((*endp

!= 0)

&&

(*endp !=

':')

&&

(*endp !=

'#'))

return do_bootm_subcommand(cmdtp, flag, argc,

argv);

}

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

return 1;

点击(此处)折叠或打开

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

其主要的目的是从bootm命令指定的内存地址中获取内核uImage的文件头(也就是在用uboot的mkiamge工具处理内核zImage时添加的那64B的数据)。核对并显示出其中包含

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。