目录

1.什么是文件偏移量?

1.1 文件偏移量介绍

1.2 文件偏移量重点

1.3 文件偏移量工作原理

2.文件偏移量设置

2.1 lseek函数

2.2 lseek内核源码分析

3.写文件

3.1 write函数

3.2 write内核源码分析

4.读文件

4.1 read函数

4.2 read内核源码分析

5.文件读写,文件偏移量设置示例代码


1.什么是文件偏移量?

1.1 文件偏移量介绍

在介绍文件偏移量之前,先来喊一个口号:只有真正理解了文件偏移量,你才会懂得文件读写。

文件偏移量是指文件中当前读取或写入位置的指示器。

在Linux中,每个打开的文件都有一个文件偏移量,用于记录下一次读取或写入操作将在文件中发生的位置。文件偏移量是一个以字节为单位的整数值,从文件开头开始计算。

当执行读取或写入操作时,文件偏移量会随之改变。

  • 读取操作会从文件偏移量所指示的位置开始读取数据,并将文件偏移量向后移动到读取操作结束后的位置。
  • 写入操作则会从文件偏移量所指示的位置开始写入数据,并将文件偏移量向后移动到写入操作结束后的位置。
  • 通过改变文件偏移量,可以在文件中定位到特定的位置进行读取或写入操作。

1.2 文件偏移量重点

关于文件偏移量我们需要注意以下几点,只有充分掌握了以下几点才能进行正确的文件读写:

  • 1.文件偏移量对应的是struct file对象的f_pos成员,这个成员由write,read,lseek函数共享,也就是说三个函数都会改变f_pos值。
  • 2.open函数如果设置O_APPEND标识,会改变write函数使用f_pos的行为,具体可以参考write内核源码分析。

1.3 文件偏移量工作原理

(1)正常情况下文件偏移量工作原理

文件IO_文件读写(附Linux-5.15.10内核源码分析)-LMLPHP

 图 1-1 正常情况下文件偏移量工作原理

(2)设置O_APPEND情况下文件偏移工作原理

文件IO_文件读写(附Linux-5.15.10内核源码分析)-LMLPHP

 图 1-2 设置O_APPEND情况下文件偏移工作原理

2.文件偏移量设置

2.1 lseek函数

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

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

函数简介:lseek函数是Linux系统中的一个文件操作函数,用于改变文件读写指针的位置。它可以在文件中任意移动读写指针,实现对文件的随机访问。

函数参数:

fd:文件描述符,指定要进行操作的文件。

offset:偏移量,指定要移动的字节数。正值表示向文件末尾方向移动,负值表示向文件起始位置方向移动。

whence:起始位置,指定了偏移量的参考位置。它可以取以下三个值:

  • SEEK_SET:从文件起始位置开始计算偏移量。
  • SEEK_CUR:从当前读写指针位置开始计算偏移量。
  • SEEK_END:从文件末尾位置开始计算偏移量。

lseek参数解析:

文件IO_文件读写(附Linux-5.15.10内核源码分析)-LMLPHP

图 2-1 lseek参数解析

函数返回值:

成功:返回新的读写指针位置。

失败:返回-1,并设置errno。

2.2 lseek内核源码分析

文件IO_文件读写(附Linux-5.15.10内核源码分析)-LMLPHP

 图 2-2 lseek内核源码分析

lseek内核源码主要流程如图 2-2,lseek函数主要工作为更新struct file对象成员f_ops,Linux一切皆文件,不同的文件类型对应的lseek函数具体实现会不一样。

lseek,write,read调用完主要流程都要执行f.file->f_pos = pos更新f_pos的值。

3.写文件

3.1 write函数

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

函数简介:用于将数据写入文件描述符或设备。

函数参数:

fd:文件描述符,要写入的文件或设备的标符。通常使用open函数打开文件或设备后返回的文件描述符作为参数。

buf:要写入数据的缓冲区的指针,数据将该缓冲区写入到文件描述符指定的位置。

count:要写入的字节数,即从缓冲区中写入的数据长度。

函数返回值:

成功:返回实际写入的字节数。

失败:返回-1,并设置errno。

3.2 write内核源码分析

文件IO_文件读写(附Linux-5.15.10内核源码分析)-LMLPHP

 图 3-1 write函数内核源码分析

write函数内核源码主要流程如图 3-1,write函数主要工作为将数据写入文件并更新更新struct file对象成员f_ops,不同的文件类型对应的write函数实现会不一样,需要具体情况具体分析。

write函数有一个重点就是当open函数设置O_APPEND标识后,write每次写数据都是从队尾开始,这个特性的实现是write不会用struct file对象成员f_pos指定的位置开始写文件,而是重新计算pos(设置为文件实际大小),使用pos指定的位置开始写文件,根据内核源码,我们就能很清楚理解O_APPEND标识工作原理。

4.读文件

4.1 read函数

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

函数简介:用于从文件描述符中读取数据。

函数参数:

fd:文件描述符,要读取的文件或设备的标识符。通常使用open函数打开文件或设备后返回的文件描述符作为参数。

buf:存放读取数据的缓冲区的指针。数据将从文件描述符指定的位置读取到该缓冲区。

count:要读取的字节数,即从文件中读取的数据长度。

函数返回值:

成功:返回实际读取的字节数。

失败:返回-1,并设置errno。

4.2 read内核源码分析

文件IO_文件读写(附Linux-5.15.10内核源码分析)-LMLPHP

 图 4-1 read函数内核源码分析

read函数内核源码主要流程如图 4-1,read函数主要工作为从文件读取并更新更新struct file对象成员f_ops,不同的文件类型对应的read函数实现会不一样,需要具体情况具体分析。

5.文件读写,文件偏移量设置示例代码

本示例模拟图 1-1和图 1-2 流程

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

#define TEST_FILE "/tmp/test.txt"
#define BUF_SIZE (256)
#define READ_BUF_SIZE (2048)

void print_pos(int fd) {
    int pos = lseek(fd, 0, SEEK_CUR);
    printf("cur pos:%d\n", pos);
}

int write_len_data(int fd, unsigned char len, char ch) {
    unsigned char sbuf[BUF_SIZE] = {0};
    for (unsigned char i = 0; i < len; i++) {
        sbuf[i] = ch;
    }

    int ret = write(fd, sbuf, len);
    if (ret == -1) {
        perror("write error");
        return -1;
    }
    return 0;
}

int read_len_data(int fd, unsigned int len) {
    if (len > READ_BUF_SIZE) return -1;
    char rbuf[READ_BUF_SIZE] = {0};
    return read(fd, rbuf, len);
}


int fpos_test(bool append) {
    int flags = 0;
    if (append) {
        flags = O_RDWR | O_CREAT | O_TRUNC | O_APPEND;
    } else {
        flags = O_RDWR | O_CREAT | O_TRUNC;
    }
    int fd = open(TEST_FILE, flags, 0777);
    if (fd == -1) {
        perror("open error");
        return -1;
    }

    write_len_data(fd, 100, 'a');
    print_pos(fd);
    lseek(fd, 10, SEEK_SET);
    read_len_data(fd, 40);
    print_pos(fd);
    write_len_data(fd, 20, 'b');
    print_pos(fd);

    close(fd);
    return 0;
}

int main(int argc, char *argv[]) {
    fpos_test(false);
    return 0;
}
07-15 17:50