標籤:systems 貪婪 利用 locate mkfs.ext2 相對 卸載 使用 binary
1. 前言:
刷機,似乎是安卓手機使用者的一項專利,可是,會刷機的使用者一般都是喜新厭舊的角色。
一個系統用久了。就想換到還有一個系統。或者認為沒有原來的好,或者又認為要換回去。這樣又要重刷。
可是刷來刷去都麻煩啊,而且每次刷機也不是沒有風險的,一不小心就可能造成關鍵資料的丟失。
沒有解決的方法嗎?
有。雙系統!
甚至三系統,四系統!
!
本文就是解決問題的,而且用本文中的方法,全然能夠實現一鍵安裝,一鍵卸載系統的功能。把系統的安裝和卸載變成apk的安裝和卸載一樣簡單。
(說明下,以下的方法以三星i93xx系列的手機為例的)
2. 先來簡介下安卓系統的啟動過程:
在手機上電時,最先啟動並執行整合到CPU晶片上的一段rom裡的程式
這段程式負責載入nand flash或者sd卡上的引導程式,引導程式一般來講都是uboot
uboot會完畢一些裝置的初始化,這裡非常重要的部分就是nand flash,以便將linux核心載入讀到記憶體裡並執行。
載入的核心依據情況,有可能是boot分區裡的核心。也有可能是recovery分區裡的核心。
核心跑起來之後,首先會掛載ramdisk到"/"根資料夾,然後運行/init建立第一個進程
init讀取/init.rc,進行進一步的初始化。完畢如建立檔案夾。設定許可權,掛載data,system,cache分區。啟動一系列的service。包含重要的zygote進程
zygote進程又會建立system_server進程以完畢進一步的初始化工作,並載入一系列的apk進程。
3. 接下來看下,一個安卓系統所需的分區:
一個uboot分區,負責引導核心
一個核心分區
一個system分區,用於存放安卓的系統程式和檔案
一個data分區。用於存放系統的資料,apk程式。以及apk程式的資料等等。
一個cache分區。一般用於升級之用,用於儲存ota升級包,升級日誌等等。
4. 再按下來看下雙系統的實現方案:
4.1 核心的引導問題
這是一個比較頭痛的問題。上面講到。核心是由uboot通過boot分區或者recovery分區載入進來的
假設還要載入其他分區的核心。就要考慮改動uboot的配置參數或者代碼了
uboot的代碼我們肯定是沒有 的,
儘管一般來講uboot的配置參數往往也是儲存在某個分區裡的。但一般都是加密的,所以也改不了。
所以我們僅僅能考慮利用己有的分區了。
最簡單的方法就是覆蓋boot分區,將第二個安卓的boot.img寫到boot分區,
然後寫一個apk,當要啟動哪個系統時,就把哪個系統的boot.img寫到boot分區。
這樣的方式的缺點是切換麻煩,每次切換都要先啟動當中的一個系統,然後執行apk進行切換。
然後,我們把貪婪的目光瞄向了recovery分區。
大家知道,recovery分區一般在系統升級或者恢復出廠預設值的時候才會用到。所以我們考慮對recovery分區進行下手。
最簡單的方法是把第二個安卓系統boot.img放到recovery分區裡,這樣能夠實現觸發進recovery來引導第二個安卓系統了。
當然。也能夠在recovery分區裡再放一個定製的uboot,從而實現更加靈活的載入方式。如能夠顯示引導菜單等等,再如從SD卡裡載入核心等等。
可是這還是要有uboot的原始碼才行。
當然,在使用recovery分區之前,要對recovery分區作下備份。
使用recovery分區作為第二個系統的linux引導分區還有個優點就是一般手機都有開機進recovery的快速鍵。
如三星的手機通常是在開機時同一時候按下:音量加,HOME,POWER三個按鍵就能夠進recovery.
從而實現方便的系統切換。
4.2 system,data分區的建立問題:
攻克了引導的問題。再來看下system和data分區的建立問題。
由於cache分區僅僅在升級的時候會用到,所以兩個系統能夠共用,不用再建立了。
1)又一次分區法:
也就是為每一個系統建立不同的分區,這樣的方法須要對儲存空間進行又一次劃分。顯然比較麻煩,風險也比較高。可行性比較低。
2)使用虛擬磁碟的方案
大家一定對ubuntu可以在windows下直接安裝的方式印象十分深刻
實際上ubuntu可以在不又一次分區的情況下實現安裝真是利用的虛擬磁碟實現的。
相對於第一種方案。避免了又一次分區的麻煩。
虛擬磁碟是linux下非常早核心就已經支援了,是非常成熟的技術了。
所以這裡虛擬磁碟是最好的選擇,而且藉助於虛擬磁碟。我們不僅能夠實現雙系統,還能夠實現三系統。四系統,這全然取決於儲存空間。
5. 理論講清楚了,接下來,看下詳細怎樣幹吧。
首先,我們要建立一個system虛擬磁碟,這裡有兩種方法:
一種是從img直接產生虛擬磁碟,還有一種方法是要將一個ota升級包中的system分區寫到虛擬磁碟中。
第一種方法比較簡單,僅僅要運行一條命令就可以:
simg2img system.img system.disk
simg2img能夠在編譯完的out/host/linux-x86/bin/檔案夾下找到
6.5 假設你拿到的是一個zip格式的ota升級包,內容類別似圖中所看到的:
那麼製作system虛擬磁碟就稍微有些麻煩了,看下怎樣製作:
須要改動壓縮包的META-INF\com\google\android檔案夾下的update-script指令碼
1)去除format和mount /system分區的指令碼
format("ext4", "EMMC", "/dev/block/mmcblk0p9", "0", "/system");mount("ext4", "EMMC", "/dev/block/mmcblk0p9", "/system");
2)去除寫boot.img和data分區的指令碼
package_extract_file("boot.img", "/dev/block/mmcblk0p5");
這裡要千萬小心,一定要把boot.img的寫操作去掉
總之。去除一卻和system分區無關的操作,去除/system分區的格式化操作和掛載操作
這裡一定要小心+細心。
3)接下來將解壓出來的ota升級包又一次打包成zip檔案
4)然後進入recovery進行進一步的操作(這裡recovery最好是第三方的recovery。如cm的recovery,命令比較豐富,比較好操作)
5)用usb串連手機,進入recovery後,adb會自己主動串連到recovery,然後依次運行例如以下命令:
在運行以下的指令碼之前請確認你的手機能夠在shell中擷取系統許可權
將升級指令碼運行程式傳到手機的/data/local/tmp檔案夾,update-binary在升級包裡的META-INF\com\google\android檔案夾,和updater-script同一個檔案夾。
adb push update-binary /data/local/tmp
將改動過update-script的ota升級包傳到手機的對應檔案夾
adb push ota.zip /data/local/tmp
切換到root許可權
adb shellsu
建立一個800M大小的虛擬磁碟
cd /data/local/tmpdd if=/dev/zero of=system.disk bs=1024 count=819200
loop虛擬磁碟system.disk
busybox losetup /dev/block/loop7 system.disk
對虛擬磁碟進行格式化
busybox mkfs.ext2 /dev/block/loop7
掛載虛擬磁碟到/system檔案夾
busybox mount -o loop -t ext4 /dev/block/loop7 /system
改動update-binary為可運行
chmod 777 update-binary
開始運行update-script指令碼,把ota升級包安裝到/system檔案夾
./update-binary 2 0 ota.zip
卸載system虛擬磁碟
umount /systembusybox losetup -d /dev/block/loop7
退出系統許可權
exit
退出adb shell
exit
從手機上將system.disk傳到PC:
adb pull /data/local/tmp/system.disk
這樣,一個燒餅--system虛擬磁碟就做好了:)。
7. 接下來看下怎樣去建立一個ext4格式的data虛擬磁碟
dd if=/dev/zero of=data.disk bs=1024 count=30720 busybox losetup /dev/loop0 data.disk mkfs.ext4 -m 1 -v /dev/block/loop7
8. 接下來看下怎樣去掛載虛擬磁碟
1)先要對boot.img動手術。先要對其解壓, 網路上應該有解壓的工具。可是我一般都喜歡自己寫一些小工具來完畢一些簡單的任務,一來加深認識。二來也方便功能的擴充。
所以,這裡就用python寫了個解壓的程式:
unpack.py""" unsigned char magic[BOOT_MAGIC_SIZE]; unsigned kernel_size; /* size in bytes */ unsigned kernel_addr; /* physical load addr */ unsigned ramdisk_size; /* size in bytes */ unsigned ramdisk_addr; /* physical load addr */ unsigned second_size; /* size in bytes */ unsigned second_addr; /* physical load addr */ unsigned tags_addr; /* physical addr for kernel tags */ unsigned page_size; /* flash page size we assume */ unsigned unused[2]; /* future expansion: should be 0 */ unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */ unsigned char cmdline[BOOT_ARGS_SIZE]; unsigned id[8]; /* timestamp / checksum / sha1 / etc */"""import sysimport structimport subprocessBOOT_MAGIC_SIZE = 8BOOT_NAME_SIZE = 16BOOT_ARGS_SIZE = 512boot_img = Nonekernel_img = Noneramdisk_img = Noneramdisk_cpio = Noneramdisk_out = Noneif len(sys.argv) >= 2: boot_img = sys.argv[1]else: boot_img = ‘boot.img‘if len(sys.argv) >= 3: kernel_img = sys.argv[2]else: kernel_img = ‘kernel.img‘if len(sys.argv) >= 4: ramdisk_img = sys.argv[3]else: ramdisk_img = ‘ramdisk.img‘if len(sys.argv) >= 5: ramdisk_cpio = sys.argv[4]else: ramdisk_cpio = ‘ramdisk-m.cpio‘if len(sys.argv) >= 6: ramdisk_out = sys.argv[5]else: ramdisk_out = ‘ramdisk‘f = open(boot_img,‘rb‘)magic = f.read(BOOT_MAGIC_SIZE)kernel_size = f.read(4)kernel_size = struct.unpack(‘I‘,kernel_size)[0]print kernel_sizekernel_addr = f.read(4)kernel_addr = struct.unpack(‘I‘,kernel_addr)[0]print "0x%x"%(kernel_addr,)ramdisk_size = f.read(4)ramdisk_size = struct.unpack(‘I‘,ramdisk_size)[0]print ramdisk_sizeramdisk_addr = f.read(4)ramdisk_addr = struct.unpack(‘I‘,ramdisk_addr)[0]print "0x%x"%(ramdisk_addr,)second_size = f.read(4)second_size = struct.unpack(‘I‘,second_size)[0]print second_sizesecond_addr = f.read(4)second_addr = struct.unpack(‘I‘,second_addr)[0]print "0x%x"%(second_addr,)""" unsigned tags_addr; /* physical addr for kernel tags */ unsigned page_size; /* flash page size we assume */ unsigned unused[2]; /* future expansion: should be 0 */"""f.seek(BOOT_MAGIC_SIZE + 3*2*4 + 4+4+4*2)""" unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */ unsigned char cmdline[BOOT_ARGS_SIZE];""" name = f.read(BOOT_NAME_SIZE)print namecmdline = f.read(BOOT_ARGS_SIZE)print cmdlinepage_size = 2048f.seek(2048)data = f.read(kernel_size)with open(kernel_img,‘wb‘) as ff: ff.write(data) ramdisk_offset = 2048 + (kernel_size+page_size-1)/page_size*page_sizeprint ‘ramdisk_offset:‘,ramdisk_offsetf.seek(ramdisk_offset)data = f.read(ramdisk_size)with open(ramdisk_img,‘wb‘) as ff: ff.write(data)cmd = ‘gzip -d -r < %s >%s‘%(ramdisk_img,ramdisk_cpio)subprocess.check_output(cmd,shell=True)cmd = ‘mkdir %(ramdisk)s;cd %(ramdisk)s;cpio -i < ../ramdisk-m.cpio‘%{‘ramdisk‘:ramdisk_out}subprocess.check_output(cmd,shell=True)
這個程式會從boot.img中提取出kernel.img和ramdisk.img。而且預設將zip格式的ramdisk.img解壓到ramdisk檔案夾
2)改動fstab.smdk4x12
原內容:
# Android fstab file.#<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags># The filesystem that contains the filesystem checker binary (typically /system) cannot# specify MF_CHECK, and must come before any filesystems that do specify MF_CHECK/dev/block/mmcblk0p9 /system ext4 ro,errors=panic wait/dev/block/mmcblk0p3 /efs ext4 nosuid,nodev,noatime,noauto_da_alloc,discard,journal_async_commit,errors=panic wait,check/dev/block/mmcblk0p8 /cache ext4 nosuid,nodev,noatime,noauto_da_alloc,discard,journal_async_commit,errors=panic wait,check# data partition must be located at the bottom for supporting device encryption/dev/block/mmcblk0p12 /data ext4 nosuid,nodev,noatime,noauto_da_alloc,discard,journal_async_commit,errors=panic wait,check,encryptable=footer# VOLD/devices/platform/s3c-sdhci.2/mmc_host/mmc1/ /storage/extSdCard vfat default voldmanaged=sdcard:auto/devices/platform/s5p-ehci/usb1 /storage/UsbDriveA vfat default voldmanaged=sda:auto/devices/platform/s5p-ehci/usb1 /storage/UsbDriveB vfat default voldmanaged=sdb:auto/devices/platform/s5p-ehci/usb1 /storage/UsbDriveC vfat default voldmanaged=sdc:auto/devices/platform/s5p-ehci/usb1 /storage/UsbDriveD vfat default voldmanaged=sdd:auto/devices/platform/s5p-ehci/usb1 /storage/UsbDriveE vfat default voldmanaged=sde:auto/devices/platform/s5p-ehci/usb1 /storage/UsbDriveF vfat default voldmanaged=sdf:auto
改動後的內容:
# Android fstab file.#<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags># The filesystem that contains the filesystem checker binary (typically /system) cannot# specify MF_CHECK, and must come before any filesystems that do specify MF_CHECK/dev/block/mmcblk0p3 /efs ext4 nosuid,nodev,noatime,noauto_da_alloc,discard,journal_async_commit,errors=panic wait,check/dev/block/mmcblk0p8 /cache ext4 nosuid,nodev,noatime,noauto_da_alloc,discard,journal_async_commit,errors=panic wait,check# data partition must be located at the bottom for supporting device encryption/dev/block/mmcblk0p12 /dat ext4 nosuid,nodev,noatime,noauto_da_alloc,discard,journal_async_commit,errors=panic wait,check,encryptable=footer# VOLD/devices/platform/s3c-sdhci.2/mmc_host/mmc1/ /storage/extSdCard vfat default voldmanaged=sdcard:auto/devices/platform/s5p-ehci/usb1 /storage/UsbDriveA vfat default voldmanaged=sda:auto/devices/platform/s5p-ehci/usb1 /storage/UsbDriveB vfat default voldmanaged=sdb:auto/devices/platform/s5p-ehci/usb1 /storage/UsbDriveC vfat default voldmanaged=sdc:auto/devices/platform/s5p-ehci/usb1 /storage/UsbDriveD vfat default voldmanaged=sdd:auto/devices/platform/s5p-ehci/usb1 /storage/UsbDriveE vfat default voldmanaged=sde:auto/devices/platform/s5p-ehci/usb1 /storage/UsbDriveF vfat default voldmanaged=sdf:auto
這裡主要是刪除了system分區的掛載,而且把data分區由原來的掛載到dat改為掛載到/dat檔案夾
由於這裡我們實際上要掛載的是虛擬磁碟
3)init.rc中:
改動on init:
在
mkdir /system
後面加上:
mkdir /dat
4)init.smdk4x12.rc中,改動on fs
在
mount_all /fstab.smdk4x12
後面加上:
mount ext4 [email protected]/dat/system.disk /system rw wait noatime mount ext4 [email protected]/dat/data.disk /data wait nosuid nodev noatime
總結:
1)這裡。先把應該mount到/data檔案夾的分區mount到/dat檔案夾
2)接下來的兩行指令碼就把虛擬磁碟給分別掛載到/system和/data
3) 是的,so easy!,可是當功能還未實現時,那種從醞釀到嘗試,再到成功的過程還有有一翻體會的,儘管關鍵的虛擬分區的掛載是在三心二意的情況下完畢的(一邊看著CCTV的記錄片,哈哈,千萬不要學)。
9. 又一次把kernel和ramdisk打包成boot.img
改動好的ramdisk,接下來就要對其進行又一次打包成boot.img了,這裡會用到三條命令:mkbootfs,minigzip和mkbootimg。mkbootfs把ramdisk裡的全部檔案打包成cpio格式的檔案
minigzip再對其進行壓縮,mkbootimg從kernel和ramdisk.img產生boot.img,用法例如以下:
mkbootfs ramdisk | minigzip > ramdisk.img
mkbootimg --kernel kernel.img --ramdisk ramdisk.img --output boot.img
10. 接下來,到了最後一步了。怎樣安裝第二個Android作業系統?
安裝的工作事實上真的非常easy,把boot.img寫到recovery分區,把system.disk。data.disk複製到/data分區。
當然。也能夠把system.disk和data.disk放到外部SD卡中。這要涉及到改動ramdisk
11.最後。總結下全文吧:
在手機上實現雙系統已經不是什麼新技術了,在用虛擬磁碟的方式實現雙系統之後。後來百度了下,看到11年寫的一篇文章, 是在SD卡上進行分區來實現雙系統的,操作比較麻煩,
切換還要啟動到當中一個系統通過刷boot分區來實現系統的切換,還不如本文直接刷recovery分區的方式來的方便。
本文的雙系統實現事實上還是比較簡單的:
1)將system.img通過simg2img轉換成能夠直接通過loop掛載的格式system.disk
2)建立data分區虛擬磁碟data.disk
3)改動boot.img中的啟動相關的指令碼,實現掛載虛擬磁碟,並又一次打包
4)將system.disk。data.disk放到原data分區
5)將又一次打包的boot.img刷到recovery分區。
12. Is this the end or just the beginning?
假設是網路機頂盒,上面的已經足夠了,可是我們是在手機上建立雙系統,所以仍然有一段路要走。下面是須要思考的問題:
1)怎樣保持雙系統中的通訊錄的同步問題
2)怎樣同步雙系統中經常使用工具的資料,如聊天記錄等等。
3)怎樣處理不同版本號碼的android系統基帶的不相容性問題。
4) 怎樣處理efs分區在不同版本號碼的android系統不相互相容問題。
5) 怎樣實現Android+WP雙系統
6) 怎樣實現三系統,四系統?
7) ...
(完)
Android雙系統實現