uboot启动优化

uboot启动优化

背景介绍

​ 为了满足嵌入式系统更快的启动速度需求,需要对uboot、kernel和根文件系统进行优化,保证原有功能的情况下,减少系统启动时间。

优化点

u-boot

1. 减小uboot镜像大小

​ 减小uboot镜像的大小,去掉不需要的驱动,不需要的命令,减少uboot的大小可以从两个方面获得好处,首先会节省驱动初始化时间,其次会使uboot镜像变小,从flash读取uboot镜像所需的时间也变小了。

2. flash优化

​ 可以通过优化flash CS时序,提升flash频率,针对emmc可从硬件上将总线位宽x4改成x8,从软硬件的方式提升IO速度。

3. 镜像读取方式

​ 先前方案需要从文件系统中通过ext4loadubifsload的方式加载内核、设备树和文件系统,改用直接从flash中读取到内存的方式,若uboot下支持DMA传输可以更有效的节省时间。对于kenerl和dtb可以通过读头部来获取具体的镜像大小后再进行读取,ramdisk一般会进行压缩,可以根据压缩后的大小来读取。
​ 注:可通过裁剪kernel和ramdisk的镜像大小来进一步减少读取时间。

4. init_sequece_f和init_sequence_r裁剪

​ uboot启动会执行init_sequece_finit_sequence_r中的一系列初始化函数,对与启动使用不到的初始化模块可以通过config配置宏进行裁剪。
​ 如设备用不到mtd flash和pci功能,可关闭CONFIG_MTD_NOR_FLASH和CONFIG_PCI功能,具体可根据需求修改配置。

5. 禁用串口输出

​ uboot启动过程中,启动信息输出通常被定向到串口,开发阶段调试打印对于我们定位问题很有帮助,但是成片的打印也是较耗时的。功能调通后,我们可以通过禁用串口输出,来消除打印字符所花费的时间。若要使用命令行时,再打开串口即可。以下是nt98566上的代码实现:

  1. 找到对应主板的串口驱动,nt98566平台在(drivers\serial\serial_ns16550.c)中,定义一个全局的uard_disable_anchor变量,若该标志为1直接返回,此时串口将没有任何输出。
static void _serial_putc(const char c, const int port)
{

#ifdef CONFIG_NVT_BOARD
	if (uart_disable_anchor)
	if (uart_disable_anchor == 1)
		return;
#endif

	if (c == '\n')
		NS16550_putc(PORT, '\r');

	NS16550_putc(PORT, c);
}
  1. 增加串口输出使能函数。
void app_uart_enable(void)
{
	uart_disable_anchor = 0;
	return;
}
  1. 当通过外部输入打断启动时,再打开串口输出,此时可以进行命令行操作。
if(0 <= bootdelay  && !app_abortboot (bootdelay))
{
   /*用户不输入任何信息*/
   /*引导内核启动*/
}
else
{
   /*用户输入ctrl+u*/
   app_uart_enable();
}
6. bootm启动优化

​ uboot启动linux内核可以用到bootm(bootz、booti)命令:

#bootm/bootz/booti [image_addr] [ramdisk_addr] [dtb_addr]
#任何一项都可以没有,如果没有,用 “-’代替
#用法1:bootm                              	#使用默认的镜像地址启动
#用法2:bootm image_addr                   	#指定镜像地址启动。一般是uImage
#用法3:bootm image_addr - dtb_addr  	    #指定设备树地址

​ bootm命令使用的是内核uImage镜像, 由工具mkimage对普通的压缩内核映像文件(zImage)加工而得。uImage是uboot专用的映像文件,它是在zImage之前加上一个长度为64字节的头,说明这个内核的版本、加载位置、生成时间、大小等信息。代码入口do_bootm位于cmd\bootm.c

bootm要做的事情包括以下:
a. 读取头部,把内核拷贝到合适的地方
b. 将启动参数给内核准备好,并告诉内核参数的首地址
c. 设置cpu寄存器,禁止中断,关闭MMU和cache
d. 跳转到内核的入口地址,kernel开始运行

全局images是bootm引导内核的一个重要变量

  • 取消内核镜像的crc校验

    bootm获取镜像时会根据images中的verify参数判断是否对镜像数据进行crc校验, 设置环境变量verify=n可以不进行校验,甚至可以只对image进行magic校验,省略其余的校验步骤。

    bootm_find_os
    |--boot_get_kernel
    | |--image_get_kernel
    | | |--image_check_magic(hdr)
    | | |--image_check_hcrc(hdr)
    | | |--if (verify)
    | | | |--image_check_dcrc(hdr)
    | | |--image_check_target_arch(hdr)
        
    bootm_find_other
    |--bootm_find_images
    | |--boot_get_ramdisk 
    | | |--image_get_ramdisk
    | | | |--image_check_magic
    | | | |--image_check_hcrc
    | | | |--if (verify)
    | | | | |--image_check_dcrc(rd_hdr)  
    
  • 节省镜像拷贝时间

    bootm_lod_os函数会解压内核镜像,这里指的是制作uImage时的压缩类型,当前为IH_COMP_NONE

    • images.os.load == mages.os.image_start,即bootm传入的image_addr+0x40等于制作uImage时指定的load地址,无需进行memmove
    • images.os.load != mages.os.image_start,则将mages.os.image_start上的image镜像拷贝到images.os.load地址上,长度为images.os.image_len

    NT98566平台制作uImage时指定的-e参数, 即镜像入口地址为ep 为0x8000, bootm命令传入的镜像地址为image_addr,保持ep = image_addr + 0x40(sizeof(image_header_t)),即bootm传入的内核镜像内存地址为0x7fc0,可节省image镜像的拷贝时间

    static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,
    			 int boot_progress)
    |--ulong load = os.load;
    |--int err = image_decomp(os.comp, load, os.image_start, os.type,
    				 load_buf, image_buf, image_len,
    				 CONFIG_SYS_BOOTM_LEN, &load_end);
    | |--if (load == os.image_start)
    | | |--break
    | |--if (image_len <= CONFIG_SYS_BOOTM_LEN)
    | | |--memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);  
    
  • 节省文件系统和设备树镜像的重地位时间

    bootm指定ramdisk_addr,boot_ramdisk_high中分为以下几种场景:

    • setenv initrd_high 不等于~0 : 从lmb.memory.region[0]中申请内存,且内存不高于initrd_high,并拷贝镜像
    • setenv initrd_high 0xffffffff,不进行reloacate,保留这块内存
    • setenv initrd_high 0 : 从lmb.memory.region[0]中任意位置申请内存,并拷贝镜像
    • 没有环境变量initrd_high: initrd_high = bootm_mapsize + bootm_low
    int boot_ramdisk_high(struct lmb *lmb, ulong rd_data, ulong rd_len,
    		  ulong *initrd_start, ulong *initrd_end)
    |--if ((s = getenv("initrd_high")) != NULL)
    | |--ulong initrd_high = simple_strtoul(s, NULL, 16);
    | |--if (initrd_high == ~0)
    | | |--initrd_copy_to_ram = 0;
    |--else
    | |--initrd_high = getenv_bootm_mapsize() + getenv_bootm_low();
    
    |--if (rd_data) 
    | |--if (!initrd_copy_to_ram)
    | |--*initrd_start = rd_data;
    | |--*initrd_end = rd_data + rd_len;
    | |--lmb_reserve(lmb, rd_data, rd_len);
    | |--else
    | | |--if (initrd_high)    
    | | | |--*initrd_start = (ulong)lmb_alloc_base(lmb,
    						rd_len, 0x1000, initrd_high);
    | | |--else
    | | | |--*initrd_start = (ulong)lmb_alloc(lmb, rd_len,
    								 0x1000);
    | | |--*initrd_end = *initrd_start + rd_len;
    /* 镜像拷贝 */ */
    | | |--memmove_wd((void *)*initrd_start,
    					(void *)rd_data, rd_len, CHUNKSZ);
    |--else
    | |--*initrd_start = 0;
    | |--*initrd_end = 0;
    

    bootm指定dtb_addr,boot_relocate_fdt中分为以下几种场景:

    • setenv fdt_high 不等于~0 : 从lmb.memory.region[0]中申请内存,且内存不高于initrd_high,并拷贝镜像
    • setenv fdt_high 0xffffffff, 不进行reloacate,保留这块内存
    • setenv fdt_high 0 : 从lmb.memory.region[0]中任意位置申请内存
    • 没有环境变量fdt_high : fdt_high = bootm_mapsize + bootm_low
    int boot_relocate_fdt(struct lmb *lmb, char **of_flat_tree, ulong *of_size) 
    |--void	*fdt_blob = *of_flat_tree;
    |--ulong of_len = *of_size + CONFIG_SYS_FDT_PAD;
    |--char	*fdt_high = getenv("fdt_high") 
    |--if (fdt_high)
    | |--void *desired_addr = (void *)simple_strtoul(fdt_high, NULL, 16);
    | |--if (((ulong) desired_addr) == ~0UL)
    | | |--void	*of_start = fdt_blob;
    | | |--lmb_reserve(lmb, (ulong)of_start, of_len);
    | | |--int	disable_relocation = 1
    | |--else if (desired_addr)
    | | |--of_start = (void *)(ulong) lmb_alloc_base(lmb, of_len, 0x1000,
    							   (ulong)desired_addr);
    | |--else 
    | | |--of_start = (void *)(ulong) lmb_alloc(lmb, of_len, 0x1000); 
    |--else
    | |--of_start = (void *)(ulong) lmb_alloc_base(lmb, of_len, 0x1000,
    						   getenv_bootm_mapsize() + getenv_bootm_low());
    
    |--if (disable_relocation)
    /* 设置设备树,并进行镜像拷贝 */
    | |--fdt_set_totalsize(of_start, of_len);
    |--else
    | |--int err = fdt_open_into(fdt_blob, of_start, of_len)
    
    |--*of_flat_tree = of_start;
    |--*of_size = of_len;
    |--set_working_fdt_addr((ulong)*of_flat_tree);
    | |--setenv_hex("fdtaddr", addr);
    

    设置setenv fdt_high = 0xffffffff,setenv initrd_high = 0xffffffff,不进行根文件系统和设备树的重定位。

7.优化环境变量

缩小环境变量env区大小,缺省的CONFIG_ENV_SIZE为0x40000,根本用不到,改成0x200即可。当然,最后作为产品,是不需要读取分区中的环境变量的,使用默认的环境变量即可。

8. bootdelay优化

​ uboot启动过程中会等待用户外部输入打断启动,从而进入命令行,如下。这边直接将等待的udelay(10000)注释掉,并且将等待1000ms改成1ms,若需要进入命令行提前按住输入即可。

kernel

1. 内核配置裁剪

​ 删除不使用的硬件驱动和内核模块初始化, 对于耗时较长的模块由编译进内核转为外部modules,在业务实际需要时加载,避免开机自加载 。

2. 修改内核的压缩方式

​ 内核支持的压缩格式如下,包括GZIP、LZMA、XZ、LZO和LZ4。比较几种不同的压缩类型,LZ4是最佳解压缩方案,LZMA和XZ的解压缩速度很慢,而在压缩大小方面,GZIP效果最好,能将文件压缩至最小,其次是LZO(大约比GZIP大16%)和LZ4(大约比GZIP大25%),而在压缩时间方面,LZ4比GZIP快7倍,LZO比GZIP快约1.25倍,因此可以看到GZIP的速度不够快。LZ4也被用在Ubuntu 19.10(Eoan Ermine)操作系统启动优化中。具体测试数据参考:

  1. https://blog.csdn.net/weixin_33329305/article/details/113450835
  2. https://catchchallenger.first-world.info/wiki/Quick_Benchmark:_Gzip_vs_Bzip2_vs_LZMA_vs_XZ_vs_LZ4_vs_LZO#With_the_benchmark_application

​ 当前内核默认的压缩方式为gzip,在arm和arm64上经过压缩后编译而成zImage和Image.gz的内核镜像,并通过mkimage工具增加头部,生成uboot专用的uImage镜像。可将内核的压缩方式改为LZ4。

根文件系统

1. 根文件系统文件裁剪

​ 删除不必要的文件。

2. rcS优化

​ 删除不必要的延时和驱动架子啊。

3. 压缩方式从GZIP改为LZ4

​ 同内核,将文件系统的压缩方式改为LZ4类型。

4. 使用mdev设备文件系统

​ 设备文件系统有devfs,mdev,udev这三种。在Linux早期,设备文件仅仅是是一些带有适当的属性集的普通文件,它由mknod命令创建,文件存放在/dev目录下。后来,采用了devfs, 一个基于内核的动态设备文件系统,他首次出现在2.3.46内核中。devfs创建的设备文件是动态的,但是devfs有一些严重的限制,从2.6.13版本后移走了,目前取代他的是udev(一个用户空间程序), 当前很多的Linux分发版本采纳了udev的方式 。
mdev是udev的简化版本,是busybox中所带的程序,相较udev更适合用在嵌入式系统。

你可能感兴趣的:(uboot,linux,内核)