Android雙系統實現

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

 
從手機上將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 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) ...

 

(完)

相關文章
相關標籤/搜索