文章大纲

  • 引言
  • 一、Android系统的分区
  • 1、/boot 引导分区
  • 2、/system 系统分区
  • 3、/recovery 恢复分区
  • 刷入RE:
  • 4、/data 用户数据区
  • 5、/cache 数据缓存区
  • 6、/misc
  • 7、/sdcard 内部存储分区
  • 8、/sd-ext SD卡扩展分区。
  • 二、BootLoader 和FastBoot
  • 1、BootLoader
  • 2、 FastBoot
  • 三、lunch 命令分析及user和userdebug编译选项含义
  • 1、source build/envsetup.sh 加载命令及编译环境
  • 2、lunch 选择平台等编译选项
  • 3、make 编译

一、Android系统的分区

android 禁用新的启动页 安卓系统禁用程序_系统分区

如上图所示标准Android系统根据不同功能划分为以下分区:

  • /boot
  • /system
  • /recovery
  • /data
  • /cache
  • /misc
  • /sdcard
  • /sd-ext

以上分区他们统一映射到Linux 系统下就分别对应一个文件节点。

1、/boot 引导分区

/boot 引导分区包含了kernel(内核) and ramdisk(虚拟内存盘,一种通过软件将一部分内存(RAM)模拟为硬盘来使用的技术,可以极大的提高在其上进行的文件访问的速度),如果没有此分区,Android设备则不能正常启动。所以只在必要的时候,才去通过Recovery软件擦除(format)这个分区,而一旦擦除,设备只有再重新安装一个新的boot分区(可以通过安装一个包含boot分区的ROM来实现),否则无法启动安卓系统。

2、/system 系统分区

/system 系统分区基本包含了除了内核(kerne)和ramdisk外的整个安卓操作系统。主要包括安卓用户界面、和所有预装的系统应用程序。而擦除这个分区,会删除整个安卓系统,但不会导致不能启动。你可以通过进入Recovery程序或者bootloader程序中,安装一个新ROM 重新刷入系统(国产Android所谓的系统本质上就是定制ROM)。它是存放所有 Google 提供的 Android 组件的地方。这个分区只能以只读方式 mount。这样主要基于稳定性和安全性考虑,即使发生用户突然断电的情况,也依然需要保证 /system 分区的内容不会受到破坏和篡改。

3、/recovery 恢复分区

/recovery 恢复分区在正常分区或内核分区被破坏,不能正常启动时,可以进入此分区进行恢复,他相当与一个简易的OS或BIOS,可以认为是一个boot分区的替代品,通过他可以让我们在这一分区进行备份维护和恢复,我们通常说的刷机便指的是此分区,进入此分区方法有两种:

  • 通过 adb reboot recovery
  • 通过组合键,电源键+音量键

Recovery 更类似于一个小型的管理系统。只不过功能简单,所做的管理有限。在recovery模式下,会加载了部分文件系统,所以才可以读sdcard中的update.zip进行刷机,当然,也可以清除cache和用户数据。该模式可根据用户的需要进行修改,因此有官方recovery模式以及第三方recovery模式。第三方recovery模式可以识别第三方rom包,因此可以用来刷机。而官方recovery一般不能识别第三方zip文件。Recovery刷机包是称为Google Update 格式;在用Recovery恢复时,刷机包通常放在SD卡里,所以这里刷机一般称为卡刷。

刷入RE:

  1. 首先进入 bootloader(fastboot)模式
  2. fastboot flash recovery recovery.img

4、/data 用户数据区

此分区包含了用户的数据信息,如:联系人、短信、设置、用户安装的程序的,擦除此分区,相当于手机恢复出厂设置,可以在Recovery模式中选择“data/factory reset ”擦除此分区,此分区下的一些常见目录:

  • /data/data/[packagename]/files 文件缓存目录——一般存小的文件缓存,如果是图片,不建议放这里,一般放到外置卡。
  • /data/data/[packagename]/cache目录——存放一些其他缓存。
  • /data/data/[packagename]/databases——存放数据库。
  • /data/data/[packagename]/lib——应用的so目录。
  • /data/data/[packagename]/shared_prefs—— 应用的SharedPreferences保存。

它是所有用户数据存放的地方。主要为了实现数据隔离,即系统升级和恢复的时候会擦除整个 /system 分区,但是却不会影响 /data 的用户数据。而恢复出厂设置,只会擦除 /data 的数据。

5、/cache 数据缓存区

此分区是安卓系统缓存区,他保存系统最常访问的数据和应用程序。擦除这个分区,不会影响个人数据,只是删除了这个分区中已经保存的缓存内容,缓存内容会在后续手机使用过程中重新自动生成。

6、/misc

此分区包含了一些系统设置和系统功能启用禁用的相关设置。这些设置包括CID(运营商或区域识别码)、USB设置和一些硬件设置等等。这是一个很重要的分区,如果此分区损坏或者部分数据丢失,手机的一些特定功能可能不能正常工作。

7、/sdcard 内部存储分区

这个分区不是设备系统存储空间,是SD卡空间。从使用上讲,这个是你自己的存储空间,可以随便你任意存放相片、视频、文档、ROM安装包等。擦除这个分区是完全安全的,只要你把分区中你需要的数据都备份到了你的电脑中。虽然一些用户安装的程序会使用这个分区保存它的数据和设置信息,擦除了这个分区,这些程序的数据,比如有些游戏的存档,就会全部丢失。在既有内部SD卡和外部SD卡的设备中,比如三星Galaxy S和一些平板电脑,/sdcard分区通常指向内部SD卡。外部SD卡,如果存在的话,会对应一个新的分区,每个设备都不一样。

/storage/emulated/0/Android/data/[packagename]/cache 外置缓存
/storage/emulated/0/Android/data/[packagename]/files 外置文件缓存

/storage/emulated/0/Android/data/[packagename] 在android2.2之后,在应用卸载后也会一并卸载

8、/sd-ext SD卡扩展分区。

SD卡扩展分区,即我们通常所说的外部存储区,这不是安卓系统的标准分区,但在第三方ROM届却很流行。它根本上是你SD卡上一个额外的分区,从外部功能表现上,与 /data分区的功能相同。一些第三方ROM,有一些特殊的功能叫做APP2SD+或者data2ext。这个功能在设备的内部存储空间比较小(也就是分配给/data分区的空间比较小)时非常有用。因此,需要安装更多程序,但内部空间又不够的用户,可以使用支持这个功能的第三方ROM,来获取额外的空间安装更多的应用程序。 擦除这个分区和擦除 /data分区的结果相同,你将会丢失联系人,短信、安装应用程序和设置。

vendor 分区——它是存放厂商特殊系统修改的地方。特别是在 Android 8.0 以后,隆重推出了“Treble”项目。厂商 OTA 时可以只更新自己的 /vendor 分区即可,让厂商能够以更低的成本,更轻松、更快速地将设备更新到新版 Android 系统。

二、BootLoader 和FastBoot

1、BootLoader

BootLoader本质上是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备建立内存空间映射图,从而将系统的软硬件环境初始化到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,通常并没有像BIOS那样的固件程序(虽然有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。比如在一个基于ARM7TDMI core的嵌入式系统中,系统在上电或复位时通常都从地址0x00000000处开始执行,而在这个地址处安排的通常就是系统的BootLoader程序。

2、 FastBoot

fastboot,顾名思义即『快速启动』。Fastboot 可以说是一个通信协议,电脑可以通过这个通信协议,直接向手机系统不同分区中写入文件(.img 文件)。fastboot 主要是用来与bootloader的USB通讯的PC命令行工具。他一般主要也用来向bootloader传送刷机文件进行文件分区重烧。 因此在使用时,必须有一个PC机并且USB线要始终联着,即线刷。 (用fastboot需要bootloader 支持,不是每一家公司产品都支的这个功能的),通常fastboot指的是安卓设备的Fastboot模式(一种比recovery更底层的刷机模式)。另外安卓手机还有一种刷机方式,叫做“卡刷”,两者区别如下。

  • 线刷—— 直接向手机硬盘写入*.img 文件,但是必须借助电脑(Win10 电脑有坑,有些设备驱动不识别)和数据线。
  • 卡刷——就是利用Recovery的从SD卡中更新系统的这个功能,如果你想刷第三方Rom,必须刷入个第三方recovery,只有fastboot模式才能刷recovery.img。前提是必须要把想要更新的ROM(Android系统)拷贝到SD卡上,但如果手机已经是砖了,那只能用线刷了。

进入fastboot(bootloader)模式:

  • 大多数安卓手机,都可以在关机状态下,然后同时按住【电源键】+【音量+】键,大约2-3s后,就可以进入Fastboot模式。
  • 作为开发者在开机状态下可以用下面的方式进入:
adb shell reboot bootloader
//刷机
fastboot flash system xxxx\system.img

然后就可以执行下面的fastboot命令:

fastboot flashing unlock    #6.0以上设备 设备必须解锁,开始刷机(这个不同的手机厂商不同)
  fastboot erase {partition}  # 擦除分区
  fastboot  erase  frp    # 擦除 frp 分区,frp 即 Factory Reset Protection,用于防止用户信息在手机丢失后外泄
  fastboot  flash  boot  boot.img    # 刷入 boot 分区
  fastboot  flash  system  system.img    # 刷入 system 分区
  fastboot  flash  recovery  recovery.img    # 刷入 recovery 分区
  fastboot flashall    #烧写所有分区,注意:此命令会在当前目录中查找所有img文件,将这些img文件烧写到所有对应的分区中,并重新启动手机。
  fastboot  format  data    # 格式化 data 分区
  fastboot  flashing lock    # 设备上锁,刷机完毕
  fastboot  continue    # 自动重启设备
  fastboot reboot# 重启手机
  fastboot reboot-bootloader# 重启到bootloader 刷机用
  fastboot devices  ## 发现手机,显示当前哪些手机通过fastboot连接了

三、lunch 命令分析及user和userdebug编译选项含义

在编译Android系统时,可以通过lunch命令来选择,要编译的版本,不同的选项代表着编译打包时的项目构成不一样。AOSP 原始的整编包含三大步骤。

1、source build/envsetup.sh 加载命令及编译环境

envsetup.sh定义了编译相关的命令(函数),执行该脚本可以向编译系统添加编译指令,可以理解为编译环境的预初始化。

source 命令——使当前shell读入路径为filepath的shell文件并依次执行文件中的所有语句,通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录,必须要在build 的上一级目录下 执行:

~/mojf/aibox$ source build/envsetup.sh

envsetup.sh 定义的一些function,不同定制厂商的有所不同,以下是标准的一些函数,篇幅问题实现略。

function help()                  # 显示帮助信息
function get_abs_build_var()           # 获取绝对变量
function get_build_var()             # 获取绝对变量
function check_product()             # 检查product
function check_variant()             # 检查变量
function setpaths()                # 设置文件路径
function printconfig()              # 打印配置
function set_stuff_for_environment()        # 设置环境变量
function set_sequence_number()            # 设置序号
function settitle()                # 设置标题
function choosetype()               # 设置type
function chooseproduct()              # 设置product
function choosevariant()              # 设置variant
function tapas()                  # 功能同choosecombo
function choosecombo()               # 设置编译参数
function add_lunch_combo()             # 添加lunch编译选项,
function print_lunch_menu()            # 打印lunch列表
function lunch()                 # 配置lunch
function m()                   # make from top
function findmakefile()              # 查找makefile
function mm()                   # make from current directory
function mmm()                   # make the supplied directories
function croot()                 # 回到根目录
function cproj()
function pid()
function systemstack()
function gdbclient()
function jgrep()                 # 查找java文件
function cgrep()                  # 查找c/cpp文件
function resgrep()
function tracedmdump()

在执行source build/envsetup.sh时,会设置默认的一些编译选项:

add_lunch_combo aosp_arm-eng
add_lunch_combo aosp_arm64-eng
add_lunch_combo aosp_mips-eng
add_lunch_combo aosp_mips64-eng
add_lunch_combo aosp_x86-eng
add_lunch_combo aosp_x86_64-eng
  • eng——debug 版本
  • user——release 版本
  • userDebug——部分debug版本

以上编译选项eng、user和userdebug的含义是由LOCAL_MODULE_TAGS这一Android.mk文件里的配置项来决定的。

envsetup.sh作用大致上有以下几点:

  • 加载了编译时使用到的函数命令,如:help,lunch,m,mm,mmm等
  • 添加了默认编译选项:aosp_arm-eng等系统默认选项
  • 查找build/<-厂商目录>/和build/<厂商目录>/build/目录下的vendorsetup.sh,如果存在的话,加载执行它,添加厂商自己定义产品的编译选项

2、lunch 选择平台等编译选项

function lunch()
{
    local answer

    if [ "$1" ] ; then
        answer=$1
    else
        print_lunch_menu
        echo -n "Which would you like? [aosp_arm-eng] "
        read answer
    fi

    local selection=

    if [ -z "$answer" ]
    then
        selection=aosp_arm-eng
         # 用户输入0-9或者某个编译选项
    elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
    then
        if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]
        then
            selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}
        fi
    elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$")
    then
        selection=$answer
    fi

    if [ -z "$selection" ]
    then
        echo
        echo "Invalid lunch combo: $answer"
        return 1
    fi

    export TARGET_BUILD_APPS=

    local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")
    check_variant $variant
    if [ $? -ne 0 ]
    then
        echo
        echo "** Invalid variant: '$variant'"
        echo "** Must be one of ${VARIANT_CHOICES[@]}"
        variant=
    fi

    local product=$(echo -n $selection | sed -e "s/-.*$//")
    TARGET_PRODUCT=$product \
    TARGET_BUILD_VARIANT=$variant \
    build_build_var_cache
    if [ $? -ne 0 ]
    then
        echo
        echo "** Don't have a product spec for: '$product'"
        echo "** Do you have the right repo manifest?"
        product=
    fi

    if [ -z "$product" -o -z "$variant" ]
    then
        echo
        return 1
    fi
	# 从编译选项获得PRODUCT和VARIANT,修改全局环境变量
    export TARGET_PRODUCT=$product
    export TARGET_BUILD_VARIANT=$variant
    export TARGET_BUILD_TYPE=release

    echo
	#设置其他环境变量
    set_stuff_for_environment
    printconfig
    destroy_build_var_cache
}

由此可以知道lunch 本质上是为了修改全局环境变量,主要有:

TARGET_PRODUCT=rk3399
TARGET_BUILD_VARIANT=userdebug
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=...

其中TARGET_BUILD_VARIANT是通过lunch 选择的版本,可以区分编译选项时user还是userdebug,将在/编译脚本xxx.mk文件中中设置一个prop,

ifeq ($(TARGET_BUILD_VARIANT),user)
PRODUCT_DEFAULT_PROPERTY_OVERRIDES+= \
    ro.adb.secure=1
endif

来限制adb这个daemon进程获取root权限,adb的deamon进程通过xxx.mk中设置的属性prop来确定是否可以给adb root权限(默认情况下user版adb将无法获取root权限而受到限制)对应的代码实现在/system/core/adb/daemon/main.cpp

//查看prop 属性值
PS D:\CodeBank\ForWork\AIVoiceServer\app\release> adb shell
Hi3751V811:/ # getprop | grep DhA
[init.svc.AIAudioNativeService]: [running]
[xmo.propchange.name]: [persist.xmo.AIAudio.algorithm.auth]
[persist.xmo.AIAudio.algorithm]: [in]
[persist.xmo.AIAudio.algorithm.auth]: [off]
[persist.xmo.AIAudio.in]: [AIAudio]
[persist.xmo.AIAudio.out]: [AIAudio]
[persist.xmo.AIAudio.route.in.ops]: [0]
[persist.xmo.AIAudio.route.out.spdif]: [0]
[persist.xmo.AIAudio.usb]: [1]
[ro.boottime.AIAudioNativeService]: [6648069836]
Hi3751V811:/ # EXIT

adbd 函数:

int adbd_main(int server_port) {
    umask(0);
    signal(SIGPIPE, SIG_IGN);
    init_transport_registration();
    // We need to call this even if auth isn't enabled because the file
    // descriptor will always be open.
    adbd_cloexec_auth_socket();
    if (ALLOW_ADBD_NO_AUTH && property_get_bool("ro.adb.secure", 0) == 0) {
        auth_required = false;
    }
    adbd_auth_init();
    // Our external storage path may be different than apps, since
    // we aren't able to bind mount after dropping root.
    const char* adb_external_storage = getenv("ADB_EXTERNAL_STORAGE");
    if (adb_external_storage != nullptr) {
        setenv("EXTERNAL_STORAGE", adb_external_storage, 1);
    } else {
        D("Warning: ADB_EXTERNAL_STORAGE is not set.  Leaving EXTERNAL_STORAGE"
          " unchanged.\n");
    }
    drop_privileges(server_port);
    bool is_usb = false;
    if (access(USB_ADB_PATH, F_OK) == 0 || access(USB_FFS_ADB_EP0, F_OK) == 0) {
        // Listen on USB.
        usb_init();
        is_usb = true;
    }
    // If one of these properties is set, also listen on that port.
    // If one of the properties isn't set and we couldn't listen on usb, listen
    // on the default port.
    char prop_port[PROPERTY_VALUE_MAX];
    property_get("service.adb.tcp.port", prop_port, "");
    if (prop_port[0] == '\0') {
        property_get("persist.adb.tcp.port", prop_port, "");
    }

    int port;
    if (sscanf(prop_port, "%d", &port) == 1 && port > 0) {
        D("using port=%d", port);
        // Listen on TCP port specified by service.adb.tcp.port property.
        local_init(port);
    } else if (!is_usb) {
        // Listen on default port.
        local_init(DEFAULT_ADB_LOCAL_TRANSPORT_PORT);
    }

    D("adbd_main(): pre init_jdwp()");
    init_jdwp();
    D("adbd_main(): post init_jdwp()");

    D("Event loop starting");
    fdevent_loop();
    return 0;
}

3、make 编译

定制厂商通常是通过Linux .sh脚本结合Python来实现的,未完待续…