bmap简介

本节介绍一些块映射(bmap)的基本信息,帮助读者理解bmaptool的工作机制。 本节的结构如下:

  • “稀疏文件” - bmap的想法是基于稀疏文件的,所以理解稀疏文件对理解bmap很重要。
  • “块映射” - 解释什么是bmap。
  • “Raw 镜像” - 该节主要介绍bmaptool的主要使用场景:烧写Raw镜像。
  • “使用场景” - 介绍一些bmap和bmaptool可能的使用场景。

稀疏文件

文件系统中重要的角色,一般来讲,就是把文件的数据映射到磁盘扇区。 不同的文件系统有不同的映射,文件系统的效率很大程度上依赖与该文件系统的映射方法。 文件系统的块大小通常是4KB,但是也可能是8KB或者更大。
 
显然,为了实现映射,文件系统需要维护一些磁盘上的索引。 对于文件系统中的任何文件和文件内的偏移,索引容许你访问匹配的存储文件数据的磁盘扇区。 当我们写一个文件时,文件系统找到索引,写到匹配的磁盘扇区中。 有时文件系统需要分配新的磁盘扇区,并更新索引(比如文件中添加了新数据)。 文件系统的索引通常被称作“文件系统的元数据”。
 
如果一个文件没有被映射到任何磁盘扇区,会怎么样呢?可能吗?答案是肯定的。 这是可能的,这些没有被映射的区域被称作“空穴”。 这些含有空穴的文件通常称作“稀疏文件”。
 
所有主流的文件系统,如Linux的ext[234], btrfs, XFS, or Solaris XFS, 甚至Windows' NTFS, 都支持稀疏文件。 一些过时的和非主流的文件系统,比如FAT,是不支持空穴的。
 
读空穴会返回0。 向一个空穴写,会引起文件系统分配磁盘扇区作为匹配的块。 这里介绍如何创建一个没有块映射的4GB文件,也就是说该文件是由一个巨大的4GB大小的空洞。
 $ truncate -s4G image.raw
 $ stat image.raw
   File: image.raw
   Size: 4294967296   Blocks: 0     IO Block: 4096   regular file
注意“image.raw”是一个4GB的文件,但是没有占用磁盘上的一个块。所以,整个文件的内容没有被映射。 读这个文件的结果是读到4GB的0。 如果你往image.raw的中间写入内容,将会以2个空穴结尾,并映射了中间区域。
 
因此:
  • 稀疏文件是含有空穴的文件。
  • 稀疏文件可以节省磁盘空间,因为,简单的说,空穴不占用磁盘空间。
  • 空穴是文件中没有被映射的区域,也就是说没有映射到磁盘上。
  • 读一个空穴会返回0。
  • 写数据到空穴,将会销毁该空穴,因为文件系统会映射相应的文件区域到磁盘扇区。
  • 文件系统通常会操作块,所以空穴的大小和偏移是跟块的边界对齐的。
处理稀疏文件时要格外小心。 很容易不小心展开了一个稀疏文件,这会导致映射所有的空穴到写满0的磁盘上。 例如,“scp”经常会展开稀疏文件,“tar”和“rsync”工具也会默认展开稀疏文件,除非你使用“--spares”参数。 压缩和解压缩一个稀疏文件通常会展开它。
 
Linux中有2个ioctl可以查找映射过和未映射的区域: "FIBMAP" 和"FIEMAP"。 前一个ioctl是很古老的,基本每个Linux系统都会支持,但是它需要root特权。 后面一个ioctl比较高级一点,不需要root特权,但是它相对来说比较新(Linux kernel 2.6.28中才加入)。
 
最近版本的Linux kernel(从3.1开始)也支持"SEEK_HOLE" 和 "SEEK_DATA"值作为标准系统调用"lseek()" 中的参数"whence"的值。 它们容许定位到文件的下一个空穴或者下一个已经映射过的区域。
 
现代内核中高级的Linux文件系统,也容许“冲孔”,即可以解除任何对齐区域的映射,把它变成一个空穴。 可以使用"fallocate()" 系统调用的"FALLOC_FL_PUNCH_HOLE" 和 "mode" 来实现。

块映射

bmap是一个XML文件,包含了一系列映射过的区域和一些该文件的额外的创建信息,
例如:
  • bmap文件本身的SHA256 校验
  • 映射过的区域的SHA256 校验
  • 原始文件的大小
  • 映射过的数据的总量

bmap文件被设计成便于机器阅读和人类阅读。 所有机器可读的信息都是由XML标签提供的。 人类可读的信息放在XML的注释中的,用于解释XML标签的意义,提供一些诸如映射过数据的比例或者MB和GB为单位的大小等信息。

所以,理解bmap文件最好的方法是去读它。 这里有一个Tizen IVI 2.0 alpha快照的bmap文件的例子。 大量的块range都被删掉了,这样看起来会比较短一些。

<?xml version="1.0" ?>
<!-- This file contains the block map for an image file, which is basically
     a list of useful (mapped) block numbers in the image file. In other words,
     it lists only those blocks which contain data (boot sector, partition
     table, file-system metadata, files, directories, extents, etc). These
     blocks have to be copied to the target device. The other blocks do not
     contain any useful data and do not have to be copied to the target
     device.

     The block map an optimization which allows to copy or flash the image to
     the image quicker than copying of flashing the entire image. This is
     because with bmap less data is copied: <MappedBlocksCount> blocks instead
     of <BlocksCount> blocks.

     Besides the machine-readable data, this file contains useful commentaries
     which contain human-readable information like image size, percentage of
     mapped data, etc.

     The 'version' attribute is the block map file format version in the
     'major.minor' format. The version major number is increased whenever an
     incompatible block map format change is made. The minor number changes
     in case of minor backward-compatible changes. -->

<bmap version="1.4">
    <!-- Image size in bytes: 3.7 GiB -->
    <ImageSize> 3998237696 </ImageSize>

    <!-- Size of a block in bytes -->
    <BlockSize> 4096 </BlockSize>

    <!-- Count of blocks in the image file -->
    <BlocksCount> 976133 </BlocksCount>

    <!-- Count of mapped blocks: 817.1 MiB or 21.4%  -->
    <MappedBlocksCount> 209168     </MappedBlocksCount>

    <!-- Type of checksum used in this file -->
    <ChecksumType> sha256 </ChecksumType>

    <!-- The checksum of this bmap file. When it is calculated, the value of
         the checksum has be zero (all ASCII "0" symbols).  -->
    <BmapFileChecksum> 46dd0b24ff90c05407455cc43f7eb65d219f7a8a23b9d980b041a0d97550e06d </BmapFileChecksum>

    <!-- The block map which consists of elements which may either be a
         range of blocks or a single block. The 'chksum' attribute
         (if present) is the checksum of this blocks range. -->
    <BlockMap>
        <Range chksum="ea66c5762cf7a63ecd98a5b972ed57d0ca319de9196613c29d92c16e82a9a603"> 0-4 </Range>
        <Range chksum="f50042546c89460967dfb71cbaa5aed14f18b7abe618eedc6be4c250aed5fb21"> 256-1919 </Range>
        <Range chksum="bf634e74431550f26129c00cf7f55d976adf0d71bcf97bf7f4772656521308f1"> 12544-13703 </Range>
        <Range chksum="5be706250b7a79b8bd58855b19ba91094d5b8fe9001fd62dc1294630a6819bd6"> 15616-16255 </Range>
        <Range chksum="919cfe81159e97899bd4a6dbda767a43efd8b395d39d63dbb9bf3dabc83b652a"> 16384-16511 </Range>
        <Range chksum="7c98d2da7afa2bdee9765910c81cb588e14781b77538fb54df84f7e7039e4348"> 16608-16634 </Range>
        <Range chksum="c7cbaf526b3ecc5b79ac8a993722a28995b7d0239ba210a3061b358a07a16894"> 16640-16881 </Range>
        <Range chksum="effdfd76ee7384e7b506985f8bd3b5d21cd1d1895c829fc10c3938013188b669"> 16883 </Range>
        <Range chksum="effdfd76ee7384e7b506985f8bd3b5d21cd1d1895c829fc10c3938013188b669"> 16885 </Range>
        <Range chksum="33fdf647973fde05156c38ba7dedd2b5bc874fa10e1313105fbf8aa9b50601dc"> 16889 </Range>
        <Range chksum="06308f48b3dd70980231896e3da0a3250c95a0fc7203e92c3d75f179df898dcd"> 16892-16893 </Range>
        <Range chksum="52a98324357718ec7554c38ccb7a1b5cafd38e0c012e72e40ae63020df7673db"> 16908-26665 </Range>
        <Range chksum="d15508719738ac16a38b1d328bed44b4966d11c6f25adb49ebce2dae22b882d5"> 49408-49409 </Range>
.....
        <Range chksum="30cb3c6546b0fcaf5d04947b20b68a2c46f606267ed54f093eeabd8382764df0"> 901376-901377 </Range>
        <Range chksum="8d1181e3d36e97d78876bb318e8fec2f3d1d4c50225687efeeea8e375d8d5815"> 976112-976132 </Range>
    </BlockMap>
</bmap>

Raw镜像

Raw 镜像是最简单的系统镜像,可以连续的烧写到目标板的块设备中,而不需要其他操作。 Raw镜像仅仅“反映”目标块设备:它们通常都是从MBR扇区开始。 镜像的开头有一个包含一个或者多个分区的文件系统的分区表,例如ext4。 通常,不要求专门的工具去烧写raw镜像到目标块设备中。 标准的“dd”命令可以做这件事:
$ dd if=tizen-ivi-image.raw of=/dev/usb_stick

乍一看,raw镜像没有什么吸引人的地方,因为它体积庞大,需要花很长时间去烧写。 然而,有了bmap,raw镜像变得更加吸引人了。 我们将会证明这一点,使用Tizen IVI作为例子。

Tizen IVI 项目的2.0 alpha中使用3.7GB大小的raw镜像。 该镜像是由MIC创建的。 这里有一个简单的介绍,如何使用MIC创建镜像:

  1. 创建一个3.7GB大小的稀疏文件,最后这个文件将会变成Tizen IVI镜像文件
  2. 使用分区工具对该文件分区
  3. 使用“mkfs.ext4”工具格式化分区
  4. 循环挂载所有分区
  5. 安装所有需要的包到分区中:拷贝所有需要的文件。
  6. 卸载所有循环挂载的镜像分区,镜像做好了
  7. 为镜像产生块映射文件
  8. 使用bzip2压缩镜像,压缩至300MB。

Tizen IVI raw镜像是最初的稀疏文件。 所有映射过的块代表有效数据,所有的空穴代表无效的区域,该区域全是0,在烧写镜像的时候不需要进行拷贝。 尽管镜像被压缩后空穴的信息会丢失,bmap文件依然包含,通过只拷贝映射过的区域bmap可以重构解压的镜像或者快速烧写镜像。

Raw镜像压缩效率非常高,因为空穴本质上都是0。 这就是为什么包含1.1GB映射过的块的3.7GB的Tizen IVI raw镜像可以压缩成300MB的原因。 重要的是只有在烧写的时候才需要解压缩。 bmaptool叫做“即时烧录”。

因此:

  1. raw镜像都是以压缩过的格式进行发布的,它们就像压缩包那样小(包含镜像的所有数据)。
  2. bmap文件和bmaptool让快速烧写压缩过的raw镜像到目标块设备成为可能。
  3. bmaptool可以重构原始的未压缩的稀疏raw镜像文件。

更重要的是,烧写镜像非常快速,因为是直接写入到块设备中,而且是连续写入。

Raw 镜像的另一个重要特点是它是可以直接运行的,也就是说只要放到设备上,就可以直接运行。 你不需要知道镜像的格式,包括分区和文件系统等。 这就简单了并且稳定了。

使用场景

烧写镜像

烧写或者拷贝大镜像是bmaptool的主要使用场景。 思想是如果你有一个镜像文件和它的bmap,你可以只烧写映射过的块,跳过没有映射的块。
 
有了bmap,就没有必要去尝试通过减小分区缩小Raw 镜像的体积了。 镜像可以包含目标设备要求的巨大的数十亿字节的分区。 这个镜像就是一个只包含一点映射数据的巨大的稀疏文件。 因为未映射区域都包含的是0,巨大的镜像可以有很高的压缩比,所以巨大的镜像可以变成一个很小的压缩文件。 然后可以以压缩格式进行发布,使用bmaptool和bmap文件进行快速烧写,因为bmaptool在烧写时会即时解压缩而且只烧写映射区域。
 
使用bmap进行烧写的另一个好处是校验验证。 “bmaptool create”命令会为所有映射过的块区域生成SHA256 校验码,“bmaptool copy”命令会在写入时验证这些校验码。 整个bmap文件也会被一个 SHA256 校验码保护,bmaptool在烧写前会验证它。
 
而且,bmap文件可以使用OpenPGP (gpg)进行签名,如果bmap文件提供签名,bmaptool会自动验证它的签名。 以下是验证bmap文件的完整过程。 因为bmap文件包含所有映射数据的SHA256 校验码,bmap文件签名验证需要保证镜像文件的完整和整合。

重构稀疏文件

一般来讲,如果你有一个稀疏文件,然后又把它展开了,是没有方法去重构它的。 有时候,例如
  $ cp --sparse=always expanded.file reconstructed.file

就可以了。 然而,这种方式得到的重构文件跟原始稀疏文件是不一样的。 原始稀疏文件可能包括全是0的映射过的块文件(不是空穴),在重构文件里,这些块会变成空穴。 某些情况下,这并不重要。 例如,如果你想节省磁盘空间。 但是,对于Raw镜像,烧写时会出问题,因为这里需要烧写所有的全是0的块,而不是略过。 事实上,如果你不写包含无效信息的零填充块到对应的磁盘扇区的,你在这些块会以无效信息进行结尾。 也就是说,当我们烧写raw镜像时,零填充块和空穴是有本质区别的,因为零填充块是需要的,但是希望全是0的块,而空穴是不需要的块,也就是说没有希望的相关内容。

Bmaptool可以帮助正确重构稀疏文件。 在稀疏文件展开之前,你需要为它产生bmap文件(例如,使用“bmaptool create”命令)。 然后你可以压缩你的文件,或者展开它。 然后,你可以使用“bmaptool copy”命令对它进行重构。