zoukankan      html  css  js  c++  java
  • Android双系统实现

    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-INFcomgoogleandroid文件夹下的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-INFcomgoogleandroid文件夹,和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) ...

    (完)

  • 相关阅读:
    第十四周学习进度条
    对txt文本中字符的统计
    JAVA项目中的常用的异常处理情况总结
    动手动脑:异常处理
    第一次尝试连接数据库
    Vuejs 实现权限管理
    vue 中的 ... (三个点的用法)
    Vue 中怎么发起请求(二)
    Vue 中怎么发起请求(一)
    Vue添加请求拦截器
  • 原文地址:https://www.cnblogs.com/cynchanpin/p/6752219.html
Copyright © 2011-2022 走看看