bootload在启动linux内核时可以传递一些参数,对linux内核进行配置。
bootload 用的是uboot_1_1_4
linux内核用的是linux_2_6_24
一、uboot启动内核
当在uboot下敲bootm,或啥都不做等上N秒后,uboot会调用do_bootm函数
文件uboot_1_1_4/common/cmd_bootm.c
- int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
- {
- image_header_t *hdr = &header; //这里就是linux镜像的头部了
- hdr->ih_arch里存放的是cpu架构类型
- //可选值有IH_CPU_PPC、IH_CPU_ARM、IH_CPU_I386、IH_CPU_MIPS、IH_CPU_NIOS等等
- hdr->ih_os里操作系统类型
- //可选值有IH_OS_LINUX、IH_OS_NETBSD、IH_OS_LYNXOS、IH_OS_RTEMS等等
- //最终,我们调用do_bootm_linux()
- }
- int do_bootm_linux(......)
- {
- if ((s = getenv("bootargs")) == NULL)
- s = "";
- strcpy (cmdline, s);
- strcat(cmdline," ");
- strcat(cmdline,config.cmdline);
- cmd_start = (ulong)&cmdline[0];
- cmd_end = cmd_start + strlen(cmdline);
- kernel = (void (*)(bd_t *, ulong, ulong, ulong, ulong))hdr->ih_ep;
- /*
- * Linux Kernel Parameters:
- * r3: ptr to board info data
- * r4: initrd_start or 0 if no initrd
- * r5: initrd_end - unused if r4 is 0
- * r6: Start of command line string
- * r7: End of command line string
- */
- (*kernel) (kbd, initrd_start, initrd_end, cmd_start, cmd_end);
- }
linux内核启动需要5个参数,则使用r3 -- r7 这五个GPR寄存器(为啥? 看这个帖子)
其中r6,r7里面存的就是cmd_line的起始地址和长度
cmd_line来自环境变量bootargs、全局变量 config.cmdline
- typedef struct CE_CONFIG {
- char location[32]; /* CE location */
- char status[32]; /* CE status */
- char hostname[32]; /* host name */
- char kernel[32]; /* kernel file name */
- char image[32]; /* ramdisk file name */
- char masterip[8]; /* kernel multicast IP */
- char slaveip[8]; /* ramdisk multicast IP */
- char type[32]; /* CE type */
- char pce[16];
- long imagesize;
- char cmdline[CMDLINE_LEN]; /* kernel command line */
- } ce_config;
- ce_config config;
二、linux内核启动
从uboot跳转到内核指令地址后,内核第一件事就是保存bootload传来的参数
linux_2_6_24/arch/ppc/kernel/head_fsl_booke.S
- 80 .text
- 81 _GLOBAL(_stext)
- 82 _GLOBAL(_start)
- 83 /*
- 84 * Reserve a word at a fixed location to store the address
- 85 * of abatron_pteptrs
- 86 */
- 87 nop
- 88 /*
- 89 * Save parameters we are passed
- 90 */
- 91 mr r31,r3
- 92 mr r30,r4
- 93 mr r29,r5
- 94 mr r28,r6
- 95 mr r27,r7
- 96 li r24,0 /* CPU number */
接着在执行machine_init的时候,将上面这些参数作为形参导入
- 446 * Decide what sort of machine this is and initialize the MMU.
- 447 */
- 448 mr r3,r31
- 449 mr r4,r30
- 450 mr r5,r29
- 451 mr r6,r28
- 452 mr r7,r27
- 453 /*
- 454 * Find out what kind of machine we're on and save any data we need
- 455 * from the early boot process (devtree is copied on pmac by prom_init()).
- 456 * This is called very early on the boot process, after a minimal
- 457 * MMU environment has been set up but before MMU_init is called.
- 458 */
- 459
- 460 bl machine_init
- mahine_init(r3,r4, r5, r6, r7)
- --> platform_init(r3, r4, r5, r6, r7);
linux_2_6_24/arch/ppc/platforms/85xx/mpc85xx_cds_common.c
- platform_init(unsigned long r3, unsigned long r4, unsigned long r5,
- unsigned long r6, unsigned long r7)
- {
- /*
- * If we were passed in a board information, copy it into the
- * residual data area.
- */
- if (r3)
- memcpy((void *) __res, (void *) (r3 + KERNELBASE), sizeof (bd_t));
- #if defined(CONFIG_BLK_DEV_INITRD)
- /*
- * If the init RAM disk has been configured in, and there's a valid
- * starting address for it, set it up.
- */
- if (r4) {
- initrd_start = r4 + KERNELBASE;
- initrd_end = r5 + KERNELBASE;
- }
- #endif /* CONFIG_BLK_DEV_INITRD */
- /* Copy the kernel command line arguments to a safe place. */
- if (r6) {
- *(char *) (r7 + KERNELBASE) = 0;
- strcpy(cmd_line, (char *) (r6 + KERNELBASE));
- }
- //......省略
- return;
- }
好,现在bootload传来的启动参数全保存在全局变量cmd_line里面,接下来就要处理cmd_line了
三、处理内核启动参数
下面的函数对无关代码进行了省略
- void __init start_kernel(void)
- {
- char * command_line;
- setup_arch(&command_line);
- strlcpy(boot_command_line, cmd_line, COMMAND_LINE_SIZE);
- command_line = cmd_line; //这个cmd_line就是从bootload里传来的
- parse_early_param();
- // --> parse_args("early options", tmp_cmdline, NULL, 0, do_early_param);
- setup_command_line(command_line);
- saved_command_line = alloc_bootmem(strlen (boot_command_line)+1);
- static_command_line = alloc_bootmem(strlen (command_line)+1);
- strcpy (saved_command_line, boot_command_line);
- strcpy (static_command_line, command_line);
- parse_args("Booting kernel", static_command_line, __start___param,
- __stop___param - __start___param, &unknown_bootoption);
- }
最重要的就是看parse_args()这个函数了
- int parse_args(const char *name, // description 字符串,可无视
- char *args, // 待解析的字符串args
- struct kernel_param *params, // 内核的数据结构数组
- unsigned num, // 数组中元素个数
- int (*unknown)(char *param, char *val)) // 函数指针
- {
- while(args中还有p=v对){
- 从args中取出p=v对
- 遍历params数组,若有p对应的数组元素的话,调用数组元素的set函数
- 执行unknow(p,v)
- }
- }
start_kernel里有如下调用parse_early_param();
最终调用 parse_args("early options", tmp_cmdline, NULL, 0, do_early_param);
实际上就是从命令字符串中解析出一个个的p=v对后,对其调用do_early_param
- static int __init do_early_param(char *param, char *val)
- {
- struct obs_kernel_param *p;
- for (p = __setup_start; p < __setup_end; p++) {
- if ((p->early && strcmp(param, p->str) == 0) //注意此处的p->early
- || (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;
- }
__setup_start和__setup_end是什么东西呢
- #define early_param(str, fn) \
- __setup_param(str, fn, fn, 1)
- #define __setup(str, fn) \
- __setup_param(str, fn, fn, 0)
- #define __setup_param(str, unique_id, fn, early) \
- static char __setup_str_##unique_id[] __initdata __aligned(1) = str; \
- static struct obs_kernel_param __setup_##unique_id \
- __attribute_used__ \
- __attribute__((__section__(".init.setup"))) \
- __attribute__((aligned((sizeof(long))))) \
- = { __setup_str_##unique_id, fn, early }
linux_2_6_24/arch/powerpc/kernel/vmlinux.lds.S
- 122 . = ALIGN(16);
- 123 __setup_start = .;
- 124 .init.setup : { *(.init.setup) }
- 125 __setup_end = .;
- 126 __initcall_start = .;
- 127 .initcall.init : {
- 128 INITCALLS
- 129 }
宏early_param和 __setup_param初始化了一个struct obs_kernel_param结构并放到节.init.setup中
两个宏的区别就是
early_param把
obs_kernel_param.early初始化为1,__setup_param初始化为0
do_early_param() 遍历节.init.setup(此节可以看成一个数组)只处理被early_param()链接进去的元素
如果有和p匹配的数据结构元素,调用其setup_func(v)指针函数
比如参数"mem=xx"由 early_parse_mem() 来处理
参数"numa=xx"有 early_numa() 来处理
还有有哪些呢,搜搜看,我擦,还真多
- arch/powerpc/mm/numa.c:707:early_param("numa", early_numa);
- arch/powerpc/xmon/xmon.c:2656:early_param("xmon", early_parse_xmon);
- arch/powerpc/kernel/prom.c:431:early_param("mem", early_parse_mem);
- arch/powerpc/kernel/setup_64.c:138:early_param("smt-enabled", early_smt_enabled);
- arch/powerpc/kernel/setup_32.c:150:early_param("wdt", early_parse_wdt);
- arch/powerpc/kernel/setup_32.c:159:early_param("wdt_period", early_parse_wdt_period);
- arch/ppc/kernel/setup.c:611:early_param("wdt", early_parse_wdt);
- arch/ppc/kernel/setup.c:620:early_param("wdt_period", early_parse_wdt_period);
- arch/ppc/kernel/setup.c:783:early_param("earlycon", setup_early_serial8250_console);
- arch/ppc/kernel/setup.c:784:early_param("mem", early_parse_mem);
- drivers/serial/8250_early.c:313:early_param("earlycon", setup_early_serial8250_console);
- drivers/pci/pci.c:1617:early_param("pci", pci_setup);
- init/main.c:155:early_param("nosmp", nosmp);
- init/main.c:166:early_param("maxcpus", maxcpus);
- init/main.c:1694:early_param("earlycon", setup_early_serial8250_console);
- init/main.c:1695:early_param("mem", early_parse_mem);
- mm/page_alloc.c:2180:early_param("numa_zonelist_order", setup_numa_zonelist_order);
- mm/page_alloc.c:4239:early_param("kernelcore", cmdline_parse_kernelcore);
- mm/page_alloc.c:4240:early_param("movablecore", cmdline_parse_movablecore);
start_kernel()里又调用
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param, &unknown_bootoption);
实际上就是从命令字符串中解析出一个个的p=v对后,对其调用unknown_bootoption
遍历节.init.setup(此节可以看成一个数组)只处理由__setup(str, fn) 链接进去的数组元素
有匹配p的话,调用数组元素的setup_func(v)钩子函数
- arch/powerpc/kernel/iommu.c:81:__setup("protect4gb=", setup_protect4gb);
- arch/powerpc/kernel/iommu.c:82:__setup("iommu=", setup_iommu);
- arch/powerpc/kernel/irq.c:1155:__setup("noirqdistrib", setup_noirqdistrib);
- arch/powerpc/kernel/setup_32.c:173:__setup("l2cr=", ppc_setup_l2cr);
- arch/powerpc/kernel/crash_dump.c:73:__setup("elfcorehdr=", parse_elfcorehdr);
- arch/powerpc/kernel/crash_dump.c:83:__setup("savemaxmem=", parse_savemaxmem);
- arch/powerpc/kernel/idle.c:51:__setup("powersave=off", powersave_off);
- //此处略去N多行
三、常用的内核启动参数
Linux的内核参数是以空格分开的一个字符串列表,通常具有如下形式:
name[=value_1][,value_2]...[,value_10]
比如你在启动时设置参数name=a,b,c,d,内核搜索bootsetups数组,如果发现“name”已注册,则调用“name”的设置函数如name_setup(),并把a,b,c,d传递给name_setup()执行。
所有型如“name=value”参数,如果没有被上面所述的设置函数接收,将被解释为系统启动后的环境变量,比如“TERM=vt100”就会被作为一个启动时参数。
所有没有被内核设置函数接收也没又被设置成环境变量的参数都将留给init进程处理,比如“single”。
1、init=...
设置内核执行的初始化进程名,如果该项没有设置,内核会按顺序尝试/etc/init,
/bin/init,/sbin/init, /bin/sh,如果所有的都没找到,内核会抛出 kernel panic:的错误。
2、nfsaddrs=...
设置从网络启动时NFS的启动地址,已字符串的形式给出。
3、nfsroot=...
设置网络启动时的NFS根名字,如果该字符串不是以 "/"、","、"."开始,默认指向“/tftp-boot”。
以上2、3在无盘站中很有用处。
4、no-hlt
该选项仅当定义了CONFIG_BUGi386时才能用,一些早期的i486DX-100芯片在处理“hlt”指令时会有问题,执行该指令后不能可靠的返回操作系统,使用该选项,可以让linux系统在CPU空闲的时候不要挂起CPU。
5、root=...
该参数告诉内核启动时使用哪个设备作为根文件系统。比如可以指定根文件为hda8:root=/dev/hda8。
6、ro和rw
ro参数告诉内核以只读方式加载根文件系统,以便进行文件系统完整性检查,比如运行fsck;rw参数告诉内核以读写方式加载根文件系统,这是默认值。
7、reserve=...
保留端口号。格式:reserve=iobase,extent[,iobase,extent]...,用来保护一定区域的I/O端口不被设备驱动程序自动探测。在某些机器上,自动探测会失败,或者设备探测错误或者不想让内核初始化设备时会用到该参数;比如: reserve=0x300,32 device=0x300,除device=0x300外所有设备驱动不探测 0x300-0x31f范围的I/O端口。
8、mem=...
限制内核使用的内存数量。早期BIOS设计为只能识别64M以下的内存,如果你的内存数量大于64M,你可以指明,如果你指明的数量超过了实际安装的内存数量,系统崩溃是迟早的事情。如:mem=0x1000000意味着有16M内存,如果是 mem=0x6000000,就是96M内存了。
9、panic=N
默认情况,内核崩溃--kernel panic 后会宕机而不会重启,你可以设置宕机多少秒之后重启机器;也可以在/proc/sys/kernel/panic文件里设置。
10、reboot=[warm|cold][,[bios|hard]]
该选项仅当定义了CONFIG_BUGi386时才能用。2.0.22的内核重启默认为cool reboot,warm reboot 更快,使用"reboot=bios"可以继承bios的设置。
11、debug
linux的日志级别比较多(详细信息可以参看linux/kernel.h),一般地,日志的守护进程klogd只把比DEBUG级别高的日志写进磁盘;如果使用该选项,klogd也把内核的DEBUG信息写进日志。
12、profile=N
在做内核开发的时候,如果想清楚的知道内核在什么地方耗用了多少CPU的时钟周期,可以使用核心的分析函数设置变量prof_shift为非0值,有两种方式可以实现:一种是在编译时指定,另一种就是通过“profile=”来指定; 他给出了一个相当于最小单位--即时钟周期;系统在执行内核代码的时候, profile[address >;>; prof_shift]的值就会累加,你也可以从 /proc/profile得到关于它的一些信息。
13、swap=N1,N2,N3,N4,N5,N6,N7,N8
设置内核交换算法的八个参数:max_page_age, page_advance, page_decline,page_initial_age, age_cluster_fract, age_cluster_min, pageout_weight,bufferout_weight。
14、buff=N1,N2,N3,N4,N5,N6
设置内核缓冲内存管理的六个参数:max_buff_age, buff_advance, buff_decline,buff_initial_age, bufferout_weight, buffermem_grace。
使用 RAMDISK的参数
(仅当内核配置并编译了 CONFIG_BLK_DEV_RAM)。一般的来说,使用ramdisk并不是一件好事,系统自己会更加有效的使用可用的内存;但是,在启动或者制作启动盘时,使用ramdisk可以很方便的装载软盘等设备上的映象(尤其是安装程序、启动过程中),因为在正真使用物理磁盘之前,必须要加载一些必要的模块,比如文件系统模块,scsi驱动等(可以参见我的initrd-x.x.x.img文件分析-制作安装程序不支持的根文件系统)。
早期的ramdisk(比如1.3.48的核心)是静态分配的,必须以ramdisk=N来指定ramdisk的大小;现在ramdisk可以动态增加。一共有四个参数,两个布尔型,两个整形。
1、load_ramdisk=N
如果N=1,就加载ramdisk;如果N=0,就不加载ramdisk;默认值为0。
2、prompt_ramdisk=N
N=1,提示插入软盘;N=0,不提示插入软盘;默认为1。
3、ramdisk_size=N或者ramdisk=N
设定ramdisk的最大值为N KB,默认为4096KB。
4、ramdisk_start=N
设置ramdisk的开始块号为N,当ramdisk有内核的映象文件是需要这个参数。
5、noinitrd
(仅当内核配置了选项 CONFIG_BLK_DEV_RAM和CONFIG_BLK_DEV_INITRD)现在的内核都可以支持initrd了,引导进程首先装载内核和一个初始化的ramdisk,然后内核将initrd转换成普通的ramdisk,也就是读写模式的根文件系统设备。然后linuxrc执行,然后装载真正的根文件系统,之后ramdisk被卸载,最后执行启动序列,比如/sbin/init。
选项noinitrd告诉内核不执行上面的步骤,即使内核编译了initrd,而是把initrd的数据写到 /dev/initrd,只是这是一个一次性的设备。
------------------------------ 请叫我华丽的分割线 --------------------------------
系统起来以后,可以敲
cat /proc/cmdline 来查看内核启动参数