​​lseek​​的“时空跳跃”:从获取大小到制造“文件空洞” - 详解

80T资源合集下载
链接:https://pan.quark.cn/s/5643428d4f9f

在 Linux 的文件世界里,我们的读写操作就像是在一本书上顺序阅读或书写。但有时,我们需要更灵活的能力:

  • 想知道这本书到底有多少页?
  • 想直接翻到某一页开始阅读?
  • 甚至,想在这本书的末尾凭空加上 100 张空白页?

​lseek​​ 系统调用就是赋予我们这种“时空跳跃”能力的魔法棒。今天,我们将通过三个实战案例,彻底掌握 ​​lseek​​ 的三大核心技巧,并揭开一个有趣的概念——文件空洞 (File Hole)

​lseek​​ 函数速览

在我们开始“跳跃”之前,先看一下它的“操作面板”:

#include 
#include 
off_t lseek(int fd, off_t offset, int whence);
  • ​fd​​: 文件描述符,我们的“书本”。
  • ​offset​​: 偏移量,我们要跳跃的“页数”。正数向后跳,负数向前跳。
  • ​whence​​: 起始位置,我们从哪里开始跳?
  • ​SEEK_SET​​: 从书的开头(文件头)开始。
  • ​SEEK_CUR​​: 从当前翻到的位置(当前读写位置)开始。
  • ​SEEK_END​​: 从书的结尾(文件末尾)开始。
  • 返回值: 这是一个关键!**无论 ​​whence​​ 是什么,成功时它永远返回从文件头算起的新位置。**失败则返回 -1。
技巧一:获取文件大小 —— 翻到最后一页

想知道文件有多大?最简单的方法就是用 ​​lseek​​ 直接“翻到”文件的末尾,然后看看“页码”。

lseek(fd, 0, SEEK_END)​ 这行代码的意思是:从文件末尾开始,移动 0 个字节。这并不会改变文件内容,但它的返回值恰好就是文件末尾相对于文件头的偏移量,也就是文件的大小。

案例1:测量文件大小

  1. 准备文件 data.txt
echo "Hello, lseek world!" > data.txt
# wc -c data.txt  (可以看到文件大小是 20 字节)
  1. 编写代码 get_size.c
#include 
#include 
#include 
#include 
int main() {
    int fd = open("data.txt", O_RDONLY);
    if (fd < 0) {
        perror("open");
        exit(1);
    }
    off_t file_size = lseek(fd, 0, SEEK_END);
    if (file_size == -1) {
        perror("lseek");
        exit(1);
    }
    printf("The size of data.txt is: %ld bytes\n", file_size);
    close(fd);
    return 0;
}
  1. 编译并运行
gcc get_size.c -o get_size
./get_size
  1. 运行结果
The size of data.txt is: 20 bytes

分析:结果与 ​​wc -c​​ 命令完全一致,我们成功地用 ​​lseek​​ 获取了文件大小。

技巧二:精确定位 —— 读写指针的共享秘密

​lseek​​ 最本职的工作是移动读写指针。但这里有一个非常重要的“坑”:文件的读指针和写指针是共享的,它们是同一个东西!

这意味着,你执行了一次 ​​write​​​ 操作后,指针会停在写入内容的末尾。如果你紧接着执行 ​​read​​,你将从那个新位置开始读,而不是从文件头。

案例2:写后立即读的“陷阱”与破解 这个程序会先向一个空文件写入 "ABC",然后尝试立即读出文件内容。

// read_after_write.c
#include 
#include 
#include 
#include 
int main() {
    int fd = open("test.log", O_RDWR | O_CREAT | O_TRUNC, 0664);
    // ... 错误检查 ...
    // 写入 "ABC"
    write(fd, "ABC", 3);
    printf("Wrote 'ABC' to file. Current position should be at 3.\n");
    char buffer[10] = {0};
    // 陷阱:直接读取
    ssize_t n = read(fd, buffer, sizeof(buffer));
    printf("\nAttempt 1 (without lseek):\n");
    printf("Read %ld bytes. Content: '%s'\n", n, buffer);
    // 破解:使用 lseek 将指针移回文件头
    lseek(fd, 0, SEEK_SET);
    printf("\nUsed lseek(fd, 0, SEEK_SET) to rewind.\n");
    // 再次读取
    n = read(fd, buffer, sizeof(buffer));
    printf("\nAttempt 2 (with lseek):\n");
    printf("Read %ld bytes. Content: '%s'\n", n, buffer);
    close(fd);
    return 0;
}

编译并运行

gcc read_after_write.c -o read_after_write
./read_after_write

运行结果

Wrote 'ABC' to file. Current position should be at 3.
Attempt 1 (without lseek):
Read 0 bytes. Content: ''
Used lseek(fd, 0, SEEK_SET) to rewind.
Attempt 2 (with lseek):
Read 3 bytes. Content: 'ABC'

分析:第一次 ​​read​​​ 失败了!因为它从指针停留的第 3 个字节处开始读,那里已经是文件末尾 (EOF),所以读到了 0 字节。通过 ​​lseek​​ 将指针“倒带”回文件头后,我们才成功读出了写入的内容。切记:读写共享同一指针!

技巧三:文件拓展与“文件空洞” —— 凭空造页

这是 ​​lseek​​​ 最神奇也是面试中最常被问到的功能。​​lseek​​ 允许你将指针移动到超过文件末尾的地方。

但这还不够,单纯移动指针并不会改变文件大小。你必须在那个遥远的位置执行一次 write​ 操作,哪怕只写一个字节。这时,文件系统才会真正地将文件大小拓展到你的目标位置。

那么,从原文件末尾到你新写入位置之间的那段巨大空间,是什么呢?它就是文件空洞 (File Hole)

  • 特点:它在逻辑上存在,你可以 ​​lseek​​​ 到其中任何位置。当你读取它时,你会读到满屏的 ​​\0​​ (空字符)。
  • 优点它不占用任何实际的磁盘空间! 只有你真正写入数据的部分才会占用磁盘块。这种文件被称为“稀疏文件 (Sparse File)”,在虚拟机镜像、数据库等场景中非常有用。

案例3:创建一个带“空洞”的大文件 这个程序会创建一个 10 字节的文件,然后通过 ​​lseek​​​ 和 ​​write​​ 将其拓展到 1000 字节。

// create_hole.c
#include 
#include 
#include 
#include 
int main() {
    int fd = open("hole.dat", O_RDWR | O_CREAT | O_TRUNC, 0664);
    // ... 错误检查 ...
    // 写入 10 字节的初始数据
    write(fd, "0123456789", 10);
    // 将指针移动到 999 字节处
    lseek(fd, 989, SEEK_CUR); // 从当前位置(10)再向后移动989字节,到达999
    // 在 999 字节处写入一个字节
    write(fd, "X", 1);
    // 文件大小现在应该是 1000 字节
    close(fd);
    printf("hole.dat created.\n");
    return 0;
}

编译并运行

gcc create_hole.c -o create_hole
./create_hole

验证结果

  1. 查看逻辑大小
ls -l hole.dat
# 输出:
# -rw-rw-r-- 1 user user 1000 date time hole.dat

​ls​​ 告诉我们,这个文件逻辑上确实有 1000 字节。

  1. 查看实际占用的磁盘空间
du -h hole.dat
# 输出:
# 4.0K hole.dat

​du​​ (Disk Usage) 告诉我们,它实际占用的磁盘空间只有一个块的大小(通常是 4KB),远小于 1000 字节!这就是“文件空洞”的魔力。

别忘了 ​​truncate​

虽然 ​​lseek​​​ + ​​write​​​ 的方式很酷,但如果你只是想简单地改变文件大小(无论是增大还是截断),有一个更直接的函数:​​truncate​​。

truncate("file.path", new_size);

它会直接修改文件的元数据,扩展的部分会自动用 ​​\0​​​ 填充。它比 ​​lseek​​ 方案更简洁,是拓展文件的“正规军”。

总结

​lseek​​ 是一个强大而底层的工具,它让我们能够:

  1. 高效获取文件大小: ​​lseek(fd, 0, SEEK_END)​
  2. 自由定位读写: 牢记读写指针共享的特性。
  3. 创建稀疏文件: 通过 ​​lseek​​​ + ​​write​​ 制造不占磁盘空间的“文件空洞”。

posted @ 2025-11-24 18:49  clnchanpin  阅读(40)  评论(0)    收藏  举报