目录
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)正常情况下文件偏移量工作原理
图 1-1 正常情况下文件偏移量工作原理
(2)设置O_APPEND情况下文件偏移工作原理
图 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参数解析:
图 2-1 lseek参数解析
函数返回值:
成功:返回新的读写指针位置。
失败:返回-1,并设置errno。
2.2 lseek内核源码分析
图 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内核源码分析
图 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内核源码分析
图 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;
}