Skip to content

Latest commit

 

History

History
203 lines (169 loc) · 7.53 KB

(08)空洞文件.md

File metadata and controls

203 lines (169 loc) · 7.53 KB

空洞文件

lseek

不带缓冲I/O函数lseek()仅仅将当前的文件偏移量记录在内核中,它并不会引起任何I/O操作。然后,该偏移量用于下一个读或写操作。

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

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

空洞文件的存在意味着一个文件实际上占用的磁盘大小是可能会小于该文件名义上的大小的。

为了便于管理文件,文件系统都是按块大小来分配给文件的。

刚打开一个文件的时候,偏移量都是0。

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

off_t lseek(int fd, off_t offset, int whence);

// whence的值有SEEK_SET,SEEK_CUR,SEEK_END
// SEEK_SET, 将该文件的偏移量设置为距文件开始处offset个字节
// SEEK_CUR, 将该文件的偏移量为其当前值加上offset,offset可正数可负数
// SEEK_END, 将该文件的偏移量设置为文件长度加offset,offset可正数可负数

返回值off_t,若成功,则返回新的偏移量;若出错,返回 -1。

判断一个设备或文件是否可以设置偏移量

off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);

常规文件都可以设置偏移量,而设备一般是不可以设置偏移量的。

如果设备不支持lseek,则lseek返回-1

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

main(void)
{
    if(lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
        printf("can not seek\n");
    else
        printf("seek ok\n");
    return 0;
}

其中STDIN_FILENO属于系统API接口库,其声明为 int 型,是一个打开文件句柄,对应的函数主要包括 open/read/write/close 等系统级调用。 操作系统一级提供的文件API都是以文件描述符来表示文件。STDIN_FILENO就是标准输入设备的文件描述符。

效果如下:

[root@fqhnode01 c++]# cat lseek_test_file 
hello
[root@fqhnode01 c++]# cat lseek_test_file | ./mylseek 
can not seek
[root@fqhnode01 c++]# ./mylseek < lseek_test_file 
seek ok

生成一个空洞文件

首先新建一个文件,可以看出其字节数是12,os为其分配了8个数据块,其中文件系统的最佳缓冲大小为4096。

这里需要说明的是stat的输出,8个数据块表示的是占用了8个物理扇区。 扇区,是硬盘片上的最小存储单位,一个扇区一般是512字节。

逻辑块(block): 分区进行格式化时所指定的“最小存储单位”。即文件系统存储的最小单位,这里是4096。

// vim /usr/include/bits/stat.h
__off64_t st_size;                  /* Size of file, in bytes.  */

__blksize_t st_blksize;     /* Optimal block size for I/O.  */
__blkcnt64_t st_blocks;     /* Nr. 512-byte blocks allocated.  */

4096/512=8

[root@fqhnode01 c++]# fdisk -l

磁盘 /dev/sda:32.2 GB, 32212254720 字节,62914560 个扇区
Units = 扇区 of 1 * 512 = 512 bytes
扇区大小(逻辑/物理):512 字节 / 512 字节
I/O 大小(最小/最佳):512 字节 / 512 字节
磁盘标签类型:dos
磁盘标识符:0x0006197f

   设备 Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048    33548287    16773120   83  Linux
/dev/sda2        33548288    41936895     4194304   82  Linux swap / Solaris
/dev/sda3        41936896    62914559    10488832   83  Linux

系统通常一次会读取一个Block size大小,而不是一个扇区大小。 即一个文件至少占用一个逻辑块block,即4.0K,包含8个扇区

[root@fqhnode01 c++]# touch lseek_test_file 
[root@fqhnode01 c++]# echo "hello world">lseek_test_file 
[root@fqhnode01 c++]# du -h lseek_test_file 
4.0K	lseek_test_file
[root@fqhnode01 c++]# stat lseek_test_file 
  文件:"lseek_test_file"
  大小:12        	块:8          IO 块:4096   普通文件
设备:801h/2049d	Inode:36529634    硬链接:1
权限:(0644/-rw-r--r--)  Uid:(    0/    root)   Gid:(    0/    root)
环境:unconfined_u:object_r:admin_home_t:s0
最近访问:2018-02-14 10:34:11.089426007 -0500
最近更改:2018-02-14 10:34:13.714738010 -0500
最近改动:2018-02-14 10:34:13.714738010 -0500
创建时间:-

执行下面的程序

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

int main(int argc, char *argv[])
{
    int fd;
    fd = open(argv[1], O_RDWR);
    if (fd<0){
        perror("fd<0");
        return 0;
    }
    lseek(fd, 16384,SEEK_SET);
    write(fd, "abcde",5);
    close(fd);
    return 0;
}

(16384+5)/512=32

效果如下:

[root@fqhnode01 c++]# gcc -o mylseek mylseek.c 
[root@fqhnode01 c++]# ./mylseek lseek_test_file 
[root@fqhnode01 c++]# ll lseek_test_file 
-rw-r--r--. 1 root root 16389 2月  14 11:10 lseek_test_file
[root@fqhnode01 c++]# stat lseek_test_file 
  文件:"lseek_test_file"
  大小:16389     	块:16         IO 块:4096   普通文件
设备:801h/2049d	Inode:36284030    硬链接:1
权限:(0644/-rw-r--r--)  Uid:(    0/    root)   Gid:(    0/    root)
环境:unconfined_u:object_r:admin_home_t:s0
最近访问:2018-02-14 11:08:53.743231691 -0500
最近更改:2018-02-14 11:10:05.093886607 -0500
最近改动:2018-02-14 11:10:05.093886607 -0500
创建时间:-
[root@fqhnode01 c++]# du -h lseek_test_file 
8.0K	lseek_test_file

[root@fqhnode01 c++]# od -c lseek_test_file 
0000000   h   e   l   l   o       w   o   r   l   d  \n  \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
0040005

可以发现文件lseek_test_file名义上的大小变为了16389个字节,但占用的扇区只有16个,是要小于32个的。

最后,来看看把空洞的地方填充为0,会如何?read()会把空洞的地方读取为0,然后写入到新的文件中。

[root@fqhnode01 c++]# cat lseek_test_file >lseek_new_file
[root@fqhnode01 c++]# du -c lseek_test_file lseek_new_file 
8	lseek_test_file
20	lseek_new_file
28	总用量

[root@fqhnode01 c++]# stat lseek_new_file 
  文件:"lseek_new_file"
  大小:16389     	块:40         IO 块:4096   普通文件
设备:801h/2049d	Inode:36284001    硬链接:1
权限:(0644/-rw-r--r--)  Uid:(    0/    root)   Gid:(    0/    root)
环境:unconfined_u:object_r:admin_home_t:s0
最近访问:2018-02-14 11:15:05.215808560 -0500
最近更改:2018-02-14 11:14:41.869808560 -0500
最近改动:2018-02-14 11:14:41.869808560 -0500
创建时间:-

这里分配了40个扇区大小,不是上面算出来的32。。。。多了一个逻辑块,可能和文件系统有关系??

需要说明的是

  1. ls -l file 查看文件逻辑大小
  2. du -c file 查看文件实际占用的存储块多少
  3. od -c file 查看文件存储的内容

用途

实际中的空洞文件会在哪里用到呢?常见的场景有两个: 一是在下载电影的时候,发现刚开始下载,文件的大小就已经到几百M了。

二是在创建虚拟机的磁盘镜像的时候,你创建了一个100G的磁盘镜像,但是其实装起来系统之后,开始也不过只占用了3,4G的磁盘空间,如果一开始把100G都分配出去的话,无疑是很大的浪费.