本文討論如何利用安卓系統自身的啟動特性,加進 Linux 系統的啟動支持,并實現雙系統的切換。 要達到這一點,有必要先了解一下安卓系統的啟動流程。
安卓系統的啟動模式有兩種:正常模式和急救(recovery)模式。急救模式,其內核和根文件系統均獨立于正常模式,功能簡單,一般很少更新,用作系統修復和維護。也就是說,安卓系統本身就是支持雙啟動的。安卓系統的啟動流程是:
1 U-Boot 初始化 1.1 U-Boot 讀取 CPU 寄存器,如果有 recovery 標志,則跳轉到 3 1.2 U-Boot 讀取 misc 分區,如果含有 recovery 命令,則跳轉到 3 1.3 正常啟動模式,跳轉到 2 2 正常啟動模式 2.1 加載 boot 分區 2.1.1 如果 boot 分區含有內核和 initramfs, 則分別加載到內存特定位置,跳轉到 2.3 (略過 kernel 分區處理) 2.1.2 如果 boot 分區僅含有 initramfs, 則加載到內存特定位置。 2.2 加載 kernel 分區到內存特定位置。 2.3 跳轉到 4 3 急救模式 3.1 讀出 recovery 分區內含的內核 和 initramfs, 分別加載到內存特定位置,跳轉到 4 4 初始化內核啟動參數,將執行權移交內核。
注意,initramfs 是固化了的小型根文件系統,內核啟動后會將其解壓至內存中,并執行其中的 init 程序進行初始化。也就是說,initramfs 是第一個獲得執行權的根文件系統,負責掛載真正的根文件系統(可以在各種各樣的存儲設備中,如 U盤、TF 卡、USB 硬盤、NAND 或 eMMC 閃存等)。
分析安卓系統的啟動流程,一個比較簡單的雙啟動方案就是:
- 加入 Linux 系統的根文件系統分區。
- 替換 recovery 分區成 Linux 系統的內核和 initramfs。
- 改變 misc 分區的內容,就可設定開機啟動的操作系統。
- 在 Linux 系統內實現安卓急救系統的部分功能。
如何進入 Linux 呢? 因為我們將 Linux 放在 recovery 分區,因此,問題等價于如何進入安卓的急救模式。以下有幾種方式:
-
拔掉 USB 線,按住開發板的
recovery
鍵開機(無論是初次上電、重啟或按reset
鍵開機都可以)。這是臨時性的切換,下次開機不按,還是會進入 Linux 。 - 在安卓系統的設置里選擇恢復出廠設置。實際上,恢復出廠設備這個功能已被閹割了,重啟后會進入 Linux。
-
在安卓系統的關機菜單(點底部工具欄的關機按鈕進入)增加了一項切換系統的選擇。當然,它是檢測到
linuxroot
分區才會出現,也就是說單系統是不會出現的。 -
將 SDK 里的
rkst/Image/misc.img
刷進到misc
分區。
2~4 項都是通過寫 misc 分區,達到切換到 recovery,這里也即是 Linux 的目的。
我們先來看看純安卓的存儲分區情況。分區信息在 parameter 文件里的CMDLINE
行:
FIRMWARE_VER:4.4.2 MACHINE_MODEL:rk30sdk MACHINE_ID:007 MANUFACTURER:RK30SDK MAGIC: 0x5041524B ATAG: 0x60000800 MACHINE: 3066 CHECK_MASK: 0x80 PWR_HLD: 0,0,A,0,1 #KERNEL_IMG: 0x62008000 #FDT_NAME: rk-kernel.dtb #RECOVER_KEY: 1,1,0,20,0 CMDLINE:console=ttyFIQ0 androidboot.hardware=rk30board androidboot.console=ttyFIQ0 board.ap_has_alsa=0 init=/init initrd=0x62000000,0x00800000 mtdparts=rk29xxnand:0x00002000@0x00002000(uboot),0x00002000@0x00004000(misc),0x00008000@0x00006000(resource),0x00008000@0x0000e000(kernel),0x00010000@0x00016000(boot),0x00010000@0x00026000(recovery),0x0001a000@0x00036000(backup),0x00040000@0x00050000(cache),0x00002000@0x00090000(kpanic),0x00180000@0x00092000(system),0x00002000@0x00212000(metadata),0x00200000@0x00214000(userdata),0x00020000@0x00414000(radical_update),-@0x00434000(user)
CMDLINE
是傳遞到內核的命令行,參數mtdparts
就含有分區信息,其格式是:
0x00002000@0x00002000(uboot) 大小 偏移 分區名稱 單位是 512 字節(即傳統磁盤的扇區大?。?
轉換成表格比較直觀些:
-
uboot
:是用來存放第二階段(stage two) U-Boot,如果開發板用的是 eMMC 分區,其 U-Boot 就不需要分階段。 -
misc
:非常有用的一個分區,下面會介紹到,用來控制啟動模式的。 -
resource
:存放內核的開機圖片和設備樹(Device Tree)信息。 -
kernel
:存放安卓的內核 -
boot
: 存放安卓的正常系統啟動的初始內存文件系統(initramfs)。注意,如果在 OTA 方式下,boot
分區跟recovery
分區一樣,含有內核和初始內存文件系統,此時kernel
分區不作使用。 -
recovery
: 存放安卓急救模式所使用到的內核和初始內存文件系統。 -
backup
: RK 設計的用來存放備份固件的分區, FireNow 系統開發板沒有用到。 -
cache
: 安卓的緩存分區 -
kpanic
: 安卓的 kernel panic 分區(?) -
system
: 安卓的系統分區(掛載于/system
) -
metadata
: RK 的元數據分區,使用情況不詳 -
userdata
: 安卓的數據分區(掛載于/data
) -
radical_update
: RK 的升級分區,使用情況不詳 -
user
: 安卓的內部存儲分區(掛載于/mnt/sdcard
)
我們需要增加一個名為 ‘linuxroot’ 的新分區,用來存放 Linux 的根文件系統。為了使分區保持兼容,我們選擇了替換 radical_update 分區,容量給夠 3G :
這樣,修改后的 parameter 文件,其 CMDLINE 更改為:
CMDLINE:console=ttyFIQ0 androidboot.hardware=rk30board androidboot.console=ttyFIQ0 board.ap_has_alsa=0 root=/dev/block/mtd/by-name/linuxroot rw rootfstype=ext4 init=/sbin/init initrd=0x62000000,0x00800000 mtdparts=rk29xxnand:0x00002000@0x00002000(uboot),0x00002000@0x00004000(misc),0x00008000@0x00006000(resource),0x00008000@0x0000e000(kernel),0x00010000@0x00016000(boot),0x00010000@0x00026000(recovery),0x0001a000@0x00036000(backup),0x00040000@0x00050000(cache),0x00002000@0x00090000(kpanic),0x00180000@0x00092000(system),0x00002000@0x00212000(metadata),0x00200000@0x00214000(userdata),0x00620000@0x00414000(linuxroot),-@0x00a34000(user)
改變 misc 分區的內容,實現設定開機啟動的操作系統,就必須了解 misc 分區的格式。 misc.img 是初次燒寫固件時寫到 misc 分區的映像,用 hexdump 命令可以方便地查看其內容:
$ hexdump -C rkst/Image/misc.img 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00004000 62 6f 6f 74 2d 72 65 63 6f 76 65 72 79 00 00 00 |boot-recovery...| 00004010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00004040 72 65 63 6f 76 65 72 79 0a 2d 2d 77 69 70 65 5f |recovery.--wipe_| 00004050 61 6c 6c 00 00 00 00 00 00 00 00 00 00 00 00 00 |all.............| 00004060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 0000c000
可見,前 16K (0x4000) 字節都是 0,然后是一個 “boot-recovery” 命令,后面又跟著 “recovery”, “–wipe_all” 這些動作和參數,因此初次升級固件,系統會進入 recovery 模式,格式化所需的分區,之后才重啟進入安卓系統。將 misc 分區清空,系統啟動時就會加載 boot 和 kernel 分區,從而進入 Android;而往 misc 分區寫入 “boot-recovery” 命令,系統啟動時就會加載 recovery 分區,從而進入 Linux 系統。開發時,可以用燒寫工具燒寫 misc 分區,從而控制進入哪個系統。
將安卓系統本來的 recovery 分區替換成 Linux 系統的內核和 initramfs,從而實現雙系統的啟動,這很簡單直觀,卻去掉了安卓急救系統。安卓的急救系統可以做多項系統維護工作,比較重要。某些安卓急救系統的功能,例如恢復出廠設置等,雖然可以在 Linux 系統自己加進相應的命令去處理,但顯得煩瑣而且容易出錯;另一些功能,如 OTA 升級,不是無法替代,就是成本太高,需要移植相關模塊。安卓急救系統需要保留,但 recovery 分區又被替換掉,該怎樣去實現呢?方法可以有多種,例如修改 U-Boot, 增加另一分區的引導。我們的目標是系統的修改保持在最低限度內,從而降低復雜性?,F在采用的方法,是保留 recovery 分區為 Linux 系統的內核和 initramfs 不變,將安卓系統的急救系統(即原 recovery 分區里的安卓系統的內核和 initramfs)里的 initramfs 放在 backup 分區里(該分區一般沒有用上),然后修改 Linux 系統的 initramfs 里的初始化流程:
1. 判斷 misc 分區是否有特殊的標志內容“firefly-linux”,如果沒有,則轉 6。 2. 判斷 backup 分區是否含有安卓急救系統的 initramfs,如果沒有,則轉 6。 3. 提取 backup 分區的 initramfs,解壓至 /root 目錄中。 4. 將 /proc, /sys, /dev 等重要的系統目錄移到 /root 中 (mount –n –o move)。 5. 執行 exec chroot /root /init 命令,將 /root 目錄切換成新的根目錄,并執行里面的 init 程序,從而引導安卓系統本身的急救系統。操作完成。 6. 走原有流程,正常加載 Linux 系統。操作完成。
采用這樣的修改,用 Linux 系統的 initramfs 有選擇地去加載安卓的急救系統,便可以達到要求。Linux 系統的 initramfs 的 init程序是 shell 腳本,修改和調試起來非常方便安卓系統的急救程序無需任何修改。剩下要做的,就要修改安卓系統的切換系統菜單項,將入切換到 Linux 系統的特殊標志內容“firefly-linux”寫到 misc 分區即可。如此修改,可以最大程序上兼容原有系統:
實現見以下提交: https://github.com/TeeFirefly/initrd/commit/24035459bbb5d84e4408e0901e488d88d5014af0
很簡單,寫個腳本 /usr/local/bin/b2android.sh 將 misc 分區清空,然后重啟即可:
sudo dd if=/dev/zero of=/dev/block/mtd/by-name/misc bs=16k count=3 sudo reboot
相關的提交為:
- https://bitbucket.org/T-Firefly/firenow-lollipop/commits/c3f07c18e3da2359877722910fea2973a58683f4
- https://bitbucket.org/T-Firefly/firenow-lollipop/commits/94b43d7e9c4636fa7c20f9c9316c4514ad9d12bb
- https://bitbucket.org/T-Firefly/firenow-lollipop/commits/be90bd3d29cf7c50956df3091189db0d117909e4
主要的切換腳本為 device/rockchip/common/boot-linux.sh
#!/system/bin/sh out=/dev/block/rknand_misc pad() { busybox dd if=/dev/zero bs=$1 count=1 } echo_pad() { echo -n "$1" | busybox dd bs=$2 conv=sync; } { pad 16k echo_pad boot-recovery 8k echo_pad firefly-linux 8k pad 16k } > $out
-
嵌入式主板
+關注
關注
7文章
6086瀏覽量
35643 -
安卓
+關注
關注
5文章
2137瀏覽量
57656 -
Firefly
+關注
關注
2文章
538瀏覽量
7141
發布評論請先 登錄
相關推薦
Firefly-RK3288雙系統啟動的設計和實現
Firefly-RK3128開發板上手教程(上)
Firefly-RK3128開發板上手教程(下)
Firefly-RK3128 MIPI DSI使用步驟說明
firefly RK3128開發板介紹
![<b class='flag-5'>firefly</b> <b class='flag-5'>RK3128</b>開發板介紹](https://file.elecfans.com/web1/M00/AB/47/o4YBAF22v7qAfi4JAAEGUSHQ2aM083.png)
Firefly-RK3128主板ADC簡介
![<b class='flag-5'>Firefly-RK3128</b><b class='flag-5'>主板</b>ADC簡介](https://file.elecfans.com/web1/M00/AF/4F/pIYBAF3fkN-AD72HAAItN2Ds-Vo243.png)
Firefly-RK3128主板接口定義介紹
![<b class='flag-5'>Firefly-RK3128</b><b class='flag-5'>主板</b>接口定義介紹](https://file.elecfans.com/web1/M00/AB/A2/pIYBAF22z-CABYwNAAxvOtqBG94169.png)
評論