空洞的概念

linux 上普通文件的大小与占用空间是两个概念,前者表示文件中数据的长度,后者表示数据占用的磁盘空间,通常后者大于前者,因为需要一些额外的空间用来记录文件的某些统计信息或附加信息、以及切分为块的数据信息 (通常不会占用太多)。文件占用空间也可以小于文件尺寸,此时文件内部就存在空洞了。

所谓空洞其实就是没有分配存储空间的数据块,当访问这些数据块时,系统返回 0,就如同读到空文件一般,当写这些块时,系统再实地分配对应的存储空间。其实这个和内存中的虚址地址与物理地址的概念非常相似——操作系统可以预分配一大块内存地址,这个地址只是一段连续的数字,用来保证虚拟地址不会被其它人占用,而对应的物理地址只在用到时才分配,这样就避免了一下分配一大块内存带来的浪费问题。同理,如果抽象出一个文件地址和存储地址来的话,完全可以套用上面的结论:连续的文件地址保证用户可以访问任意偏移的文件数据;文件中的空洞又避免了一下子分配太多的物理存储带来的浪费。

所以空洞不光针对文件,也可以针对内存,可以将虚址中的缺页中断理解为填补内存空洞的过程,文件中也有类似的机制。不过也有一些差异,例如内存因进程间共享而引入的 copy-on-write 机制,文件中就没有。文件同一地址的数据如果被多个进程同时写入时,只有最后一个写入的会生效,前面的那些都会被覆盖,因为文件是系统级别的概念,不像内存一样专属于某个进程。

空洞的产生

下面分平台说明。

Linux

所有的类 Unix 系统都差不多,方法比较简单,满足以下两点即可:

  • 设置文件的偏移量 (lseek) 超过文件尾端
  • 并写了某些数据后 (write)

此时原文件末尾到新文件末尾之间将标记为空洞。甚至都不需要写一个程序,就可以验证:

$ echo "this is a test" > test.txt
$ ls -lh test.txt
-rw-rw-r-- 1 yunh yunh 15 Oct 30 16:14 test.txt $ stat test.txt
File: test.txt
Size: 15 Blocks: 8 IO Block: 4096 regular file
Device: 805h/2053d Inode: 35259462 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ yunh) Gid: ( 1000/ yunh)
Access: 2021-10-30 16:15:00.767760242 +0800
Modify: 2021-10-30 16:14:58.160147599 +0800
Change: 2021-10-30 16:14:58.160147599 +0800
Birth: - $ du -sh test.txt
4.0K test.txt $ truncate -s 1M test.txt
$ ls -lh test.txt
-rw-rw-r-- 1 yunh yunh 1.0M Oct 30 16:16 test.txt $ stat test.txt
File: test.txt
Size: 1048576 Blocks: 8 IO Block: 4096 regular file
Device: 805h/2053d Inode: 35259462 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ yunh) Gid: ( 1000/ yunh)
Access: 2021-10-30 16:15:00.767760242 +0800
Modify: 2021-10-30 16:16:02.914508936 +0800
Change: 2021-10-30 16:16:02.914508936 +0800
Birth: - $ du -sh test.txt
4.0K test.txt

上面的例子中,标称 1MB 的 test.txt 文件只占用 4KB 空间。带有空洞的文件复制后还有空洞吗?这要看你用什么方式复制了,如果是 cp 答案是有,如果是 cat + 重定向,没有,请看下面的例子:

$ cp test.txt foo.txt
$ cat test.txt > bar.txt
$ ls -lh *.txt
-rw-rw-r-- 1 yunh yunh 1.0M Oct 30 16:29 bar.txt
-rw-rw-r-- 1 yunh yunh 1.0M Oct 30 16:29 foo.txt
-rw-rw-r-- 1 yunh yunh 1.0M Oct 30 16:16 test.txt $ stat *.txt
File: bar.txt
Size: 1048576 Blocks: 2048 IO Block: 4096 regular file
Device: 805h/2053d Inode: 35259560 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ yunh) Gid: ( 1000/ yunh)
Access: 2021-10-30 16:29:21.921709008 +0800
Modify: 2021-10-30 16:29:21.925707851 +0800
Change: 2021-10-30 16:29:21.925707851 +0800
Birth: -
File: foo.txt
Size: 1048576 Blocks: 8 IO Block: 4096 regular file
Device: 805h/2053d Inode: 35259559 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ yunh) Gid: ( 1000/ yunh)
Access: 2021-10-30 16:29:16.751224261 +0800
Modify: 2021-10-30 16:29:16.755223068 +0800
Change: 2021-10-30 16:29:16.755223068 +0800
Birth: -
File: test.txt
Size: 1048576 Blocks: 8 IO Block: 4096 regular file
Device: 805h/2053d Inode: 35259462 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ yunh) Gid: ( 1000/ yunh)
Access: 2021-10-30 16:19:51.460219249 +0800
Modify: 2021-10-30 16:16:02.914508936 +0800
Change: 2021-10-30 16:16:02.914508936 +0800
Birth: - $ du -sh *.txt
1.0M bar.txt
4.0K foo.txt
4.0K test.txt

cp 后的文件保留了相同的空洞,cat + 重定向的则生成了没有空洞的文件。从另一个侧面说明读取空洞时,系统是返回了 0 的。

Windows

与类 Unix 系统不同,windows 使用稀疏文件 (sparse) 来表示含有空洞的文件。不光是概念上有区别,实现上也有差别,例如使用类似 linux 的超出文件末尾写策略,并不能生成一个稀疏文件。当然了,首先要保证文件系统是 NTFS,其次需要使用 windows 特定的 api 来完成这项工作。

  • SetFilePointer (lseek)
  • WriteFile (write)
  • SetEndOfFile (n/a)

并且需要在这样做之前声明文件为稀疏文件,系统才会为它生成空洞节省空间:

DeviceIoControl(hFile, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &dwTemp, NULL);

hFile 为打开的文件句柄。widnows 的空洞本质上是一种数据压缩,将很多 0 压缩在一起,不过确确实实起到了节省存储空间的目的。

空洞的应用

下面的脚本可以搜索文件系统中带空洞的文件:

#! /bin/sh

function main()
{
local path="."
if [ $# -gt 0 ]; then
path="$1"
fi echo "detect hole under ${path}"
local size=0
local space=0
for file in $(find "${path}" -type f); do
if [ -f "${file}" ]; then
size=$(stat -c "%s" "${file}")
space=$(($(du -k "${file}" | awk '{print $1}')*1024))
if [ ${size} -gt ${space} ]; then
echo "${file} has hole, space ${space}, size ${size}"
fi
else
# file-name has chinese character ?
#echo "no ${file}"
:
fi
done
echo "done!"
} main "$@"

在我的一台笔记本设备上的确产生了输出:

$ bash -f find_hole.sh /home 2>/dev/null
detect hole under /home
/home/yunh/snap/ohmygiraffe/common/.cache/mesa_shader_cache/index has hole, space 0, size 1310728
/home/yunh/.config/baidunetdisk/GPUCache/data_0 has hole, space 12288, size 45056
/home/yunh/.config/baidunetdisk/GPUCache/index has hole, space 45056, size 262512
/home/yunh/.config/baidunetdisk/GPUCache/data_3 has hole, space 98304, size 4202496
/home/yunh/.config/baidunetdisk/GPUCache/data_1 has hole, space 12288, size 270336
/home/yunh/.cache/mesa_shader_cache/index has hole, space 753664, size 1310728
/home/yunh/code/apue/04.chapter/foo.txt has hole, space 4096, size 1048576
/home/yunh/code/apue/04.chapter/test.txt has hole, space 4096, size 1048576
/home/yunh/code/apue/08.chapter/file.map has hole, space 20480, size 1048576
/home/yunh/.mozilla/firefox/g6azoga7.default-release/storage/default/https+++mail.126.com/cache/caches.sqlite has hole, space 86016, size 98304
/home/yunh/.mozilla/firefox/g6azoga7.default-release/storage/default/https+++126.com/cache/caches.sqlite has hole, space 86016, size 98304
/home/yunh/.mozilla/firefox/g6azoga7.default-release/storage/default/moz-extension+++d24d4498-4011-4423-805a-f6f4f5ace4f7^userContextId=4294967295/idb/3647222921wleabcEoxlt-eengsairo.sqlite has hole, space 45056, size 49152
/home/yunh/.mozilla/firefox/g6azoga7.default-release/storage/default/https+++blog.csdn.net/cache/caches.sqlite has hole, space 61440, size 65536
/home/yunh/.mozilla/firefox/g6azoga7.default-release/cookies.sqlite has hole, space 98304, size 524288
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/moz-extension+++15f580b5-b741-4f58-b7b2-50144c678660^userContextId=4294967295/idb/3647222921wleabcEoxlt-eengsairo.sqlite has hole, space 45056, size 49152
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/http+++www.chinadegrees.cn/idb/3178482897EPkc.sqlite has hole, space 45056, size 49152
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/https+++mail.126.com/cache/caches.sqlite has hole, space 176128, size 196608
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/https+++translate.yandex.kz/idb/3977681304ystnro_ictoclel.sqlite has hole, space 45056, size 49152
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/moz-extension+++5cbf54b3-f5e8-493a-9096-76e3ad392e45^userContextId=4294967295/idb/3647222921wleabcEoxlt-eengsairo.sqlite has hole, space 45056, size 49152
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/http+++www.cdgdc.edu.cn/idb/3178482897EPkc.sqlite has hole, space 45056, size 49152
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/https+++account.cnblogs.com/idb/1170976282GNEEEKROATNMDO.sqlite has hole, space 45056, size 49152
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/moz-extension+++f17a211a-4d0c-413c-8ced-df3b145f19ec^userContextId=4294967295/idb/3647222921wleabcEoxlt-eengsairo.sqlite has hole, space 45056, size 49152
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/moz-extension+++56a2ddfb-80ba-4bd7-913a-8ae756a8dc6f^userContextId=4294967295/idb/3647222921wleabcEoxlt-eengsairo.sqlite has hole, space 45056, size 49152
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/http+++www.chinadegrees.com.cn/idb/3178482897EPkc.sqlite has hole, space 45056, size 49152
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/https+++126.com/cache/caches.sqlite has hole, space 122880, size 131072
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/https+++126.com/idb/4197078560wnooriktbaorxi-pex.sqlite has hole, space 61440, size 65536
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/https+++newtab.firefoxchina.cn/cache/caches.sqlite has hole, space 94208, size 98304
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/https+++www.recaptcha.net/idb/548905059db.sqlite has hole, space 45056, size 49152
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/https+++blog.csdn.net/cache/caches.sqlite has hole, space 61440, size 65536
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/storage/default/moz-extension+++ae22fc49-8037-4c59-8299-2523bd5c1548^userContextId=4294967295/idb/3647222921wleabcEoxlt-eengsairo.sqlite has hole, space 45056, size 49152
/home/yunh/.mozilla/firefox/l7y3lhkj.default-esr/cookies.sqlite has hole, space 196608, size 524288

看起来像是用来做 cache 的,不明觉厉~

能想到的另一个应用场景就是下载大文件,例如一个 2GB 的文件,如果害怕因下载时间太长导致后面磁盘空间不足而失败的情况,可以预先将文件扩展到 2GB,再分别填充其中的数据。不过这个更像是 windows 上的 SetEndOfFile 的应用场景,因为需要事先分配这么多存储空间,而不是像文件空洞那样只给一个标称的 2GB 文件而实际不分配存储空间。从这个角度看,windows 确实有一定的优势,因为在 linux 上占用 2GB 空间还真不是几个调用就可以搞定的。

还能想到的一个场景就是分块下载,这个和文件空洞确实可以产生一些化学反应。当大文件被切分为多个数据块同时下载以提高速度时,传统的方式是按块号顺序合并,如果中间有一个块没有下载完成,那么之后的数据块都不能合并到目标文件里去。如果使用文件空洞,哪个块下载完了就可以先合并到目标文件,不存在合并顺序的问题,从而解决上面的问题,防止太多块文件留存在文件系统中。不过只要还有一个块没下载完,文件就是不完整的,肯定会影响后期的解压、播放、加载,因此并没有解决很大的问题。

最终结论就是,文件空洞并没有内存空洞那么有用,如果你遇到过它的应用场景,欢迎在评论区拍砖斧正~~

参考

[1]. lseek函数与文件空洞

[2]. windows稀疏文件

[apue] 文件中的空洞的更多相关文章

  1. UNIX环境高级编程APUE练习4.6-实现类似cp(1)的程序,保留文件中的空洞

    1 题面 编写类似cp(1)的程序,它复制包含空洞的文件,但是不将字节0写到输出文件中去. 2 基本思路 首先要搞清楚空洞的性质以判断一个文件是否有空洞,以及空洞的位置 知道了空洞的位置之后,读到源文 ...

  2. Unix - 文件中构成一个空洞的分析

    lseek函数显示地为一个打开文件设置偏移量,文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,这一点是允许的.位于文件中但没有写过的字节都被读为 ...

  3. APUE 文件IO

    文件 IO 记录书中的重要知识和思考实践部分 Unix 每个文件都对应一个文件描述符(file descriptor),为一个非负整数,一个文件可以有多个fd, 后面所有与文件(设备,套接字等)有关操 ...

  4. [APUE] 文件 I/O

    文件操作相关 API:open, read, write, lseek, close. 多进程共享文件的相关 API:dup, dup2, fcntl, sync, fsync, ioctl. 文件操 ...

  5. 关于apue.3e中apue.h的使用

    关于apue.3e中apue.h的使用 近来要学一遍APUE第三版,并于此开博做为记录. 先下载源文件: # url: http://http//www.apuebook.com/code3e.htm ...

  6. 《APUE》中的函数整理

    第1章 unix基础知识 1. char *strerror(int errnum) 该函数将errnum(就是errno值)映射为一个出错信息字符串,返回该字符串指针.声明在string.h文件中. ...

  7. 如何自己编译apue.3e中代码 & 学习写makefile

    本来是搜pthread的相关资料,看blog发现很多linux程序员都看的一本神书<APUE>,里面有系统的两章内容专门讲pthread(不过是用c语言做的代码示例,这个不碍事,还是归到原 ...

  8. ASP.NET Core 在 JSON 文件中配置依赖注入

    前言 在上一篇文章中写了如何在MVC中配置全局路由前缀,今天给大家介绍一下如何在在 json 文件中配置依赖注入. 在以前的 ASP.NET 4+ (MVC,Web Api,Owin,SingalR等 ...

  9. 将Json数据保存在静态脚本文件中读取

    一些常用的数据例如一些网站的区域信息被改变的可能性不大,一般不通过请求获取,于是我们选择存在静态文件中,例如以下Demo: 1.动态加载Json数据显示到前台 [HttpPost] public Ac ...

随机推荐

  1. mybatis plus 的 ActiveRecord 模式

    实体类继承 Model public class Test extends Model<Test> implements Serializable {} 就可以 new Test().in ...

  2. 一文学会Java的交互式编程环境jshell

    什么是交互式编程环境?重点词交互,在这样的编程环境中,你每输入一行代码,环境都会给你一个反馈,这就是交互式的编程环境.这种编程环境并不太适合工程化的复杂性需求,但在一些快速验证.简单计算之类的场景下还 ...

  3. Mybatis-Plus乐观锁Version

    实现原理 取出记录时,获取当前version更新时,带上这个version执行更新时, set version = newVersion where version = oldVersion如果ver ...

  4. 好客租房33-事件绑定this指向(总结)

    1推荐使用class的实例方法 //导入react import React from 'react'   import ReactDOM from 'react-dom' //导入组件   // 约 ...

  5. 实践torch.fx第一篇——基于Pytorch的模型优化量化神器

    第一篇--什么是torch.fx 今天聊一下比较重要的torch.fx,也趁着这次机会把之前的torch.fx笔记整理下,笔记大概拆成三份,分别对应三篇: 什么是torch.fx 基于torch.fx ...

  6. K8S 部署Dashboard UI

    Kubernetes Dashboard是Kubernetes集群的通用.基于Web的UI.它允许用户管理集群中运行的应用程序并对其进行故障排除,以及管理集群本身. 访问到DashBoard有两种方式 ...

  7. T1创世纪(原创)

    创世纪 这是我的第一道原创题 题解: 这道题的核心算法是:加维度的最短路+贪心 状态:\(dis[i][j][t][a]\)表示在 \(t\) 时,到达 \((i,j)\) ,当前共造\(a\)只&q ...

  8. LC T668笔记 & 有关二分查找、第K小数、BFPRT算法

    LC T668笔记 [涉及知识:二分查找.第K小数.BFPRT算法] [以下内容仅为本人在做题学习中的所感所想,本人水平有限目前尚处学习阶段,如有错误及不妥之处还请各位大佬指正,请谅解,谢谢!] !! ...

  9. element-ui table组件使用v-if时的问题

    element-ui项目中经常遇到需要使用v-if指令来根据情况动态显示隐藏某些列情况,这时就会出现滚动条样式异常.列错乱.列宽错乱等问题 解决办法:在el-table上添加:key="Ma ...

  10. Hash表、 继承

    Hash表 我们来了解什么是Hash表?? 要想知道什么是哈希表,那得先了解哈希函数 二叉平衡树 红黑树 B B+树,它们的查找都是先从根节点进行查找,从节点取出数据或索引与查找值进行比较.那么,有没 ...