ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

tar 打包黑洞文件的特别之处

2021-01-19 21:59:53  阅读:222  来源: 互联网

标签:特别之处 du tar 文件 st file hole 黑洞


文件中的 hole

《APUE》中对文件 hole 的描述内容摘录如下:

文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被读为 0。

文件中的空洞并不要求在磁盘上占用存储区。具体处理方式与文件系统的实现有关,当定位到超出文件尾端之后写时,对于新写的数据需要分配磁盘块,但是对于源文件尾端和新开始写位置之间的部分则不需要分配磁盘块。

创建一个带 hole 文件的 demo

使用如下 demo 能够创建一个带有 hole 的文件:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

static void err_sys(const char *str)
{
        fprintf(stderr, "%s\n", str);
        exit(1);
}

static char     buf1[] = "abcdefghij";
static char     buf2[] = "ABCDEFGHIJ";

#define    FILE_MODE       (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

int
main(void)
{
        int             fd;

        if ((fd = creat("file.hole", FILE_MODE)) < 0)
                err_sys("creat error");

        if (write(fd, buf1, 10) != 10)
                err_sys("buf1 write error");
        /* offset now = 10 */

        if (lseek(fd, 16384, SEEK_SET) == -1)
                err_sys("lseek error");
        /* offset now = 16384 */

        if (write(fd, buf2, 10) != 10)
                err_sys("buf2 write error");
        /* offset now = 16394 */

        exit(0);
}

此 demo 摘自 《APUE》,并进行了必要的修改。编译生成可执行文件 hole,执行则会在当前目录中生成 file.hole 文件。

执行示例如下:

$ ./hole
$ od -c ./file.hole
0000000   a   b   c   d   e   f   g   h   i   j  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
0040000   A   B   C   D   E   F   G   H   I   J
0040012

从 od 命令的输出看,这个文件中间的内容全部是 ‘\0’,这些内容都是文件的 hole。

带 hole 的文件与不带 hole 的文件对比

为了进一步说明带 hole 文件的特别之处,首先创建一个相同大小的不带 hole 的文件。通过执行如下命令创建:

dd if=/dev/zero of=./file.nohole bs=512 count=32

du -b 命令输出信息对比

$ du -b ./*hole
16394   ./file.hole
16394   ./file.nohole

du -b 显示的文件大小一致,都是 16394 字节。

du -k 命令输出信息对比

$ du -k ./*hole
8       ./file.hole
20      ./file.nohole

du -k 命令输出的信息中,带 hole 的文件大小只有 8k,而不带 hole 的文件大小为 20k,这一差别正是因为 hole 并不占磁盘空间,但是为什么 du -b 看到的大小是一样的呢?

du -b vs du -k

du 命令通过 stat 类函数来获取文件、目录的信息,stat 结构体内容如下:

           struct stat {
               dev_t     st_dev;         /* ID of device containing file */
               ino_t     st_ino;         /* Inode number */
               mode_t    st_mode;        /* File type and mode */
               nlink_t   st_nlink;       /* Number of hard links */
               uid_t     st_uid;         /* User ID of owner */
               gid_t     st_gid;         /* Group ID of owner */
               dev_t     st_rdev;        /* Device ID (if special file) */
               off_t     st_size;        /* Total size, in bytes */
               blksize_t st_blksize;     /* Block size for filesystem I/O */
               blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */

               /* Since Linux 2.6, the kernel supports nanosecond
                  precision for the following timestamp fields.
                  For the details before Linux 2.6, see NOTES. */

               struct timespec st_atim;  /* Time of last access */
               struct timespec st_mtim;  /* Time of last modification */
               struct timespec st_ctim;  /* Time of last status change */

           #define st_atime st_atim.tv_sec      /* Backward compatibility */
           #define st_mtime st_mtim.tv_sec
           #define st_ctime st_ctim.tv_sec
           };

st_size 是文件大小(以字节为单位),st_blocks 表示文件占据的以 512-byte 为单元的磁盘块数量,由于文件中存在的黑洞,st_blocks 表示的大小可能比 st_size/512 要小,这正是 du -k 输出信息中带 hole 文件空间变小的原因。

strace -v du -k 中重要的输出内容如下:

newfstatat(AT_FDCWD, "./file.hole", {st_dev=makedev(0x8, 0x21), st_ino=1854490, st_mode=S_IFREG|0644, st_nlink=1, st_uid=1000, st_gid=1000, st_blksize=4096, st_blocks=16, st_size=16394, st_atime=1610945425 /* 2021-01-18T12:50:25.295867503+0800 */, st_atime_nsec=295867503, st_mtime=1610948124 /* 2021-01-18T13:35:24.823884827+0800 */, st_mtime_nsec=823884827, st_ctime=1610948124 /* 2021-01-18T13:35:24.823884827+0800 */, st_ctime_nsec=823884827}, AT_SYMLINK_NOFOLLOW) = 0
write(1, "8.0K\t./file.hole\n", 178.0K  ./file.hole
newfstatat(AT_FDCWD, "./file.nohole", {st_dev=makedev(0x8, 0x21), st_ino=1854491, st_mode=S_IFREG|0644, st_nlink=1, st_uid=1000, st_gid=1000, st_blksize=4096, st_blocks=40, st_size=16394, st_atime=1610945578 /* 2021-01-18T12:52:58.190766552+0800 */, st_atime_nsec=190766552, st_mtime=1610945567 /* 2021-01-18T12:52:47.878588722+0800 */, st_mtime_nsec=878588722, st_ctime=1610945567 /* 2021-01-18T12:52:47.878588722+0800 */, st_ctime_nsec=878588722}, AT_SYMLINK_NOFOLLOW) = 0
write(1, "20K\t./file.nohole\n", 1820K  ./file.nohole

newfstatat 系统调用获取到的两个文件占用的 st_blocks 数目及其占用的磁盘大小如下:

file.hole       st_size=16394   st_blocks=16   8K
file.nohole     st_size=16394   st_blocks=40   20K

这个大小正是 du -k 命令输出的文件实际大小,至于说为啥 du -b 命令输出的文件大小一致,其实是因为 du -b 返回的是 st_size 表示的文件大小

tar 打包带 hole 的文件后解压

用 tar 命令打包带 hole 的文件后,重新解压,解压生成的文件实际占据的磁盘大小变大了。

操作示例如下:

$ tar -cvf file.hole.tar.gz ./file.hole 
./file.hole
$ tar -xvf ./file.hole.tar.gz 
./file.hole
$ du -kh ./file.hole
20K	./file.hole

当目录中存在很多带 hole 的文件,使用 tar 打包后在其它环境中解压会发现解压生成的新文件要比源文件更大,当存储空间不足时就可能会失败

为了解决这种问题,tar 支持 -S 选项,这个选项能够帮我们处理上面这种情况,避免由于 hole 的存在导致解压出来的文件比源文件占用更多磁盘空间的情况。

man tar 得到如下与 -S 选项相关的信息:

       -S, --sparse
              Handle sparse files efficiently.  Some files in the file system may have segments  which  were  actually  never
              written (quite often these are database files created by such systems as DBM).  When given this option, tar at‐
              tempts to determine if the file is sparse prior to archiving it, and if so, to  reduce  the  resulting  archive
              size by not dumping empty parts of the file.

使用 -S 选项后,重新打包并解压,确定解压出的文件占据的磁盘大小与源文件一致。

测试过程记录如下:

$ tar -cvSf ./file.hole.tar.gz ./file.hole
./file.hole
$ tar -xvf file.hole.tar.gz 
./file.hole
$ du -kh ./file.hole
8.0K	./file.hole

-S 选项背后的机关

在确认 -S 选项能够解决问题后,不妨再琢磨下它背后的机关,执行如下命令:

strace -v tar -cvSf ./file.hole.tar.gz ./file.hole

输出信息中重要的内容如下:

lseek(4, 0, SEEK_DATA)                  = 0
lseek(4, 0, SEEK_HOLE)                  = 4096
lseek(4, 4096, SEEK_DATA)               = 16384
lseek(4, 16384, SEEK_HOLE)              = 16394
lseek(4, 16394, SEEK_DATA)              = -1 ENXIO (没有那个设备或地址)
.........
read(4, "abcdefghij\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 512) = 512
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 512) = 512
重复 6 次
lseek(4, 16384, SEEK_SET)               = 16384
read(4, "ABCDEFGHIJ", 10)               = 10
lseek(4, 16394, SEEK_SET)               = 16394
write(3, "./file.hole\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 10240) = 10240

核心在于调用 lseek 确定 hole 的位置!man lseek 获取到如下信息:

       Since version 3.1, Linux supports the following additional values for whence:

       SEEK_DATA
              Adjust  the  file offset to the next location in the file greater than or equal to offset containing data.  If offset points to data, then the file offset
              is set to offset.

       SEEK_HOLE
              Adjust the file offset to the next hole in the file greater than or equal to offset.  If offset points into the middle of a hole, then the file offset  is
              set  to  offset.   If there is no hole past offset, then the file offset is adjusted to the end of the file (i.e., there is an implicit hole at the end of
              any file).

在这个例子中如下两个系统调用界定了 hole 的范围为从 4096 到 16384 之间的区域。

lseek(4, 0, SEEK_HOLE)                  = 4096
lseek(4, 4096, SEEK_DATA)               = 16384

可以看到读取 file.hole 文件内容时,先读取了 4096 字节,然后直接执行 lseek 拨动 offset 到 16384,读取最后 10 个字节数据,这就完成了所有的过程。

最后需要注意的是 SEEK_HOLE 与 SEEK_DATA 从 3.1 版本内核开始才支持!

标签:特别之处,du,tar,文件,st,file,hole,黑洞
来源: https://blog.csdn.net/Longyu_wlz/article/details/112813117

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有