背景
分区作为磁盘划分区域的手段,通过起始地址跟size等信息保存至分区表,可以将磁盘分成若干的区域,用于存储不同的内容。
分区的使用不仅可以使系统文件条目清晰,兼容多个文件系统使其挂载在不同分区,且通过挂载权限的管理还可以提高系统的稳定性,这在其他系统上都是通用的概念。
本文通过个人对Linux中接触到的分区的理解跟使用,介绍一些常用概念、基本信息查询方法及image制作方法,仅供参考。
基本概念
1.分区表
包含起始地址及大小等基本信息,一般保存在存储器头部,通过对分区表的调整即可达到调整分区的目的。简单的理解就是将一整块存储区域划分成若干等分,操作系统通过查找每个分区的start address跟size来查找分区位置。
分区表一般通过刷机的方式写入存储区,调整分区表后要重新写入相应的image;常用的imx6平台,通过一个shell脚本来记录分区,通过mfgtool刷机更新分区表,此处节选一段mfgtool刷机脚本中更新分区的部分:
<!-- create partition -->
<CMD state="Updater" type="push" body="send" file="mksdcard-android.sh.tar">Sending partition shell</CMD>
<CMD state="Updater" type="push" body="$ tar xf $FILE "> Partitioning...</CMD>
<CMD state="Updater" type="push" body="$ sh mksdcard-android.sh /dev/mmcblk%mmc%"> Partitioning...</CMD>
send指令将分区表压缩包拷贝到内存,之后通过指令解压缩,最后通过sh执行脚本内容,脚本有且仅有一个参数,为本地存储设备/dev/mmcblk0,是一个32G emmc;完成分区后会进一步格式化各个分区,并刷入分区对应内容,不全贴了。
刷机完成后系统启动,通过指令cat /proc/partitions可查看存储器total size跟各个分区size。
平台不同,分区表呈现的方式也有所差异,高通平台分区表通过xml文件来管理,此处节选小部分仅供参考:
<partition label="aboot" size_in_kb="1024" type="400FFDCD-22E0-47E7-9A23-F16ED9382388" bootable="false" readonly="true" filename="emmc_appsboot.mbn"/>
<partition label="abootbak" size_in_kb="1024" type="EBD0A0A2-B9E5-4433-87C0-68B6B72699C7" bootable="false" readonly="true" filename="emmc_appsboot.mbn"/>
<partition label="boot" size_in_kb="65536" type="20117F86-E985-4357-B9EE-374BC1D8487D" bootable="false" readonly="true" filename="boot.img"/>
<partition label="recovery" size_in_kb="65536" type="9D72D4E4-9958-42DA-AC26-BEA7A90B0434" bootable="false" readonly="true" filename="recovery.img"/>
<partition label="devinfo" size_in_kb="1024" type="1B81E7E6-F50D-419B-A739-2AEEF8DA3335" bootable="false" readonly="true" filename="" sparse="true"/>
<partition label="system" size_in_kb="2097152" type="97D7B011-54DA-4835-B3C4-917AD6E73D74" bootable="false" readonly="true" filename="system.img" sparse="true"/>
<partition label="vendor" size_in_kb="1048576" type="97D7B011-54DA-4835-B3C4-917AD6E73D74" bootable="false" readonly="true" filename="vendor.img" sparse="true"/>
这属于高通平台独有的分区表,参数的含义一目了然,他们的顺序也决定了分区的index值,这是一个类似从机器语言到C语言的过程,目的是为了使用者配置分区更加的方便;所以你们可以猜到,完成xml的编辑仅仅是第一步,高通有配套的python脚本用来解析这个xml,通过脚本的解析转化,分区表最终以binary的形式呈现,通过QFIL刷机更新分区表,待系统启动后用同样的方式查看分区调整是否成功。
相比imx平台,高通的分区显得更加高级,不仅可以支持更多的参数,这个表同时也标出了每个分区对应的image文件、只读权限、是否为sparse格式等,所以这个表不仅包含分区,同时也是一个刷机指南。
2.Linux分区接口
分区表写入机器,系统启动后会根据分区信息枚举出块设备节点,因为这部分driver都是系统系统写好的,并没有相关功能的调试经验,所以具体的枚举过程本人并不清楚,枚举出的设备节点在/dev/block/下,高通平台的分区还有自己的label,可通过by name文件夹下查看分区对应的label,他们通过软连接的方式关联到一起,此处节选一部分仅供参考。
lrwxrwxrwx 1 root root 20 1970-01-01 08:00 sbl1 -> /dev/block/mmcblk0p4
lrwxrwxrwx 1 root root 20 1970-01-01 08:00 sbl1bak -> /dev/block/mmcblk0p5
lrwxrwxrwx 1 root root 21 1970-01-01 08:00 sec -> /dev/block/mmcblk0p18
lrwxrwxrwx 1 root root 21 1970-01-01 08:00 splash -> /dev/block/mmcblk0p26
lrwxrwxrwx 1 root root 20 1970-01-01 08:00 ssd -> /dev/block/mmcblk0p3
lrwxrwxrwx 1 root root 21 1970-01-01 08:00 syscfg -> /dev/block/mmcblk0p35
lrwxrwxrwx 1 root root 21 1970-01-01 08:00 system -> /dev/block/mmcblk0p24
lrwxrwxrwx 1 root root 21 1970-01-01 08:00 tbox -> /dev/block/mmcblk0p50
lrwxrwxrwx 1 root root 20 1970-01-01 08:00 tz -> /dev/block/mmcblk0p8
lrwxrwxrwx 1 root root 20 1970-01-01 08:00 tzbak -> /dev/block/mmcblk0p9
lrwxrwxrwx 1 root root 21 1970-01-01 08:00 userdata -> /dev/block/mmcblk0p53
lrwxrwxrwx 1 root root 21 1970-01-01 08:00 vendor -> /dev/block/mmcblk0p25
root: /# /dev/block/platform/soc/7824900.sdhci/by-name #
有了设备节点,应用程序就有办法通过接口访问相应分区里的内容了。
我们同样可以通过到/sys/class/block/下面查看不同分区的相应信息。
3.image制作
完成了分区工作,仅仅是完成了存储器区域划分,就像是画好了车位还要有车停进来他才有价值,所以这里要介绍一下分区写入的内容。
分区内容基本可以分为两类,一类是没有文件系统的image,另一类是有文件系统的image;是否有文件系统决定了该分区是否需要挂载,分别介绍一下这两种;
a)没有文件系统的image,可以理解这个是一个巨大的binary,里面保存了数据跟代码,没有文件系统决定了它并不需要挂载。
所以例如我们用到的bootloader/boot image/recovery image,还有相当一部分固件都是没有文件系统的,不过这里面boot跟recovery相对特殊,二者都是kernel+dtb+ramdisk的形式,ramdisk本身是有文件系统的,通过mount指令可以看到,它就是我们常说的rootfs根文件系统,通过拷贝到内存的方式再对其进行挂载,所以叫ramdisk,感兴趣可查相关资料进一步了解,这里不过多展开。而kernel dtb都是纯粹的机器代码,使用方式就是将其拷贝到内存中,等待bootloader引导,等到执行kernel的第一行指令,系统就交接到kernel手中了;
除此之外一些固件或是raw data,里面保存的不是代码而是数据,使用方法就是直接读取该分区的内容,读到的当然都是冷冰冰的十六进制;
b)有文件系统的image,相比与没有文件系统的image更加有温度,相信做应用程序的开发一定非常熟悉,我们常用的system/vendor/data等分区都是ext4的文件系统,有相当多的应用程序都放在system跟vendor下面;
我们可以通过指令cat /proc/filesystems查看系统支持哪些文件系统;常用的文件系统包括ext/vfat/fuseblk等,也包括sys/proc/debugfs/configfs等虚拟文件系统,其内部文件并不保存在存储器上,都是系统启动后动态生成的,用来表示系统参数跟状态等,一切接文件嘛。
在没有接触Android之前,有文件系统的image都是通过一些基础的Linux指令制作,或者将他们写入shell脚本一键执行;Android则是把这个过程集成到了build/编译脚本里,这部分不在此处展开了;
root: /# dd if=/dev/zero of=spongebob.img bs=1M count=1024
root: /# mkfs.ext4 spongebob.img
root: /# mkdir spongebob
root: /# mount -o rw -t ext4 spongebob.img spongebob
root: /# cp source_file spongebob/; sync
root: /# umount spongebob/
通过file指令查看文件系统:
root: /# file boot.img emmc_appsboot.mbn spongebob_raw.img system.img nvram.img
boot.img: Android bootimg, kernel, ramdisk, page size: 2048, cmdline (console=ttyHSL0,115200,n8 androidboot.console=ttyHSL0 )
emmc_appsboot.mbn: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, stripped
spongebob.img: Linux rev 1.0 ext4 filesystem data, UUID=57f8f4bc-abf4-655f-bf67-946fc0f9f25b (extents) (large files)
system.img: Android sparse image, version: 1.0, Total of 524288 4096-byte output blocks in 2601 input chunks.
nvram.img: data
4.挂载及使用
image制作完成后,可通过dd指令写入对应分区;
root: /# dd if=/mnt/media/xxx/spongebob.img of=/dev/block/mmcblk0p54
没有文件系统的话,应用层可通过设备节点直接读写分区内容;
有文件系统的分区可通过mount指令将该分区挂载到指定目录下,这样就可以到挂载点路径下直接访问该分区内容;挂载过程在Android里一般保存在init.rc中,通过init进程对其解析,执行各个分区的挂载动作,挂载时就可以指定权限,来限制应用程序的访问,提高系统稳定性。
当然我们的测试分区也可以通过命令手动挂载,在通过mount或df指令查看分区挂载情况;
root: /# mkdir -p /data/tmpdir/
root: /# mount -o ro -t ext4 /dev/block/mmcblk0p54 /data/tmpdir/
5.分区空间利用充分
这里要介绍一个遇到过的问题,分区size仅仅代表预留的空间大小,但真正用了多少是image size决定的,image的大小决定了分区占用多少,剩余的部分就浪费了。
所以这里涉及到一个充分利用的问题,一般我们可以将image size跟分区做的一样大,刷进去也不会有问题,这样就达到了分区空间充分利用的目的;但这样也会带来了一个问题,常用的user data分区少则几G大则十几G几十G,这样做出来的userdata image超级大,无限增加了升级包的size,所以Android通过sparse的方式对image size进行优化,极大减小了image size,解决了升级包过大的问题。
6.Android O系统自定义ext4 sparse image
高通平台详见/device/qcom/common/generate_extra_images.mk,可参考下面节选:
#####################################################################################################
# support for spongebob image
ifneq ($(strip $(BOARD_SPONGEBOBIMAGE_PARTITION_SIZE)),)
TARGET_OUT_SPONGEBOB := $(PRODUCT_OUT)/spongebob
INSTALLED_SPONGEBOBIMAGE_TARGET := $(PRODUCT_OUT)/spongebob.img
define build-spongebobimage
@echo "target spongebob image"
$(hide) mkdir -p $(1)
$(hide) $(MKEXTUSERIMG) -s $(TARGET_OUT_DATA) $(2) ext4 spongebob $(BOARD_SPONGEBOBIMAGE_PARTITION_SIZE)
$(hide) chmod a+r $@
$(hide) $(call assert-max-image-size,$@,$(BOARD_SPONGEBOBIMAGE_PARTITION_SIZE),yaffs)
endef
$(INSTALLED_SPONGEBOBIMAGE_TARGET): $(MKEXTUSERIMG) $(MAKE_EXT4FS)
$(hide) $(call build-spongebobimage,$(TARGET_OUT_SPONGEBOB),$(INSTALLED_SPONGEBOBIMAGE_TARGET))
ALL_DEFAULT_INSTALLED_MODULES += $(INSTALLED_SPONGEBOBIMAGE_TARGET)
ALL_MODULES.$(LOCAL_MODULE).INSTALLED += $(INSTALLED_SPONGEBOBIMAGE_TARGET)
endif
###################################################################################################
.PHONY: spongebobimage
spongebobimage: $(INSTALLED_SPONGEBOBIMAGE_TARGET)
变量BOARD_SPONGEBOBIMAGE_PARTITION_SIZE为image size,可在其他mk中配置;导入环境变量后通过指令make spongebobimage即可制作该image。
总结
本文提到的内容基本都是一些概念性的内容,包括一些简单指令查看系统分区信息,以及如何制作ext4 image;
但值得研究的内容是内核利用分区表创建块设备,我们知道emmc有约定好的协议,外设协议的部分被固化在emmc内部,所以并不需要芯片厂商提供额外的driver加载到Linux系统中,driver的部分将全部由Linux源码实现;包括Android编译脚本如何生成ext4的image也是个复杂有趣的过程,有机会一定进一步研究探讨。