首先介紹原理,最後介紹具體怎麼實作。
1. Android啟動流程
eloader,uboot存儲在SPI Flash上面。SPI FLash在開機時會被SoC映射到某位址,然後依次啟動eloader,uboot。
uboot啟動後,将會啟動kernel。
1.1.kernel在哪裡呢?
1.1.1. kernel可以在boot.img中
boot.img是Android搞出來的一種檔案布局格式。boot.img包括三大部分,kernel、ramdisk和cmdline。
ramdisk中包括了Android需要的根目錄,核心包括init,init.rc等。
cmdline是傳遞給核心的參數。uboot中有環境變量bootargs也是傳遞給核心的參數。誰優先呢?需要根據不同平台的uboot來看。不同廠家實作不一樣。
各Android平台下的uboot中增加了對boot.img的解析功能。可以從boot.img的布局中解析出核心、ramdisk及cmdline來。
1.1.2. kernel可以在某個檔案系統裡
檔案系統必須布局在某個分區。分區可以存在于sd卡,emmc上,甚至是u盤。也就是kernel存在于某種儲存設備的分區上。uboot需要支援分區上的檔案系統,能夠從檔案系統中将kernel讀到記憶體裡。
以上兩種方式的kernel,檔案封裝格式方式不一樣。
1.2.kernel的兩種檔案封裝格式
對于boot.img中的kernel,就是普通的zImage格式,而對于在檔案系統裡存儲的,kernel是uImage方式,此uImage通過mkimage方式産生,請參考後文内容。
zImage就是經過普通壓縮的vmlinux。uImage是把zImage再次封裝後的形式。u的意思是uboot再次解析的意思。
uboot通過各種努力,從不同地方找到了kernel了,這各種努力,根據在kernel存儲在哪裡有不同的行為。
1.3.kernel如何被執行起來并找到根分區
根據kernel存儲在哪裡來分别描述:
1.3.1. kernel在boot.img中– booti
boot.img如何被uboot找到呢?
又分為兩種情況:
1.3.1.1. boot.img dd 到boot分區。
一般來說,無論是emmc,還是nand,都會有一個boot分區存在。boot.img會被同linux 指令dd一樣的方式寫入到這個分區裡。
uboot可以找到這個分區,然後讀出boot.img裡的内容。是以uboot需要支援emmc的讀寫,sd卡的讀寫,nand的讀寫,甚至u盤的讀寫。這些讀寫都是按扇區方式讀寫的。
1.3.1.2. 從分區直接讀--booti boot
這種啟動方式,uboot的啟動參數是booti boot。
booti指令首先根據分區名稱找到boot分區位置,然後讀入到記憶體中,記憶體中的内容,就是boot.img。
解析boot.img,得到核心,并得到ramdisk,cmdline。
uboot中有一個重要的環境變量bootargs,不同的uboot版本,會讓cmdline和bootargs較量來決定什麼值傳遞給kernel。
最重要的boot args是指定了根分區及init。
kernel會以此為依據找到根分區及init。
1.3.1.3. boot.img存在某檔案系統中--booti address
boot.img不是直接按二進制方式寫入到某個分區,不像dd一樣。而是存在分區的檔案系統中。比如存在SD卡第一個fat32檔案系統分區中,或者emmc第15個ext4檔案系統中。
這種啟動方式,首先需要将boot.img加載到記憶體,然後uboot從這個位址啟動。boot.img就可以存在sd,emmc,nand,u盤等分區的檔案系統中,需要首先被ext4load,fatload等加載到記憶體中。
加載到記憶體後,uboot的booti指令可以從這個記憶體位址啟動。
booti address
1.3.2. kernel在分區檔案系統中-bootm
這時候,kernel需要是uImage封裝方式。并且需要被uboot提供的ext4load,fatload等方式将uImage加載到記憶體,然後被uboot的bootm來啟動。
1.3.3. kernel如何找到根分區
kernel需要找到根分區,這個是通過uboot傳遞給kernel的cmdline指定的。VT6080一般都是指定一個實際分區。也可以是ramdisk。
kernel啟動了,也找到了根分區,如何啟動Android系統呢?
1.4.Android的啟動
在根分區有一個init程序及Init.rc,kernel從根分區啟動init,init解析init.rc。
在init.rc及其include進來的rc檔案裡,我們需要建立system目錄及data目錄。
init.rc會通過mount_all指令,挂載system分區到/system,data分區到/data.
system裡存儲的,就是android系統,主要包括framework。
找到system和data目錄,基本上Android就能夠啟動了。
system目錄是system分區的挂載點。一般是在刷機的時候,将system.img刷在system分區。是以system.img刷到system分區的時候,是帶着檔案系統刷過去的,也就是system.img一般是ext4的img,和boot.img不一樣的格式。
2. 如何讓Android運作在SD卡上
讓Android運作在SD卡上,就是從SD卡啟動的意思了。根據前面介紹,我們知道必須具備以下條件:
1.uboot能夠支援sd卡讀寫。(mmcread/write)
2.uboot可以讀取sd卡分區上的檔案。(ext4load,fatload mmc0:1)
3.uImage在SD卡分區上。對于VT6080,還需要一個dtb檔案。
4.kernel啟動後,能夠驅動sd卡(mmc支援)及sd卡上的檔案系統。
5.kernel啟動後,根據核心參數,将SD卡分區當作系統的根分區。
6.SD卡分區中有init,有system,data目錄。
3. VT6080實做從SD卡啟動
0.sd卡格式化成ext4
1.編譯核心,找到uImage及dtb檔案(arch/arm/boot/)
2.編譯Android。
3.拷貝android輸出檔案到sd根目錄:
3.1 out../root/*
3.2 out../system
注意一個是root下的所有檔案,一個是整個system目錄。
4.拷貝核心uImage,dtb檔案到sd卡根目錄。
5.修改init.rc,init.elite1000.rc,fstab.elite1000等檔案中對system,userdata分區的挂載動作,都去掉。因為sd卡根目錄下已經有了system目錄了。
6.将s3g GFX driver安裝到sd根目錄。
7.修改uboot參數
setenv bootcmd 'ext4load mmc 0:1 0x0 scriptcmd; ifiminfo 0x0; then source 0; else run bootcmd_mmc; fi'
--uboot啟動後,首先運作bootcmd指令。這行意思是找sd卡上有沒有scriptcmd檔案,有的話就執行它。沒有就運作bootcmd_mmc
--是以sd卡上不能存在scriptcmd檔案。這個檔案是安裝bootloader及android的時候用的。
setenv bootcmd_mmc '
run bootargs_mmc&&
ext4load mmc 0:1 0x6000000elite1000-emmc.dtb;
ext4load mmc 0:1 0x2800000uImage;
if iminfo 0x2800000; then bootm0x2800000 - 0x6000000;
else run bootcmd_emmc;
fi'
--首先設定mmc啟動的參數。下文解釋
--加載dtb和核心到記憶體。
--如果記憶體中核心image符合uImage的封裝格式,運作之,否則,從emmc啟動。
setenv bootargs_mmc '
setenv bootargs
${kernelargs}
androidboot.serialno=${androidno}
root=/dev/mmcblk0p1 rootwait
rootflags=errors=remount-ro,commit=0
rootfstype=ext4 rw
${mtdparts}
init=/initrw'
bootargs_mmc指令就是設定bootargs參數,這個參數被uboot解釋并傳遞給kernel。
kernelargs是傳遞給kernel的參數。
root=,這是訓示根分區在哪裡。
mmcblk0
vt6080上有3個mmc控制器。sd卡是mmc 0,emmc是mmc2,mmc1沒有使用。這裡的0,1,2編号是uboot中使用的。
linux裡如何編号還不清楚。
這裡的mmcblk0是在linux核心中使用的,這裡指的是sd卡
mmcblk0p1,p1指的是sd卡上的第一個分區。分區編号從1數起。
setenv kernelargs 'console=ttyS1,115200n8rootdelay=1'
setenv androidno '030a3d8408e49475'
setenv mtdparts 'mtdparts=nand:4M(secureboot),4M(secureos),4M(audiofirmware),2M(uboot_env),4M(uboot),4M(boot_logo),4M(nvram),12M(devicetree),32M(otaloader),32M(iploader),16M(device_info),16M(misc),16M(boot),16M(recovery),384M(system),32M(package),384M(cache),-(data)'
setenv bootargs_mmc 'setenv bootargs ${kernelargs} androidboot.serialno=${androidno} root=/dev/mmcblk0p1 rootwaitrootflags=errors=remount-ro,commit=0 rootfstype=ext4 rw ${mtdparts} init=/initrw'
setenv bootcmd_mmc 'run bootargs_mmc &&ext4load mmc 0:1 0x6000000 elite1000-emmc.dtb; ext4load mmc 0:1 0x2800000uImage; if iminfo 0x2800000; then bootm 0x2800000 - 0x6000000; else runbootcmd_emmc; fi'
setenv bootcmd 'ext4load mmc 0:1 0x0 scriptcmd; ifiminfo 0x0; then source 0; else run bootcmd_mmc; fi'
以上就是uboot參數。可以在uboot指令行環境輸入執行。注意最後要saveenv。
8.重新開機。android将會從sd卡啟動了。
從zImage建立uImage:
mkimage -A arm -O linux -T kernel -C none -a02008000 -e 02008000 -n Linux-3.4.6 -d zImage uImage
列印資訊:
Image Name: Linux-3.4.6
Created: Mon Dec 29 17:21:36 2014
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 3857160 Bytes = 3766.76 kB = 3.68 MB
Load Address: 0x02008000
Entry Point: 0x02008000
如何從image安裝方式改變成從SD卡啟動
1.使用工具splite_bootimg.pl,分解boot.img,得到kernel和Ramdisk
2.使用mkimage工具将kenrel轉換成uImage
3.将ramdisk解壓到目錄ramdisk:
# mkdir ramdisk
# cd ramdisk
# gzip -dc ../*-ramdisk.gz | cpio -i
4.修改fstab.elite1000或者init.rc,移除system分區的挂載,因為system也會存在于SD卡。
5.将system.img挂載到某目錄,如挂載到/home/brilly/system:
使用Android編譯後得到的host工具simg2img工具将system.img轉換成ext4檔案系統的image
#simg2img system.img system.ext4
#mount -t ext4 -o loop system.ext4/home/brilly/system
6.ext4格式化sd卡。
7.拷貝ramdisk目錄所有檔案到SD根目錄
8.拷貝system目錄所有檔案到SD根目錄下的system目錄。
9.拷貝uImage到SD根目錄。