Linux嵌入式啟動以及優(yōu)化

小破綻的這個文章編輯功能真是渣,難用的很!由于本文里很多有序條目,文章cp過來序號給亂標,一修改更亂,實在懶得改了!有興趣的可看我的博文網(wǎng)頁https://wikieee.com/blog/leiad/linux/linux%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%90%AF%E5%8A%A8%E4%BB%A5%E5%8F%8A%E4%BC%98%E5%8C%96/we
或者同用戶名的公眾號or頭條號。
Original address:?http://blog.21ic.com/user1/5593/archives/2010/67071.html
以前寫了一篇Linux PC啟動過程的日記, 最近項目中, 想優(yōu)化一下啟動過程, 減少啟動時間.
因此研究了我們項目的啟動全過程.
1. 第一步: BootLoader -- U boot
1.1 在cpu/arm926ejs/start.s中
b reset ; //jump to reset
set cpsr ;svc mode ,disable I,F interrupt
調(diào)用lowlevel_init (在board\xxxx\lowlevel_init.S中), 將調(diào)用
__platform_cmu_init (設(shè)置cpu時鐘,啟動那些模塊等)
__platform_mpmc_init (mpmc初始化,配置SDRAM時序)
__platform_static_memory_init
__platform_static_uart_init
__platform_mpmc_clear
用LDMIA,STMIA命令 copy uboot 到內(nèi)存中
ldr pc ,_start_armboot
執(zhí)行start_armboot
1.2 start_armboot 在 lib-arm 中
根據(jù)init_sequence 執(zhí)行初始化序列, 包括:
cpu_init
board_init
中斷初始化
initialize environment
initialze baudrate settings
serial communications setup
打印uboot 版本
display_dram_config (打印DRAM大?。?/p>
而在board_init中
將打印公司名稱, 前后還加了delay
timer 初始化
dw_init --- I2C 設(shè)置
驗證時鐘來源 (來自wifi還是DECT)
LCD初始化
鍵盤初始化
Flash 初始化 (空函數(shù))
網(wǎng)卡初始化 (其中有個udelay(1000) 1ms的delay )
NOR FLASH 初始化
display_flash_config (打印Flash大?。?/p>
nand 初始化 (將scan整個nand chip,建立 bbt table)
env_relocate 環(huán)境變量重新定位到內(nèi)存中
得到IP 地址和網(wǎng)卡 MAC地址
devices_init
中斷enable
然后: start_armboot --> main_loop
1.3 main_loop在 common/main.c中
getenv("bootdelay")
? ?--> 循環(huán) readline
? ? ? ?run_command
2. 第二步: Kernel
2.1 Kernel自解壓
arch\arm\boot\compressed\head.S中調(diào)用decompress_kernel(misc.c),完了打印出"done,booting the kernel", 然后根據(jù)arch_id = 多少, 打印出 arch_id
2.2
在arch\arm\kernel\head.S中
check cpu 以及 machine ID
build the initial 頁表
_switch_data (arm\kernel\head_common.s中) 將process id存入process_id變量中
start_kernel
2.3 start_kernel
打印Linux version information
call setup_arch,(它將打印cpu特定的信息,
machine look_machine_type -> arm\tools\mach_types look_processor_type --> .proc.info.init. -->arm\mm\proc_arm926.S
在/arm\mach_xx\xx.c中,有MACHINE_START(….)
打印commnad_line
初始化
vfs_caches_init
虛擬文件系統(tǒng)VFS初始化,主要初始化dentry等,它將調(diào)用 mnt_init. 而mnt_init將調(diào)用init_rootfs,注冊rootfs文件系統(tǒng),init_mount_tree()創(chuàng)建rootfs文件系統(tǒng),會把rootfs掛載到/目錄.
rest_init
啟動init kernel thread
在init 線程中:
populate_rootfs()
函數(shù)負責加載initramfs.
我們的系統(tǒng)沒有定義CONFIG_BLK_DEV_INITRD,因此populate_rootfs什么也沒做
do_basic_setup
-->driver_init()->platform_bus_init()->…初始化platform bus(虛擬總線)
這樣以后設(shè)備向內(nèi)核注冊的時候platform_device_register()->platform_device_add()->…內(nèi)核把設(shè)備掛在虛擬的platform bus下,
驅(qū)動注冊的時候 platform_driver_register()->driver_register()->bus_add_driver()->driver_attach()->bus_for_each_dev() 對每個掛在虛擬的platform bus的設(shè)備作 __driver_attach()->driver_probe_device()->drv->bus->match()==platform_match()->比較strncmp(pdev->name, drv->name, BUS_ID_SIZE),如果相符就調(diào)用platform_drv_probe()->driver->probe(),如果probe成 功則綁定該設(shè)備到該驅(qū)動.
好象聲卡怎么先注冊驅(qū)動,再注冊設(shè)備呢?反了?
-->do_initcalls
而do_initcalls將調(diào)用__initcall_start到__initcall_end中的所有函數(shù)
__initcall_start和__initcall_end定義在arch/arm/kernel/vmlinux.lds.S中
它是這樣定義的:
__initcall_start = .; *(.initcall1.init) *(.initcall2.init) *(.initcall3.init) *(.initcall4.init) *(.initcall5.init) *(.initcall6.init) *(.initcall7.init) __initcall_end = .;
而在include/linux/init.h中
#define core_initcall(fn) __define_initcall("1",fn) #define postcore_initcall(fn) __define_initcall("2",fn) #define arch_initcall(fn) __define_initcall("3",fn) #define subsys_initcall(fn) __define_initcall("4",fn) #define fs_initcall(fn) __define_initcall("5",fn) #define device_initcall(fn) __define_initcall("6",fn) #define late_initcall(fn) __define_initcall("7",fn)
其中
#define __define_initcall(level,fn) \ static initcall_t __initcall_##fn __attribute_used__ \ __attribute__((__section__(".initcall" level ".init"))) = fn
這說明core_initcall宏的作用是將函數(shù)指針(注意不是函數(shù)體本身)將放在.initcall1.init section中, 而device_initcall宏將函數(shù)指針將放在.initcall6.init section中.
函數(shù)本身用_init標識,在include/linux/init.h中
#define __init __attribute__ ((__section__ (".init.text")))
這些_init函數(shù)將放在.init.text這個區(qū)段內(nèi).函數(shù)的擺放順序是和鏈接的順序有關(guān)的,是不確定的。
因此函數(shù)的調(diào)用順序是:
core_initcall postcore_initcall 如amba_init arch_init 如 subsys_initcall fs_initcall device_initcall ---> module_init late_initcall
先調(diào)用core_initcall區(qū)段中的函數(shù),最后調(diào)用late_initcall中的函數(shù),而對于上述7個區(qū)段中每個區(qū)段中的函數(shù)指針,由于其擺放順序和鏈接的順序有關(guān)的,是不確定的,因此其調(diào)用順序也是不確定的.
rootfs 加載
prepare_namespace 掛載真正的根文件系統(tǒng),
在do_mounts.c中:
c static int __init root_dev_setup(char *line) { strlcpy(saved_root_name, line, sizeof(saved_root_name)); return 1; } __setup("root=", root_dev_setup);
也就是說: 在bootargs中root=/dev/nfs rw 或者 root=/dev/mtdblock4等將傳入saved_root_name.
c void __init prepare_namespace(void) { int is_floppy; mount_devfs(); if (root_delay) { printk(KERN_INFO "Waiting %dsec before mounting root device...\n", root_delay); ssleep(root_delay); } md_run_setup(); if (saved_root_name[0]) { root_device_name = saved_root_name; //保存在root_device_name中 ROOT_DEV = name_to_dev_t(root_device_name); //在root_dev.h中定義了Root_NFS,Root_RAM0等結(jié)點號 if (strncmp(root_device_name, "/dev/", 5) == 0) root_device_name += 5; } is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR; if (initrd_load()) goto out; if (is_floppy && rd_doload && rd_load_disk(0)) ROOT_DEV = Root_RAM0; mount_root(); //加載rootfs out: umount_devfs("/dev"); sys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot("."); security_sb_post_mountroot(); mount_devfs_fs (); }
yaffs2_read_super被調(diào)用來建立文件系統(tǒng), 它scan所有的block
free_initmem
釋放init內(nèi)存
打開/dev/console
失敗則會打印:
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
判斷是否有execute_command,這個參數(shù)是在uboot參數(shù)的bootargs中init=xxx ,如果定義了的話則執(zhí)行 run_init_process(execute_command).
可以通過這種方法實現(xiàn)自己的init process,
或者可以init=/linuxrc, 這樣執(zhí)行l(wèi)inuxrc
如果沒有execute_command, init kernel線程缺省的也是最后的步驟是:
run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh");
如果/sbin/init沒有, 則執(zhí)行/etc/init.
/etc/init沒有則執(zhí)行/bin/init, 如果這四者都沒有, 則Linux打印
panic("No init found. Try passing init= option to kernel.");
3. 第三步: Init Process
run_init_process也就是調(diào)用execve, 這樣就啟動了init process
上面的/sbin/init,/etc/init,/bin/init,/bin/sh這四者都指向busybox, 但對于/bin/sh則只是打開shell, 然后等待用戶命令.
而對于/sbin/init ,將分析/etc/inittab.
在/etc/inittab中
id:5:initdefault: 缺省的runlevel x
si::sysinit:/etc/init.d/rcS
執(zhí)行 rcS腳本
l5:5:wait:/etc/init.d/rc 5
S:2345:respawn:/sbin/getty 38400 ttyDW0
getty提示用戶輸入username, 然后調(diào)用login, login的參數(shù)為username, 登錄后啟動了shell
如果修改為 /bin/sh 則直接啟動shell, 此時你可以輸入命令 比如ls
在/etc/init.d/rcS中
mount proc 文件系統(tǒng)
/etc/default/rcS (設(shè)置一些參數(shù))
exec /etc/init.d/rc S
執(zhí)行 /etc/init.d/rc S --> 這樣將執(zhí)行/etc/rcS.d中以S開頭的腳本
S00psplash.sh psplash S02banner.sh make node /dev/tty S03sysfs.sh mount sysfs S03udev 啟動udev S06alignment.sh 為什么為3? S10checkroot.sh 讀取fatab ,mount 這些文件系統(tǒng) S20modutils.sh 加載module S35mountall.sh 不做什么事情 S37populate-volatile.sh S38devpts.sh mount devpts File System S39hostname.sh set hostname to /etc/hostname S40networking ifup -a to up the lo interface S45mountnfs.sh read /etc/fstab to whether NFS exists and then mount the NFS S55bootmisc.sh 調(diào)用/etc/init.d/hwclock.sh去設(shè)置時間,日期等 S60ldconfig.sh ldconfig建立庫的路徑
l5:5:wait:/etc/init.d/rc 5將執(zhí)行 /etc/rc5.d/ 依次為:
S00qpe 啟動qpe S02dbus-1 D_BUS dameon S10dropbear SSH service S20cron 自動執(zhí)行指定任務的程序 cron , in etc/crontab , ntpd will run to get the NTP time S20ntpd Not used , should delete S20syslog run /sbin/klogd S39wifiinit.sh wifi init and calibration S70regaccess mknod regaccess.ko S99rmnologin.sh do nothing since DELAYLOGIN = no in /etc/default/rcS
整個系統(tǒng)啟動后 ,將有 25 個進程 :其中12個內(nèi)核的進程 ,13個用戶進程
??
4. 優(yōu)化:
uboot:
setenv bootcmd1 "nand read.jffs2 0x62000000 kernel 0x180000 ; bootm 62000000"
這樣 load內(nèi)核的時候 從以前0x300000的3M->1.5M 省1S
2.setenv bootdelay 1 從2變?yōu)? 加上CONFIG_ZERO_BOOTDELAY_CHECK
quiet=1
bootargs=root=/dev/mtdblock4 rootfstype=yaffs2 console=ttyDW0 mem=64M mtdparts=dwnand:3m(kernel),3m(splash),64m(rootfs),-(userdata);dwflash.0:384k(u-boot),128k(u-boot_env) quiet
加上quiet 省不到1S
啟動的時候不掃描整個芯片的壞塊, 因為uboot只會用到kernel和splash區(qū),只需要檢驗這兩個區(qū)的壞塊。
可以省不到 0.2s ,沒什么明顯的改進
將環(huán)境變量verify設(shè)置為n, 這樣load kernel后, 不會去計算校驗kernel image的checksum
開始打印公司 這些可以去掉 ,在這里還有delay ,以及其他的一些不必要的打印 ,一起去掉
修改memcpy函數(shù) 在./lib_generic/string.c下:
(在linux 中,arm的memcpy有優(yōu)化的版本 , 在/arch/arm/lib/memcpy.S中)
下面2個建議,沒試過:
在環(huán)境變量區(qū)的末尾, 存有CRC,啟動的時候會校驗CRC ,去掉可以省一些時間
把一些驅(qū)動的初始化在正常啟動的時候不執(zhí)行,當用戶按了鍵,進入uboot命令模式的時候執(zhí)行
修改SDRAM控制器時序
Kernel:
啟動時間 有兩種方法 :
在u-boot的 bootargs 中加上參數(shù) time
在內(nèi)核的 kernel hacking 中選擇 PRINTK_TIME
方法2的好處是可以得到內(nèi)核在解析command_line前所有信息的時間, 而之前會有:打印linux 版本信息,CPU D cache , I cache 等等 。。。
啟動完后用 :
dmesg -s 131072 > ktime
然后用:
/usr/src/linux-x.xx.xx/s/show_delta ktime > dtime
這樣得到啟動內(nèi)核時間的報告
修改Nand驅(qū)動 提高讀速度
從 JFFS2 換成 yaffs
kernel變?yōu)榉菈嚎s的image, 但這樣的話內(nèi)核變大了, 從NAND中搬運內(nèi)核的時間將變長, 所以需要測試是否使得時間變短
建議:
把delay的calibration去掉
上面改動后基本上8s從開機到 Freeing init memory
Application:
udev 啟動很花時間
安排好啟動順序。