1. 前言:node
刷機,彷佛是安卓手機用戶的一項專利,但是,會刷機的用戶通常都是喜新厭舊的角色。python
一個系統用久了。就想換到還有一個系統。或者認爲沒有原來的好,或者又認爲要換回去。這樣又要重刷。linux
但是刷來刷去都麻煩啊,並且每次刷機也不是沒有風險的,一不當心就可能形成關鍵數據的丟失。android
沒有解決的方法嗎?shell
有。雙系統!ubuntu
甚至三系統,四系統!windows
!微信
本文就是解決問題的,並且用本文中的方法,全然可以實現一鍵安裝,一鍵卸載系統的功能。把系統的安裝和卸載變成apk的安裝和卸載同樣簡單。網絡
(說明下,如下的方法以三星i93xx系列的手機爲例的)async
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 shell su
建立一個800M大小的虛擬磁盤
cd /data/local/tmp dd 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 /system busybox losetup -d /dev/block/loop7
退出系統權限
exit
退出adb shell
exit
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 sys import struct import subprocess BOOT_MAGIC_SIZE = 8 BOOT_NAME_SIZE = 16 BOOT_ARGS_SIZE = 512 boot_img = None kernel_img = None ramdisk_img = None ramdisk_cpio = None ramdisk_out = None if 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_size kernel_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_size ramdisk_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_size second_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 name cmdline = f.read(BOOT_ARGS_SIZE) print cmdline page_size = 2048 f.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_size print 'ramdisk_offset:',ramdisk_offset f.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 loop@/dat/system.disk /system rw wait noatime mount ext4 loop@/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) ...
(完)