空洞的概念

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. 2022管家婆工贸版ERP T3 V22.0工厂管理软件单机网络版无限用户免狗软件可定制

    管家婆工贸版是一款针对国内中小加工企业开发的管理软件,软件以财务管理为核心,集采购.销售.存货.生产.工资.固定资产.账务管理等模块于一体,对企业的信息进行监控,实现对企业物流.资金流.信息流和生产成 ...

  2. 常见的邮箱服务器(SMTP,POP3)地址,端口

    163.com: POP3服务器地址:pop.163.com(端口:110) SMTP服务器地址:smtp.163.com(端口:25) sina.com: POP3服务器地址:pop3.sina.c ...

  3. 690. Employee Importance - LeetCode

    Question 690. Employee Importance Example 1: Input: [[1, 5, [2, 3]], [2, 3, []], [3, 3, []]], 1 Outp ...

  4. Java线程同步操作

    synchronized 作用于对象实例:对给定对象加锁,进入同步代码前要获得给定对象的锁. 作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁. 作用于静态方法:相当于对当前类加 ...

  5. linux篇-Parse error: syntax error, unexpected ‘new’ (T_NEW) in /usr/local/nginx/html/cacti/lib/adodb

    1首先这是基于lnmp模式进行的 2yum安装 yum -y install httpd mysql mysql-server php php-mysql php-json php-pdo 3lib库 ...

  6. python面向对象双下划线方法与元类

    目录 双下划线方法(__) 元类简介 产生类的两种表现形式 元类的基本使用 元类进阶操作 __new__方法 双下划线方法(__) 面向对象中的双下方法也有一些人称之为是魔法方法,有些双下方法不需要刻 ...

  7. SpringBoot 整合 RabbitMQ 实现消息可靠传输

    消息的可靠传输是面试必问的问题之一,保证消息的可靠传输主要在生产端开启 comfirm 模式,RabbitMQ 开启持久化,消费端关闭自动 ack 模式. 环境配置 SpringBoot 整合 Rab ...

  8. django框架9

    内容概要 用户名动态校验 删除二次确认 sweetalert前端插件 django自带的序列化组件 批量数据操作 分页器推导流程 自定义分页器封装代码 自定义分页器使用方法 校验性组件之forms组件 ...

  9. 27.MySQL 索引、事务与存储引擎

    MySQL 索引.事务与存储引擎 目录 MySQL 索引.事务与存储引擎 MySQL 索引 索引的概念 索引的作用及副作用 索引的作用 索引的副作用 创建索引的原则依据 索引的分类和创建 普通索引 唯 ...

  10. 5G的发布加快了智慧城市/三维物联网等行业的发展

    最近很多人发现自己的5G手机突然没了5G信号,难道是美国搞的鬼? 不不不,这其实是因为5G的NSA基站被撤站了,官方已经做了部署,要大力推进SA网络建设.所以之前支持NSA模式的5G手机,现在都成了4 ...