为了满足嵌入式系统更快的启动速度需求,需要对uboot、kernel和根文件系统进行优化,保证原有功能的情况下,减少系统启动时间。
减小uboot镜像的大小,去掉不需要的驱动,不需要的命令,减少uboot的大小可以从两个方面获得好处,首先会节省驱动初始化时间,其次会使uboot镜像变小,从flash读取uboot镜像所需的时间也变小了。
可以通过优化flash CS时序,提升flash频率,针对emmc可从硬件上将总线位宽x4改成x8,从软硬件的方式提升IO速度。
先前方案需要从文件系统中通过ext4load
或ubifsload
的方式加载内核、设备树和文件系统,改用直接从flash中读取到内存的方式,若uboot下支持DMA传输可以更有效的节省时间。对于kenerl和dtb可以通过读头部来获取具体的镜像大小后再进行读取,ramdisk一般会进行压缩,可以根据压缩后的大小来读取。
注:可通过裁剪kernel和ramdisk的镜像大小来进一步减少读取时间。
uboot启动会执行init_sequece_f
和init_sequence_r
中的一系列初始化函数,对与启动使用不到的初始化模块可以通过config配置宏进行裁剪。
如设备用不到mtd flash和pci功能,可关闭CONFIG_MTD_NOR_FLASH和CONFIG_PCI功能,具体可根据需求修改配置。
uboot启动过程中,启动信息输出通常被定向到串口,开发阶段调试打印对于我们定位问题很有帮助,但是成片的打印也是较耗时的。功能调通后,我们可以通过禁用串口输出,来消除打印字符所花费的时间。若要使用命令行时,再打开串口即可。以下是nt98566上的代码实现:
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);
}
void app_uart_enable(void)
{
uart_disable_anchor = 0;
return;
}
if(0 <= bootdelay && !app_abortboot (bootdelay))
{
/*用户不输入任何信息*/
/*引导内核启动*/
}
else
{
/*用户输入ctrl+u*/
app_uart_enable();
}
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
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
中分为以下几种场景:
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
中分为以下几种场景:
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,不进行根文件系统和设备树的重定位。
缩小环境变量env区大小,缺省的CONFIG_ENV_SIZE为0x40000,根本用不到,改成0x200即可。当然,最后作为产品,是不需要读取分区中的环境变量的,使用默认的环境变量即可。
uboot启动过程中会等待用户外部输入打断启动,从而进入命令行,如下。这边直接将等待的udelay(10000)注释掉,并且将等待1000ms改成1ms,若需要进入命令行提前按住输入即可。
删除不使用的硬件驱动和内核模块初始化, 对于耗时较长的模块由编译进内核转为外部modules,在业务实际需要时加载,避免开机自加载 。
内核支持的压缩格式如下,包括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)操作系统启动优化中。具体测试数据参考:
当前内核默认的压缩方式为gzip,在arm和arm64上经过压缩后编译而成zImage和Image.gz的内核镜像,并通过mkimage工具增加头部,生成uboot专用的uImage镜像。可将内核的压缩方式改为LZ4。
删除不必要的文件。
删除不必要的延时和驱动架子啊。
同内核,将文件系统的压缩方式改为LZ4类型。
设备文件系统有devfs,mdev,udev这三种。在Linux早期,设备文件仅仅是是一些带有适当的属性集的普通文件,它由mknod命令创建,文件存放在/dev目录下。后来,采用了devfs, 一个基于内核的动态设备文件系统,他首次出现在2.3.46内核中。devfs创建的设备文件是动态的,但是devfs有一些严重的限制,从2.6.13版本后移走了,目前取代他的是udev(一个用户空间程序), 当前很多的Linux分发版本采纳了udev的方式 。
mdev是udev的简化版本,是busybox中所带的程序,相较udev更适合用在嵌入式系统。