既然不能使用新的U-boot,那就優化一點是一點,慢慢干吧。
1.去掉啟動時的按鍵等待
U-boot 啟動的時候出現一個 Hit any key to stop autoboot 不爽,干嗎要停上1秒?雖然可以通過設置參數bootdelay=0來關掉這個延時,但這樣做了以后就再也進不去U-boot了,更煩。檢查代碼,發現是在main.c函數int abortboot(int bootdelay)來干這個活的,好吧,改掉它
static __inline__ int abortboot(int bootdelay)
? ? {
? ? ? ? int abort = 0;
? ? ? ? char inputkey;
? ? if (tstc())
? ? ? ? {?
? ? ? ? ? ? inputkey = getc();?
? ? ? ? ? ? abort = (inputkey == 'u');
? ? }
#ifdef CONFIG_SILENT_CONSOLE
? ? ? ?if (abort)
? ? ? ? ? gd->flags &= ~GD_FLG_SILENT;
#endif
? ? ? ? return abort;
? ?}
這樣,就不需要等待了,如果想進入U-boot,就在上電的時候按住u吧,把它改成一個固定的鍵而不是任意鍵,因為串口線很容易受到干擾,如果是任意鍵的話,運行時即使不想進去有時也會進入U-boot的命令行。?
2.去掉網卡的初始化
每次上電,U-boot 都會初始化網卡,其實這根本不需要,把配置文件中
#define CONFIG_MII ? 1
去掉,啟動時就不會初始化了,需要使用TFTP時,它會自動初始化,又節省了3.4秒的啟動時間。
3.智能讀取OS Image
U-boot 通過nand read 來讀取OS Image,通常為了避免麻煩,我們設置的讀取長度要比實際OS長度長一些,多讀的那部分純粹是浪費CPU時間,能不能精確判斷讀取長度呢,當然可以,為了不影響系統的正常功能,擴展一個nand read.os 指令來讀取,修改方法如下:
在 nand_read_options_t 里面添加一個成員 int is_os_img
在函數 int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
修改讀操作的判斷語句,添加 !strcmp(s, ".os"),然后設置opts.is_os_img = !strcmp(s, ".os");大概修改后結果參考第7步代碼。
? ? 最后,在函數int nand_read_opts(nand_info_t *meminfo, const nand_read_options_t *opts)中修改
代碼,檢測如果opts->is_os_img == 1 并且是第一次讀取(2024B)之后,檢查度取得結果是否是OS Image,如果是更新需要讀取的長度,否則,也不需要再往下讀了,直接返回錯誤就可以了嵌入式Linux啟動優化手記2 U-boot優化
? ? ? ? ? ? ? ? image_header_t ?*hdr = (image_header_t *)buffer; ? ??
? ? ? ? ? ? ? ? if (image_check_magic(hdr) && image_check_hcrc (hdr))
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? size_t ossz = uimage_to_cpu(hdr->ih_size);//+ image_get_header_size();
? ? ? ? ? ? ? ? ? ? imglen = ossz + + image_get_header_size();
? ? ? ? ? ? ? ? ? ? printf("## Find valid OS image, at 0x%x, Size: %d Bytes = %d KB
",
? ? ? ? ? ? ? ? ? ? ? ? ? ?(unsigned int)mtdoffset, ossz, ossz/1024);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? printf("Invalid OS image at 0x%x
", (unsigned int)mtdoffset);
? ? ? ? ? ? ? ? ? ? return -1;
? ? ? ? ? ? ? ? }
4.去掉OS Image 內存復制過程
使用 nand read 讀取OS Image 后,U-boot 使用 bootm 指令來啟動Linux,檢查其實現代碼
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
發現他會把已經讀取到內存中的OS Image 在復制到一個指定的位置,OS Image 中的頭部參數,這個值一般是固定的,本系統使用的是 0x70008000, 如果在 nand read 時讀到的內存位置恰好合適,就可以省掉這些毫秒數了,做法如下:
nand read.os 0x70007FC0 0x100000 0x500000
(其中 0x70007FC0 = 0x70008000 - sizeof(sizeof(image_header_t)))
然后在內存復制的地方(函數do_bootm中),加入修改,跳過內存復制
? ? switch (comp) {
? ? case IH_COMP_NONE:
? ? ? ? if (load_start == (ulong)os_hdr) {
? ? ? ? ? ? printf (" ? XIP %s ... ", type_name);
? ? ? ? } else {
? ? ? ? ? ? if (load_start != os_data)//位置不匹配,依然移動,否則就跳過此部
? ? ? ? ? ? {
? ? ? ? ? ? ? ? printf (" ? Loading %s ... ", type_name);
? ? ? ? ? ? ? ? memmove_wd ((void *)load_start, (void *)os_data, os_len, CHUNKSZ);
? ? ? ? ? ? ? ? puts("OK
");
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? load_end = load_start + os_len;
對于我們的Kernel,修改后大小是1.4M,省去這個搬移過程,節省了大約800ms的時間
5.減少內存初始化的時間
在U-boot 初始化時,在 start_armboot 函數中,多次使用到了 memset函數,其中最耗時的是在mem_malloc_init函數中調用memset 初始化 512K內存的調用,檢查U-boot 1.3.4對memset的實現,發現是最簡單的字節復制,把它改為按32bits復制的方式,這些memset 調用所花費的時間立即從202ms減少到了45ms
修改方法,再 string.c 中,找到memset函數,修改其實現(代碼是從U-boot 2011.12 中復制過來的嵌入式Linux啟動優化手記2 U-boot優化)
void * memset(void * s,int c,size_t count)
? ? {
? ? ? ? unsigned long *sl = (unsigned long *) s;
? ? ? ? unsigned long cl = 0;
? ? ? ? char *s8;
? ? ? ? int i;
??
? ? ? ? if ( ((ulong)s & (sizeof(*sl) - 1)) == 0) {
? ? ? ? ? ? for (i = 0; i < sizeof(*sl); i++) {
? ? ? ? ? ? ? ? cl <<= 8;
? ? ? ? ? ? ? ? cl |= c & 0xff;
? ? ? ? ? ? }
? ? ? ? ? ? while (count >= sizeof(*sl)) {
? ? ? ? ? ? ? ? *sl++ = cl;
? ? ? ? ? ? ? ? count -= sizeof(*sl);
? ? ? ? ? ? }
? ? ? ? }
? ?
? ? ? ? s8 = (char *)sl;
? ? ? ? while (count--)
? ? ? ? ? ? *s8++ = c;
? ? ? ? return s;
? ? }
6.減少NAND初始化時間
? ? 每次 U-boot 啟動,發現NAND初始化需要大約3秒的時間,仔細追蹤發現,在nand_base.c文件中nand_scan函數的最后一步return this->scan_bbt (mtd);最花費時間,這個scan_bbt掃描整個NAND并檢查壞塊,重建壞塊表,在啟動過程中,這個耗時的操作毫無意義,去掉這一步,讓nand_scan 函數直接返回0就可以了。
7.添加Yaffs2支持
從網上各位前輩的論述中,都發現YAFFS比JFFS2要快,也決定測試一下,從YAFFS網站下載最新的代碼,按照說明加入到Linux 中,重新編譯內核,讓內核支持YAFFS2(按照默認的選項就可以了),弄一個空的分區,格式化成YAFFS2格式,感覺的確比較快,把ROOTFS復制到這個分區,然后修改Linux啟動參數讓它把YAFFS2分區當作根分區啟動,發現果然快了不少,初始化和掛載根分區僅需要370ms,比JFFS2的速度快多了,決定就采用YAFFS2作為根文件系統了。自己在u-boot中添加對yaffs2 image的支持
說起來容易,真正做起來還是很麻煩的,總是不能把yaffs2的image 燒寫成功,不知道是Image不正確還是Uboot沒改對,折騰了幾天也沒搞定,最后終于發現了一個第三方的工具
http://code.google.com/p/yaffs2utils/
下載,編譯,制作Image,驗證,OK,把新工具生成的IMAGE與YAFFS2自帶的工具對照,發現YAFFS2自帶的工具生成的IMAGE不正確,暈死。
重新修改UBoot,改了很少一部份代碼,就可以了。
依然是在函數do_nand中修改,添加一個擴展 nand write.y 指令來寫入Image:
按照慣例,YAFFS2的第一個塊不使用,留給文件系統自己使用,在 nand_write_options_t 里面添加一個成員 int skip_first_block;
在函數 int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
修改讀寫操作的判斷語句,添加 !strcmp(s, ".y"),然后設置opts.is_os_img = !strcmp(s, ".os");大概修改后結果如下(紅色部分)
? ? s = strchr(cmd, '.');
? ? ? ? if (s != NULL && (!strcmp(s, ".jffs2") || !strcmp(s, ".e") || !strcmp(s, ".i") || !strcmp(s, ".os") || !strcmp(s, ".y"))) ? ? {
? ? ? ? ? ? if (read) {
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? nand_read_options_t opts;
? ? ? ? ? ? ? ? memset(&opts, 0, sizeof(opts));
? ? ? ? ? ? ? ? opts.buffer ? ?= (u_char*) addr;
? ? ? ? ? ? ? ? opts.length ? ?= size;
? ? ? ? ? ? ? ? opts.offset ? ?= off;
? ? ? ? ? ? ? ? opts.readoob = 0;//remove this function.
? ? ? ? ? ? ? ? opts.is_os_img = !strcmp(s, ".os");
? ? ? ? ? ? ? ? opts.quiet ? ? ?= quiet;
? ? ? ? ? ? ? ? ret = nand_read_opts(nand, &opts);
? ? ? ? ? ? ? ? //printf("call nand_read_opts buffer %lu len %lu offset %d off, ret %d
", addr, size, off, ret);
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? nand_write_options_t opts;
? ? ? ? ? ? ? ? memset(&opts, 0, sizeof(opts));
? ? ? ? ? ? ? ? opts.buffer ? ?= (u_char*) addr;
? ? ? ? ? ? ? ? opts.length ? ?= size;
? ? ? ? ? ? ? ? opts.offset ? ?= off;
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? if (!strcmp(s, ".y"))
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? opts.pad ? ?= 0;
? ? ? ? ? ? ? ? ? ? opts.writeoob = 1; ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? //opts.noecc = 1;
? ? ? ? ? ? ? ? ? ? opts.skip_first_block = 1;
? ? ? ? ? ? ? ? ? ? opts.autoplace = 1;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? { ??
? ? ? ? ? ? ? ? ? ? opts.pad ? ?= 1;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? opts.blockalign = 1; ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? opts.quiet ? ? ?= quiet;
? ? ? ? ? ? ? ? ret = nand_write_opts(nand, &opts);
? ? ? ? ? ? }
? ? ? ? } else if (s != NULL && !strcmp(s, ".oob")) {...}
在函數nand_write_opts中相應修改
int nand_write_opts(nand_info_t *meminfo, const nand_write_options_t *opts)
? ? {
? ? ? ? ? int yaffs_skip_first = opts->skip_first_block;
? ? ? ...
? ? ? while ((imglen > 0) && (mtdoffset < meminfo->size)) {
? ? ? ? ? ? ?...
? ? ? ? ? ? ? while (blockstart != (mtdoffset & (~erasesize_blockalign+1))) {
? ? ? ? ? ? ? ? ? do {
? ? ? ? ? ? ? ? ? ? ? ...
? ? ? ? ? ? ? ? ? } while (offs < blockstart + erasesize_blockalign);
? ? ? ? ? }
? ? ? ? ? if (yaffs_skip_first)
? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ?yaffs_skip_first = 0;
? ? ? ? ? ? ? ? ? ?mtdoffset += erasesize_blockalign;
? ? ? ? ? ? ? ? ? ?continue;
? ? ? ? ? ? ? ?}
? ? ? ? ? ?readlen = meminfo->oobblock;
? ? ? ? ? ? ? ?if (opts->pad && (imglen < readlen))?
? ? ? ? ? ?...
? ? ? ? ? }
? ? ? ...
}
8. 其它一些優化措施
經過這些折騰之后,整個系統的啟動時間大大加快,然后優化Linux自身的一些啟動瓶頸
Linux的啟動參數優化:加上了 lpj=99072,節約了幾十個毫秒,加上quiet,節約了大約1秒時間
修改內核編譯選項,把不需要的內核模塊干掉
最后,Linux自身的啟動速度約為1.1秒,整個系統的啟動速度大約4秒多一點,初步達到了優化目標,系統的主要延時發生在U-boot 1.3.4的FLASH讀取上,FLASH讀取速度大約只有600KB/S。嘗試把Uboot 2011.12的FLASH驅動移植到U-boot 1.3.4上,花費了幾天時間,終于可以編譯成功了,可惜經常出一些莫名其妙的錯誤,太不穩定,只好放棄。
以我的能力,U-boot優化到這里就到頭了,正準備結束工作時,發現了另外一條可以加速系統啟動的方法
可以繼續嘗試,讓我最終把系統的啟動時間減少到了1.7秒。
請看下篇嵌入式Linux啟動優化手記2 U-boot優化
?
評論
查看更多