在使用Linux时,通常会用到df命令来查看磁盘挂载情况,那么df的输出结果可信吗?一般用户似乎不应怀疑,作者以前也从没怀疑过,直到发生了下面的这些事情。
df报告的分区大小比分区表定义的大时
在使用某芯片厂商的SDK开发产品时,检查设备上的eMMC使用情况看到有下面的情况:
1 | ~# df -Th -x tmpfs -x devtmpfs |
一个名为bluetooth的分区竟然分配了1G的空间,那么,这里面究竟包含了什么呢?使用ls -a
命令查看/bluetooth
目录没有任何内容。回看厂商烧录工具使用的配置文件时,发现有这样的配置:
<program SECTOR_SIZE_IN_BYTES="512" file_sector_offset="0" filename="BTFM.bin" label="bluetooth" num_partition_sectors="2047" partofsingleimage="false" physical_partition_number="0" readbackverify="false" size_in_KB="1023.5" sparse="false" start_byte_hex="0xd8468000" start_sector="7086912" />
从描述上看这是个存放Bluetooth Firmware文件的分区,但定义的分区大小只有1023.5K,占用2047个扇区,这显然与df的输出不匹配,那这个分区的信息被Linux Kernel识别为怎样的?为此,做了下面的一系列试验:
1 | ~# cat /sys/class/block/mmcblk0p36/size # 查看mmcblk0p36的扇区数 |
从上面的测试中可以看到,cat命令读到的起始扇区能与配置文件中对应,但读到的扇区数多了1个扇区,猜测是扇区对齐导致。接下来通过复制大于分区表大小的文件进行试验,cp命令的输出表明复制成功,将原文件与目标文件比较,diff命令的输出表明文件内容一致,考虑到Linux的缓冲机制,文件并不一定写入了eMMC,于是又执行了一遍sync命令后再次比较,diff的输出仍旧表明文件内容一致,但给sync命令指定目标文件时,sync给出了一条错误消息,这表明不能把文件刷写到eMMC。继续比较,diff的输出仍旧表明文件内容一致,可见,文件始终是保留在缓冲区里,并没有写入到eMMC。为验证这一观点,将设备重启后再度比较,这次diff报了两个文件是不同的。
看来,mmcblk0p36分区确实只有2048个扇区,无法写入这么大的文件,那么,为什么df命令报出来是1G的,想来只有配置文件中的BTFM.bin导致的了,在Ubuntu上使用file命令查看BTFM.bin:
1 | $ file BTFM.bin |
file命令报告BTFM.bin的总扇区数有2097152个,以512字节为1扇区,2097152个扇区数恰巧是1G的空间,到此基本上可以确定是这个BTFM.bin导致的问题,当它写入设备的eMMC后,Linux从相应的分区读取了这些数据作为文件系统信息,并据此挂载,于是df等使用了/proc/self/mountinfo数据的程序都认为此分区有1G空间,但在实际写入时,Kernel却知道没有足够的扇区来存放数据,于是报了错误。
修复此问题也很简单,只需要提供一个大小匹配实际的BTFM.bin就可以了,以Kernel报的2048个扇区为准,在Ubuntu上重新生成一个用于烧录的vfat.bin文件的过程如下:
1 | $ dd if=/dev/zero of=vfat.bin bs=512 count=2048 # 产生一个2048个扇区大小的文件 |
为什么BTFM.bin是16位的FAT表,而vfat.bin是12位的FAT表,因为2048个扇区的空间太小,已经不足以使用16位的FAT表。将新生成的vfat.bin替换为原BTFM.bin并刷写到设备验证,可以看到df命令将输出除去文件系统自身信息后的正确大小,cp命令复制超出分区表空间大小的文件到分区时也将报错:
1 | ~# df -Th -x tmpfs -x devtmpfs |
df报告的分区大小比分区表定义的小时
在上面,已经看到了df报告的分区大小比分区表定义的大时是怎样的情况,当df报告的分区大小比分区表中小时,又将如何?正巧的是,/分区就是这样的一种情况,查看烧录工具的配置文件可以看到这样的配置:
<program SECTOR_SIZE_IN_BYTES="512" file_sector_offset="0" filename="rootfs.ext4" label="system" num_partition_sectors="6291455" partofsingleimage="false" physical_partition_number="0" readbackverify="false" size_in_KB="3145727.5" sparse="false" start_byte_hex="0xcf3a000" start_sector="424400" />
配置文件里定义的大小是6291455个扇区,3145727.5KB,也就是2.99G,查看sys文件系统中的信息验证:
1 | # mount | grep "/ " |
同样是比配置文件中定义的多了1个扇区,但df命令报告的总大小是1.9G,用掉了622M,若以2.99G为限,剩余应有(2.99G-622M)大小,那此时能复制超过1.9G的文件进去吗?试验一下就知道了:
1 | ~# ls -lh /media/usb1/rootfs.ext4 |
cp命令报告没有足够的空间来复制,有1G多的空间丢失了。
要修复此问题,仍旧是生成正确大小的文件系统镜像,对于ext4文件系统镜像来讲,可以用Android SDK中的make_ext4fs命令来生成的,此例中,如果要利用上分区表定义的大小,在Ubuntu上这样操作就可以了:
1 | $ mkdir rootfs |
上述命令使用原文件系统镜像再生成rootfs-raw.ext4文件,并指定对应到6291456个扇区的大小,产生的rootfs-raw.ext4会有3G大,若烧写工具支持Android Sparse格式,则可以使用sudo make_ext4fs -s -l 3145728K rootfs-sparse.ext4 rootfs
这样的命令产生一个较小的文件。将这个文件刷回设备上验证,可以看到df命令将输出除去文件系统自身信息后的正确大小,cp命令复制2.1G大文件到/分区时正确完成:
1 | ~# df -Th -x tmpfs -x devtmpfs |
总结
-
df、cp等命令对于产品开发者来说并不是那么可靠的,开发者正是要通过设计正确的系统来保证这些命令能正常工作
-
不带参数的sync命令是不可靠的,若要确认某个文件写入到存储设备上,最好带上具体的路径。在《UNIX环境高级编程》第三版的3.13节中,对sync类函数有这样的描述:sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。不带参数的sync命令调用这个函数。fsync函数只对由文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束才返回。fdatasync函数类似于fsync,但它只影响文件的数据部分。
-
若使用mkfs.XXX去格式化分区,这些命令都能正确识别分区大小,并格式化为恰当的文件系统
参考
- 《UNIX环境高级编程》第三版