空洞的概念

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. JUC自定义线程池练习

    JUC自定义线程池练习 首先上面该线程池的大致流程 自定义阻塞队列 首先定义一个双向的队列和锁一定两个等待的condition 本类用lock来控制多线程下的流程执行 take和push方法就是死等, ...

  2. 谈谈markdown

    谈谈markdown 欢迎关注我的博客,️点他即可. 最近一年开始学习有关编程的内容了. 迷上代码的我开始接触到一些好玩的东西,我发现很多事情都可以由代码来完成,甚至是ppt.同学就经常说我疯掉了,连 ...

  3. springMvc和Hibernate集成实现用户添加

    源码:http://pan.baidu.com/s/1i4xVLE9(百度云) 步骤:一.创建数据库(mysql) 二.导入相应jar包(注意不同数据库jdbc.jar包)配置web.xml.spri ...

  4. 好客租房3-React的基本使用

    2.1React的安装 安装命令:npm i react react-dom react 包是核心,提供创建元素,组件等功能 react-dom包提供DOM相关功能等 2.2React的使用 1引入r ...

  5. CF1681F Unique Occurrences

    题意:一棵树,问每条路径上只出现一次的值的个数的和. 思路: 显然想到考虑边贡献.每条边权下放到下面的哪个点.\(up_i\)为上面第一个点权等于它的点.我们需要一个子树内点权等于它的点(如果满足祖孙 ...

  6. 从零开始学YC-Framework之鉴权

    一.YC-Framework鉴权是基于哪一个开源框架做的? YC-Framework鉴权主要基于Dromara开源社区组织下的Sa-Token. 1.什么是Sa-Token? Sa-Token是一个轻 ...

  7. java基础题(5)

    6.常用API 6.1string类 1.动态字符串 描述 将一个由英文字母组成的字符串转换成从末尾开始每三个字母用逗号分隔的形式. 输入描述: 一个字符串 输出描述: 修改后的字符串 示例1 输入: ...

  8. MyBatis - MyBatis的层次结构

    API接口层 规定了一系列接口,能够向外提供接口,对内进行操作. 数据处理层 负责SQL相关处理工作,如:SQL查找.SQL执行.SQL映射等工作. 基础支撑层 提供基础功能支撑,包括连接管理.事务管 ...

  9. CentOS搭建BWAPP靶场并安装docker

    为了不触碰国家安全网络红线作为技术人员我们尽可能的要在自己本机在上面创建自己的靶场: 在centos上面搭建靶场看似非常简单短短几行代码,需要注意以下几个点:(1.在docker上搭建   2.端口号 ...

  10. java中的final与可变类型、不可变类型的关系

    如果你对final和不可变类型的概念与区别有疑问的话,可以打开这篇文章.希望我的解答可以帮到您! 1.不可变类型: 什么是可变类型,什么是不可变类型呢? 首先我们看一下下面的这行代码: String ...