磁盘碎片其实有两种:文件碎了,和空闲空间碎了。使用 FIEMAP 命令可以获取到文件在磁盘(的逻辑地址上)的分布情况。也是 filefrag -v 命令输出的东西。比如我的 pacman.log 就很碎:
Filesystem type is: 58465342 File size of /var/log/pacman.log is 11052443 (2699 blocks of 4096 bytes) ext: logical_offset: physical_offset: length: expected: flags: 0: 0.. 2015: 170210423.. 170212438: 2016: 1: 2016.. 2017: 170567879.. 170567880: 2: 170212439: 2: 2018.. 2027: 170569969.. 170569978: 10: 170567881: 3: 2028.. 2030: 170574582.. 170574584: 3: 170569979: 4: 2031.. 2031: 170574631.. 170574631: 1: 170574585: 5: 2032.. 2033: 170592662.. 170592663: 2: 170574632: .... 123: 2683.. 2687: 56903805.. 56903809: 5: 56906403: 124: 2688.. 2698: 56903011.. 56903021: 11: 56903810: last,eof /var/log/pacman.log: 125 extents found
整理的办法也很简单,复制一下,基本上就好了。只要剩余空间足够,小文件会变成一整块,大文件也是少数几块。如果非要弄一整块大的,比如我存放 pacman 数据库的那个小文件系统,可以用 fallocate -l 200M pacman.fs2
这样子的命令分配空间,然后把数据 dd 进去(cp 不行,因为它会先截断文件再写入,之前分配的空间就释放掉了)。
介绍完毕,重点来了:怎么找到那些被写得很碎很碎的文件呢?
对每个文件调用 filefrag 肯定太慢了,所以我写了个库和工具 fiemap-rs 直接调用 FIEMAP。它提供两个工具。一个是 fraghist,统计碎片数量分布直方图,用来了解一下某群文件有多碎。另一个是 fragmorethan,用来寻找碎到一定程度的文件。运行起来是这样子的:
/var/log: # Number of samples = 712 # Min = 1 # Max = 297 # # Mean = 11.338483146067423 # Standard deviation = 40.138129228003045 # Variance = 1611.0694179238724 # # Each ∎ is a count of 13 # 1 .. 31 [ 658 ]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ 31 .. 61 [ 11 ]: 61 .. 91 [ 9 ]: 91 .. 121 [ 10 ]: 121 .. 151 [ 6 ]: 151 .. 181 [ 5 ]: 181 .. 211 [ 3 ]: 211 .. 241 [ 2 ]: 241 .. 271 [ 3 ]: 271 .. 301 [ 5 ]:
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 271 /var/log/journal/00000000000000000000000000000000/system@xxx.journal: 277 /var/log/journal/00000000000000000000000000000000/system.journal: 274 /var/log/journal/00000000000000000000000000000000/system@xxx.journal: 297 /var/log/journal/00000000000000000000000000000000/system@xxx.journal: 274
我系统上最碎的两群文件是 journal 日志和 python2-carbon 的数据文件。carbon 优化做得挺不好的,明明是预分配的固定大小文件啊,不知道怎么的就弄得很碎了。部分程序的日志(如 pacman、getmail)和火狐的 SQLite 数据库也挺碎的。后边这些我已经处理掉了所以示例输出只好用 journal 的啦。
找到想要整理的过碎的文件之后,复制一下就好啦:
for f in $(<list); do sudo cp -a $f $f.new; sudo mv $f.new $f; done
啊对了,工具的编译方法是,获取源码并安装 Rust 之后,在项目根目录里 cargo build --release 然后就可以在 target/release 下找到新鲜的可执行文件了~顺便说一下,这东西是支持 Android 的哦。