背景
對於安卓開發而言,了解各鏡像的意義、內容以及如何制作,有極大的意義。
注意,ROM中的5個鏡像文件的擴展名都是img,但其格式卻不同,也就是說不能使用同一種方法對其進行格式解析。
系統鏡像(System.img)
系統鏡像用於存儲Android系統的核心文件,將其解壓出來,就是設備中/system
目錄,里面包含了Android系統主要的目錄和文件。一般這些文件是不允許修改的。
系統鏡像對應的文件名一般叫system.img
。
當然,系統鏡像的文件可以任意命名,之所以叫system.img
是為了與生成鏡像文件之前的system目錄保持一致,這樣比較容易與其他類型的鏡像文件區分。
system.img
可以添加:
- Android系統應用
- 更多的library
為了搞清楚system.img
鏡像中的內容,可以將其解壓:
- 舊版的鏡像是
yaffs
格式的(通過mkyaffs2image
工具制作的),可以使用unyafss
命令對其解壓。
Android源代碼中並未提供該命令,所以讀者可以到 http://code.google.com/p/unyaffs/downloads/list 下載unyaffs的二進制程序和源代碼。
unyaffs system.img
如果對編譯Android源代碼生成的system.img文件執行上面的命令,可以完美的將system.img文件還原成system目錄,會從system目錄中看到相應的子目錄,例如,/system/app、/system/lib等,實際上,system.img文件就是out/target/product/generic/system
中的文件壓縮生成的。
- 另外,高版本Android的system.img通常是ext4格式的文件系統鏡像(通過
make_ext4
工具制作),可以使用simg2img
工具進行轉換后掛載。
由於system.img是壓縮格式,所以並不能直接使用mount命令掛載。在編譯Android 源代碼后會在Android源代碼目錄/out/host/linux-x86/bin
目錄生成一個simg2img命令行工具
建議將該目錄加到PATH環境變量中,因為當中的各種命令行工具會被經常使用。
simg2img
可以通過如下的命令將system.img
轉化為普通的Linux鏡像文件(system.img.raw
);
# 轉換
$ simg2img system.img system.img.raw
# 查看 鏡像格式
$ file system.img
system.img: Android sparse image, version: 1.0, Total of 229673 4096-byte output blocks in 22 input chunks.
# 查看 鏡像格式
$ file system.img.raw
system.img.raw: Linux rev 1.0 ext2 filesystem data, UUID=efee3fdf-d4f1-5e88-9f69-57632c5d8db4 (extents) (large files) (huge files)
此后,我們就可以進行掛載了:
## 掛載到 ~/debug/system
mkdir /mnt/system -p
sudo mount system.img.raw /mnt/system
執行文上面的命令后,進到掛載目錄,所有的目錄都是可讀寫的。
文件列表如下:
目錄 | 意義 |
---|---|
app | 存放一般的apk文件。 |
bin | 存放一些Linux的工具,但是大部分都是toolbox的鏈接. |
etc | 存放系統的配置文件。 |
fonts | 存放系統的字體文件。 |
framework | 存放系統平台所有jar包和資源文件包。 |
lib | 存放系統的共享庫。 |
media | 存放系統的多媒體資源,主要是鈴聲。 |
priv-app | android4.4開始新增加的目錄,存放系統核心的apk文件。 |
tts | 存放系統的語言合成文件。 |
usr | 存放各種鍵盤布局,時間區域文件。 |
vendor | 存放一些第三方廠商的配置文件、firmware以及動態庫。 |
xbin | 存放系統管理工具,這個文件夾的作用相當於標准Linux文件系統中的sbin. |
build.prop文件 | 系統屬性的定義文件。 |
將system.img.raw
掛載出來后,該目錄中的內容實際上與system.img
中的內容完全一樣,現在可以任意修改目錄中的內容,再進行打包以達到更新system.img的目的。
例如,添加或替換目錄中的apk文件,或更換開機動畫。
在修改完系統鏡像后,還需要使用make_extfs
命令將掛載目錄重新生成system.img
文件(EXT4文件系統)。
我們可以在Linux終端執行如下的命令生成system.img
文件。
make_ext4fs -s -l 1024M -a system system.img /mnt/system
在執行make_ext4fs 命令使用了3個命令行參數,這些參數的含義如下:
-s
:生成Spare格式的鏡像。這種格式的鏡像文件的尺寸會更小,但無法直接使用mount命令掛載。要想掛載Spare格式的鏡像文件,需要首先使用simg2img命令按着前面描述的方式進行轉換。如果不加-s參數,生成的system.img文件是可以直接通過mount掛載。不過不管是不是Spare格式的系統鏡像文件,Nexus 7都可以使用(其他的Android設備應該也可以),但建議生成Spare格式的鏡像文件,因為這樣的鏡像文件尺寸更小。-l
: 鏡像的尺寸。該參數指定的值並不是生成鏡像文件(r如system.img)的實際尺寸,而是文件系統的尺寸。這有些類似在Windows中建立的心得邏輯分區,而該參數指定的值就是邏輯分區的尺寸,生成的鏡像文件的尺寸不能大於文件系統的尺寸。例如官方提供的用於Nexus 7的system.img文件(Spare格式的鏡像文件)的尺寸大小越是480M,-a
: 指定掛載點,這里是system.
重新生成經過修改的system.img文件后,首先讓設備進入Bootloader模式,然后執行下面的命令即可刷機:
fastboot flash system system.img
用戶數據鏡像(userdata.img)
用戶鏡像用來存儲與用戶相關的數據,一般對應的文件名是userdata.img
(也可以是任何文件名,為了方便,我們將userdata稱為用戶鏡像文件)。
這些數據大多都是有用戶在使用Android設備的過程中產生的,例如,通過Google play安裝的第三方APK程序,用戶的配置文件等。當然,在制作ROM時,也可以將部分數據放到userdata.img中。
例如,如果允許用戶使用普通的方法卸載ROM內置的應用,就可以將APK文件放到userdata.img文件中 (這里是普通的應用程序,而system.img放入的是系統應用程序)
userdata.img
有如下兩個功能:
- 封裝與用戶相關的文件(如果是APK程序,還允許卸載這些程序),並連同ROM一起發布,或單獨刷userdata.img文件。
- 規定Android設備存儲空間的大小。
在Android設備中可供用戶操作的存儲區域通常有如下:
- RAM :RAM就是傳統意義上的內存,與PC的內存是一個概念,只有在通電時才能存儲數據,斷點后所有數據將自動消失,所有要運行的程序都需要調用RAM。
- 存儲空間:現在所有的Android設備都有都帶有一定大小的內部存儲器(嵌入到芯片上,類似於內部嵌入一個SD卡),用於存儲一些隨機器發布的系統和應用程序。而PC除了RAM,就是硬盤了。
- 外部存儲器(通常是SD卡):有的Android系統加入了OTG(On-The-Go)支持,所以通過OTG連接的U盤、移動硬盤也應屬於外部存儲器,有一些Android設備(如Nexus 7) 不支持插入SD卡。
Android系統通過Linux 文件系統將可用的存儲空間划分成不同區域,userdata.img
就屬於userdata
分區,該分區就是前面所說的存儲空間。而剩余的內存空間就會將其作為外部存儲器。
如果Android設備不支持外部存儲器,userdata.img
就不能太大,否則系統將無法利用剩余的空間映射SD卡(/sdcard
,目錄)。
學習userdata.img
的第一步就是解剖他。方法與解剖system類似。
首先需要使用simg2img
命令將userdata.img
文件還原成非壓縮格式的鏡像文件,這樣可以直接使用mount
命令將userdata.img
文件掛載到某個目錄,進而查看userdata.img
中的內容。
# 生成還原后的userdata.img.raw文件
simg2img userdata.img userdata.img.raw
# 掛載userdata.img.raw文件
mkdir -p /mnt/rom/userdata
mount userdata.img.raw /mnt/rom/userdata
如果掛載成功,會在/mnt/rom/userdata 目錄看到userdata.img 中的內容。
通常該目錄除了“lost+found”(該目錄一般為空,在系統非法關機時會存放一些文件) 系統目錄外,什么都沒有。
讀者可以執行下面的命令查看當前掛載的用戶鏡像尺寸。
df -h
現在可以在/mnt/rom/userdata
目錄放一些目錄或文件,例如,將Test.apk
作為普通的Android應用放入userdata.img
,如要在/mnt/rom/userdata
目錄建立一個app子目錄,然后將Test.apk文件放入app目錄即可。
在修改完鏡像掛載分區以后,需要使用下面的命令重新打包,為了與userdata.img區別,在這里將該目錄打包成了userdata.img.new。
要注意,在打包的過程中會確定用戶鏡像對應的空間大小,例如,下面的命令生成了最大為128M的用戶鏡像文件(userdata.img.new,ext4格式的鏡像文件)。
make_ext4fs -s -l 128M -a data userdata.img.new /mnt/rom/userdata
注意:用戶鏡像占用的存儲空間不能超過Android設備的內部存儲器的總尺寸,否則即使成功刷機,Android設備也可能會啟動失敗,即使啟動也由於SD卡無法掛載而出現要求輸入密碼(實際上就是映射失敗)的情況。
參數說明:
- 加上"-s"命令行,參數就表示生成的userdata.img.new 文件是壓縮格式,不能直接使用mount命令掛載,需要按着簽名的而方法通過simg2img命令來還原才能掛載到某一目錄。
- “-a”命令行參數后面的是文件系統,這里需要制定data。
接下來可以使用下面的命令將userdata.img.new
文件刷到Android設備上(加上目錄Android設備正處於正常的啟動狀態,並通過USB線PC相連)。
注意:在刷userdata.img.new文件之前,一定 要備份Android設備上已安裝的應用程序、配置和其他數據,否則這些數據將全部丟失(SD卡中的數據不會丟失)。
adb reboot bootloader
fastboot flash userdata userdata.img.new
fastboot reboot
上面的命令在刷完userdata.img.new 后,會重啟Android設備。通過“設置”->"存儲"可以查看使用情況。
內存磁盤鏡像(ramdisk.img)
內存磁盤鏡像存儲了Linux內核啟動時要裝載的核心文件,通常的鏡像文件名為ramdisk.img。
之所以稱ramdisk.img
為內存磁盤鏡像,是因為ramdisk.img
中的文件映射的實際上都是內存中的文件,也就是說,即使有權修改init.rc
等文件,也只是修改原始文件在內存中的鏡像,重新啟動Android設備后,又會恢復到最初的狀態。而修改這些文件的唯一方法就是重新制作ramdisk.img
文件,並連同Linux內核二進制文件(zImage
)生成boot.img
文件,並且刷入設備中才可以生效。
ramdisk.img是boot.img中重要的組成部分之一,盡管ramdisk.img
需要放在Linux內核鏡像(boot.img
)中,但卻屬於Android源代碼的一部分。也就是說,在編譯Android 源代碼后,會生成一個ramdisk.img
文件,其實該文件就是root目錄壓縮后生成的文件。
ramdisk.img
文件中封裝的內容是Linux內核與Android系統傑出的第一批文件,其中有一個非常重要的init命令(在root目錄中可以找到該命名文件),該命令用於讀取init.rc
以及相關配置文件中的初始化命令。
其實ramdisk.img
文件只是一個普通的zip壓縮文件,可以直接使用gunzip
命令解壓,不過解壓后並不是原文件和目錄,而是有cpio命令備份的文件,所以還需要使用cpio繼續還原。
假設ramdisk.img
文件在當前目錄下,則還原ramdisk.img文件的命令如下:
mkdir ramdisk
cp ramdisk
gunzip -c ../ramdisk.img > ../ramdisk.cpio
cpio -i < ../ramdisk.cpio
也可以將最后兩行命令合成如下的一行。
gunzip -c ../ramdisk.img | cpio -i
執行上面的命令后,就會在ramdisk
目錄中看到內存磁盤鏡像還原后的目錄結構,如果現在要修改init.rc
等配置文件,可以自己在ramdisk目錄中找到相應的文件並修改。例如,有Linux的瑞士軍刀之稱busybox
,可以放到ramdisk
中的sbin
目錄下。這樣在Recovery模式下就可以使用busybox命令完成很多操作了。
修改完ramdisk目錄的內容后,就需要使用下面的命令將ramdisk目錄重新生成ramdisk.img文件。
為了與原來的ramdisk.img文件有所區別,這里生成了ramdisk.img.new文件,在執行下面的命令之前,要保證Linux終端的當前目錄是ramdisk。
cd XX # ramdisk目錄
mkbootfs . | minigzip > ../ramdisk.img.new
Linux內核鏡像(boot.img)
Linux內核鏡像包含了內核二進制(zImage
)和內存磁盤鏡像(ramdisk.img
)。
一般對應的鏡像文件是boot.img
(也可以是任何其他的名字)。
由於ramdisk.img
中包含的init命令是與Linux內核第一個交互的程序,所以在boot.img
中需要同時包含Linux內核(zImage
)和ramdisk.img
。
當Linux內核調用init后,系統就會根據init.rc
及其相關文件的代碼對整個Android系統進行初始化。其中主要的初始化工作就是建立如/system
、/data
等系統目錄,然后使用mount命令將相應的鏡像掛載到這些目錄上。
Android源代碼經過編譯后,也可以在其中找到對boot.img
解壓和生成boot.img
文件的命令。
其中unpackbooting
為解壓命令,mkbooting
命令可以將zImage
和ramdisk.img
文件合並成boot.img
。
下面先來用unpackbooting
命令將boot.img解壓,再看看boot.img
是不是有zImage
和ramdisk.img
文件組成的。
假設boot.img
文件(我們可以使用從其他Rom壓縮包中獲得的boot.img,也可以使用通過Android源代碼生成的boot.img)在當前目錄中,使用下面的命令可以將boot.img文件解壓到boot目錄中。
mkdir boot
cd boot
unpackbootimg -i ../boot.img
執行完上面的命令后,會發現boot目錄中多了幾個文件,其中有兩個主要的文件:boot.img-zImage
和boot.img-ramdisk.gz
。
前者是Linux 內核文件(與zImage文件完全一樣),后者是內存磁盤鏡像(與ramdisk.img完全一樣)。為了證明boot.img-ramdisk.gz
與ramdisk.img
文件完全相等,可以使用下面的命令將boot.img-ramdisk.gz
解壓到ramdisk目錄。
mkdir ramdisk
cd ramdisk
gunzip -c ../boot.img-ramdisk.gz | cpio -i
目錄結構與ramdisk.img
一樣。
如果想向init.rc或其他文件中添加新的內容,或在內存磁盤鏡像中添加新的命令,可以修改剛才由boot.img-ramdisk.gz 文件解壓生成的ramdisk目錄中的相應文件和目錄的內容,然后使用下面的命令重新將ramdisk目錄中的相應文件和目錄的內容,然后使用下面的命令重新將ramdisk打包成boot.img-ramdisk.gz.new(當前目錄是ramdisk)。
mkbootfs . | minigzip > ../boot.img-ramdisk.gz.new
接下來回到上一層目錄,然后使用下面的命令將boot.img-zImage
和boot.img-ramdisk.gz.new
文件合並成boot.img.new
文件(為了區分boot.img
,這里生成了boot.img.new
文件)。
mkbootimg --kernel boot.img-zImage --ramdisk boot.img-ramdisk.gz.new -o boot.img.new
如果想修改Linux內核,需要下載Linux內核源代碼(官方和CM都提供了相應Android設備的Linux內核源代碼)
接下來退到上一層目錄,然后使用下面的命令將boot.img-zImage 和boot.img-ramdisk.gz.new
現在可以使用下面的命令重新刷Linux內核(加上Android系統處於正常啟動狀態,並通過USB線和PC相連)。不過要注意的是Linux內核必須與當前Android設備匹配,否則刷完后Android設備有可能起不來。刷Linux內核不會對系統(system.img
)和用戶數據(userdata.img
)造成任何影響。
adb reboot bootloader
fastboot flash boot boot.img,new
fastboot reboot
重啟Android設備后,如果我們修改了Linux內核和內存磁盤鏡像,就會立刻生效。
注意:
boot.img
解壓后,除了生成boot.img-zImage
和boot.img-ramdisk.gz
文件外,還會生成一些其他的文件,如boot.img-base
、boot.img-cmdline
、boot.img-pagesize
等,這些文件都是一些配置文件。例如
boot.img-cmdline
文件中包含了Linux內核啟動時傳入的參數。通常並不需要管這些文件,只需要保持默認值即可)。
設備樹鏡像(dtbo.img)
熟悉嵌入式Linux的讀者應該知道,現在的BootLoader(通常是uboot)一般與dtb文件相配合,以告知Linux有關驅動節點。
因此,這個一般和boot.img
相互作用,由於一般手機用戶很難修改這一部分的內容,因此不做展開。
Recovery鏡像(recovery.img)
Recovery鏡像只用於刷機,通常的鏡像文件名為:receovery.img
。
在學習定制Recovery.img之前,先了解recovery.img到底是個什么東西。
關於如何更深入定制recovery.img,請參考recovery的源代碼。
從本質上說,recovery.img
和boot.img
基本一樣。這就意味着,recovery.img
也是Linux內核(zImage
)和內存磁盤鏡像(ramdisk.img
)組成的。這兩個鏡像中的Linux內核是完全一樣的,區別只是ramdisk.img中的少部分文件存在差異:
最主要的差異是recovery.img和ramdisk.img中的sbin目錄中多了一個recovery命令進入Recovery主界面,而不會正常啟動Android系統。
實現的原理是:
- Recovery.img和boot.img在自己的分區各自有一個Linux內核(zImage),彼此的Linux內核調用的init命令解析的init.rc及其相關文件的內容有一定的差異。
- 而Bootloader根據用戶的選擇決定使用boot.img中Linux內核,還是使用Recovery.img中的Linux內核啟動系統。如果使用前者,Android系統就會正常啟動,如果使用后者,就會進入Recovery選擇菜單,所以recovery.img和boot.img的第二個差異就是其中的init.rc及其相關配置文件的內容略有不同。
從前面的描述還可以看出,recovery.img
和boot.img
其實都是一個最小的運行系統,也就是說他們都各自帶一個滿足最低要求的運行環境(ramdisk.img
)。boot.img
利用這個運行環境監理更大的運行環境(system.img
) ,而recovery.img
就直接使用了這個運行環境進行基本的操作(復制文件、刪除文件、加壓文件、mount等),這些操作也就是Recovery模式下刷機要進行的一些操作。
既然了解了recovery.img
是什么東西,那么就可以解壓recovery.img
,並且重寫生成recovery.img
文件。
假設recovery.img
文件在當前目錄下,解壓recovery.img
:
mkdir recovery
cd recovery
uppackbootimg -i ../recovery.img
執行下面的命令會在recovery目錄下生成如下5個文件:
recovery.img-zImage
recovery.img-ramdisk.gz
recovery.img-cmdline
recovery.img-pagesize
recovery.img-base
其中前兩個分別為recovery.img中的Linux內核和內存磁盤鏡像。可以使用下面的命令解壓recovery.img-ramdisk.gz文件:
mkdir ramdisk
cd ramdisk
gunzip -c ../recovery.img-ramdisk.gz | cpio -i
現在回到上一層目錄,最后按着4.2.4小節的方法重新生成內存鏡像文件(這里為Recovery.img-randisk.gz.new),並使用下面的命令重新生成Recovery鏡像(這里為recovery.img.new )。
重新生成Recovery鏡像文件:
mkbootimg --kernel recovery.img-zImage --ramdisk recovery.img-ramdisk.gz.new -o recovery.img.new
現在可以使用下面的命令重新刷Recovery(加上Android 處在正常啟動狀態),並進入Recovery模式。
adb reboot bootloader
fastboot flash recovery recovery.img.new
fastboot reboot
adb reboot recovery
緩存鏡像(cache.img)
緩存鏡像用於存儲系統或用戶應用產生的臨時數據,通常的鏡像文件名為chche.img。
一般ROM並不需要包含緩存鏡像,不過在這里還是介紹一下如何制作和刷緩存鏡像。
緩存鏡像實際上就是一個空的ext4格式的文件系統鏡像,可以使用下面的命令生成緩存鏡像。
mkdir -p /mnt/rom/cache
make_ext4fs -s -l 256M -a cache cache.img /mnt/rom/cache
可以使用下面的abd命令刷緩存鏡像:
adb reboot bootloader
fastboot flash cache cache.img
fastboot reboot