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) ...
(完)