readahead 函数详解
1. 函数介绍
readahead
是一个Linux系统调用,用于预读文件数据到内核页面缓存中。它允许应用程序提示内核提前读取指定文件区域的数据,从而提高后续读取操作的性能。这个函数特别适用于顺序访问大文件的场景,可以减少I/O等待时间。
2. 函数原型
#define _GNU_SOURCE
#include <fcntl.h>
ssize_t readahead(int fd, off64_t offset, size_t count);
3. 功能
readahead
向内核发出预读提示,建议内核提前将文件中从 offset
开始的 count
字节数据读入页面缓存。这是一个非阻塞操作,不会立即读取数据,而是让内核在适当的时候进行预读。
4. 参数
- int fd: 文件描述符,必须是已打开的文件(通常需要支持预读的文件系统)
- off64_t offset: 文件中的偏移量,指定预读开始位置
- size_t count: 预读的字节数,内核可能根据策略调整实际预读量
5. 返回值
- 成功: 返回0,表示预读请求已提交
- 失败: 返回-1,并设置errno
6. 相似函数,或关联函数
- posix_fadvise: 文件访问建议接口,包含预读建议
- mmap: 内存映射文件,可以配合MAP_POPULATE使用
- read: 基本读取函数
- lseek: 文件定位函数
7. 示例代码
示例1:基础预读示例
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
/**
* 创建大文件用于测试
*/
int create_test_file(const char *filename, size_t size) {
int fd;
char *buffer;
size_t chunk_size = 1024 * 1024; // 1MB chunks
size_t written = 0;
fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
return -1;
}
buffer = malloc(chunk_size);
if (!buffer) {
perror("分配缓冲区失败");
close(fd);
return -1;
}
// 填充测试数据
for (size_t i = 0; i < chunk_size; i++) {
buffer[i] = 'A' + (i % 26);
}
printf("正在创建 %zu MB 的测试文件...\n", size / (1024 * 1024));
while (written < size) {
size_t to_write = (size - written < chunk_size) ? size - written : chunk_size;
ssize_t result = write(fd, buffer, to_write);
if (result == -1) {
perror("写入文件失败");
free(buffer);
close(fd);
return -1;
}
written += result;
}
free(buffer);
close(fd);
printf("测试文件创建完成\n");
return 0;
}
/**
* 测量读取时间
*/
double time_read_operation(int fd, void *buffer, size_t size) {
struct timespec start, end;
ssize_t total_read = 0;
off_t offset = 0;
clock_gettime(CLOCK_MONOTONIC, &start);
while (total_read < (ssize_t)size) {
ssize_t to_read = (size - total_read < 1024 * 1024) ? size - total_read : 1024 * 1024;
ssize_t result = pread(fd, (char*)buffer + total_read, to_read, offset);
if (result == -1) {
perror("读取文件失败");
return -1;
}
if (result == 0) break; // 文件结束
total_read += result;
offset += result;
}
clock_gettime(CLOCK_MONOTONIC, &end);
return (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
}
/**
* 演示readahead的基本使用
*/
int demo_readahead_basic() {
const char *filename = "test_readahead.dat";
const size_t file_size = 50 * 1024 * 1024; // 50MB
int fd;
char *buffer;
double time_without, time_with;
printf("=== readahead 基本使用示例 ===\n");
// 创建测试文件
if (create_test_file(filename, file_size) != 0) {
return -1;
}
// 分配读取缓冲区
buffer = malloc(file_size);
if (!buffer) {
perror("分配读取缓冲区失败");
unlink(filename);
return -1;
}
// 测试不使用readahead的读取性能
fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("打开文件失败");
free(buffer);
unlink(filename);
return -1;
}
printf("第一次读取(无预读)...\n");
time_without = time_read_operation(fd, buffer, file_size);
if (time_without > 0) {
printf("无预读读取时间: %.3f 秒\n", time_without);
}
close(fd);
// 测试使用readahead的读取性能
fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("打开文件失败");
free(buffer);
unlink(filename);
return -1;
}
// 使用readahead预读整个文件
printf("执行预读操作...\n");
if (readahead(fd, 0, file_size) == 0) {
printf("预读请求提交成功\n");
} else {
printf("预读请求失败: %s\n", strerror(errno));
}
// 等待一小段时间让预读完成
sleep(1);
printf("第二次读取(有预读)...\n");
time_with = time_read_operation(fd, buffer, file_size);
if (time_with > 0) {
printf("有预读读取时间: %.3f 秒\n", time_with);
if (time_without > 0) {
printf("性能提升: %.1f%%\n",
(time_without - time_with) / time_without * 100);
}
}
close(fd);
free(buffer);
unlink(filename);
return 0;
}
int main() {
return demo_readahead_basic();
}
示例2:分段预读示例
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
/**
* 演示分段预读的使用
*/
int demo_readahead_segmented() {
const char *filename = "segmented_test.dat";
const size_t file_size = 100 * 1024 * 1024; // 100MB
const size_t segment_size = 10 * 1024 * 1024; // 10MB per segment
int fd;
char *buffer;
struct timespec start, end;
double total_time = 0;
printf("=== readahead 分段预读示例 ===\n");
// 创建测试文件
int test_fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (test_fd == -1) {
perror("创建测试文件失败");
return -1;
}
buffer = malloc(segment_size);
if (!buffer) {
perror("分配缓冲区失败");
close(test_fd);
unlink(filename);
return -1;
}
// 填充测试数据
for (size_t i = 0; i < segment_size; i++) {
buffer[i] = 'A' + (i % 26);
}
// 写入文件数据
for (size_t offset = 0; offset < file_size; offset += segment_size) {
write(test_fd, buffer, segment_size);
}
close(test_fd);
printf("创建了 %zu MB 的测试文件\n", file_size / (1024 * 1024));
// 打开文件进行测试
fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("打开文件失败");
free(buffer);
unlink(filename);
return -1;
}
printf("开始分段读取测试...\n");
// 分段预读和读取
for (size_t offset = 0; offset < file_size; offset += segment_size) {
printf("处理段 %zu/%zu MB\n",
(offset + segment_size) / (1024 * 1024),
file_size / (1024 * 1024));
// 预读当前段
clock_gettime(CLOCK_MONOTONIC, &start);
if (readahead(fd, offset, segment_size) == 0) {
// printf(" 预读段 %zu 完成\n", offset / segment_size);
} else {
printf(" 预读段 %zu 失败: %s\n", offset / segment_size, strerror(errno));
}
// 等待预读完成(实际应用中可能不需要)
usleep(100000); // 100ms
// 读取当前段
ssize_t bytes_read = pread(fd, buffer, segment_size, offset);
if (bytes_read == -1) {
perror("读取段失败");
break;
}
clock_gettime(CLOCK_MONOTONIC, &end);
double segment_time = (end.tv_sec - start.tv_sec) +
(end.tv_nsec - start.tv_nsec) / 1e9;
total_time += segment_time;
printf(" 段处理时间: %.3f 秒\n", segment_time);
}
printf("\n总处理时间: %.3f 秒\n", total_time);
printf("平均段处理时间: %.3f 秒\n", total_time / (file_size / segment_size));
close(fd);
free(buffer);
unlink(filename);
return 0;
}
int main() {
return demo_readahead_segmented();
}
示例3:与posix_fadvise对比示例
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
#include <fcntl.h>
/**
* 创建测试文件
*/
int create_large_file(const char *filename, size_t size) {
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建文件失败");
return -1;
}
char *buffer = malloc(1024 * 1024);
if (!buffer) {
perror("分配缓冲区失败");
close(fd);
return -1;
}
// 填充数据
for (int i = 0; i < 1024 * 1024; i++) {
buffer[i] = 'A' + (i % 26);
}
size_t written = 0;
while (written < size) {
size_t to_write = (size - written < 1024 * 1024) ? size - written : 1024 * 1024;
ssize_t result = write(fd, buffer, to_write);
if (result == -1) {
perror("写入文件失败");
free(buffer);
close(fd);
return -1;
}
written += result;
}
free(buffer);
close(fd);
return 0;
}
/**
* 使用readahead进行预读
*/
int test_readahead_method(const char *filename) {
int fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("打开文件失败");
return -1;
}
struct stat sb;
if (fstat(fd, &sb) == -1) {
perror("获取文件状态失败");
close(fd);
return -1;
}
// 使用readahead预读
if (readahead(fd, 0, sb.st_size) == 0) {
printf("使用readahead预读成功\n");
} else {
printf("使用readahead预读失败: %s\n", strerror(errno));
}
close(fd);
return 0;
}
/**
* 使用posix_fadvise进行预读
*/
int test_fadvise_method(const char *filename) {
int fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("打开文件失败");
return -1;
}
struct stat sb;
if (fstat(fd, &sb) == -1) {
perror("获取文件状态失败");
close(fd);
return -1;
}
// 使用posix_fadvise预读
if (posix_fadvise(fd, 0, sb.st_size, POSIX_FADV_WILLNEED) == 0) {
printf("使用posix_fadvise预读成功\n");
} else {
printf("使用posix_fadvise预读失败: %s\n", strerror(errno));
}
close(fd);
return 0;
}
/**
* 演示readahead与posix_fadvise的对比
*/
int demo_readahead_vs_fadvise() {
const char *filename = "comparison_test.dat";
const size_t file_size = 50 * 1024 * 1024; // 50MB
printf("=== readahead vs posix_fadvise 对比示例 ===\n");
// 创建测试文件
if (create_large_file(filename, file_size) != 0) {
return -1;
}
printf("创建了 %zu MB 的测试文件\n", file_size / (1024 * 1024));
printf("\n1. 测试readahead方法:\n");
test_readahead_method(filename);
printf("\n2. 测试posix_fadvise方法:\n");
test_fadvise_method(filename);
printf("\n3. 功能对比:\n");
printf(" readahead:\n");
printf(" - 专门的预读系统调用\n");
printf(" - 直接控制预读字节数\n");
printf(" - 更精确的控制\n");
printf(" posix_fadvise:\n");
printf(" - 通用的文件访问建议接口\n");
printf(" - 支持多种访问模式\n");
printf(" - 更好的可移植性\n");
unlink(filename);
return 0;
}
int main() {
return demo_readahead_vs_fadvise();
}
示例4:实际应用场景示例
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
/**
* 模拟视频播放器的预读策略
*/
typedef struct {
int fd;
off_t file_size;
off_t current_pos;
size_t buffer_size;
} video_player_t;
/**
* 初始化视频播放器
*/
int video_player_init(video_player_t *player, const char *filename) {
player->fd = open(filename, O_RDONLY);
if (player->fd == -1) {
perror("打开视频文件失败");
return -1;
}
struct stat sb;
if (fstat(player->fd, &sb) == -1) {
perror("获取文件状态失败");
close(player->fd);
return -1;
}
player->file_size = sb.st_size;
player->current_pos = 0;
player->buffer_size = 2 * 1024 * 1024; // 2MB缓冲区
printf("视频文件大小: %.2f MB\n", player->file_size / (1024.0 * 1024.0));
return 0;
}
/**
* 播放视频(模拟)
*/
int video_player_play(video_player_t *player, int use_readahead) {
char *buffer = malloc(player->buffer_size);
if (!buffer) {
perror("分配播放缓冲区失败");
return -1;
}
printf("开始播放视频%s预读...\n", use_readahead ? "(使用" : "(不使用");
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
while (player->current_pos < player->file_size) {
// 根据播放位置决定是否预读
if (use_readahead && player->current_pos + player->buffer_size < player->file_size) {
// 预读下一缓冲区的数据
off_t ahead_pos = player->current_pos + player->buffer_size;
size_t ahead_size = (player->file_size - ahead_pos > player->buffer_size) ?
player->buffer_size : player->file_size - ahead_pos;
if (readahead(player->fd, ahead_pos, ahead_size) == 0) {
// printf("预读位置 %ld, 大小 %zu\n", ahead_pos, ahead_size);
}
}
// 读取当前缓冲区数据
ssize_t bytes_read = pread(player->fd, buffer, player->buffer_size, player->current_pos);
if (bytes_read <= 0) {
if (bytes_read == -1) {
perror("读取视频数据失败");
}
break;
}
// 模拟解码和播放处理
usleep(50000); // 50ms处理时间
player->current_pos += bytes_read;
if (player->current_pos % (10 * 1024 * 1024) == 0) {
printf("已播放 %.2f MB\n", player->current_pos / (1024.0 * 1024.0));
}
}
clock_gettime(CLOCK_MONOTONIC, &end);
double play_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
printf("播放完成,总时间: %.3f 秒\n", play_time);
free(buffer);
return 0;
}
/**
* 清理视频播放器
*/
void video_player_cleanup(video_player_t *player) {
if (player->fd != -1) {
close(player->fd);
player->fd = -1;
}
}
/**
* 演示视频播放场景中的预读应用
*/
int demo_video_player_scenario() {
const char *filename = "video_sample.dat";
const size_t file_size = 100 * 1024 * 1024; // 100MB
video_player_t player_without, player_with;
printf("=== 视频播放场景中的预读应用 ===\n");
// 创建测试视频文件
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建视频文件失败");
return -1;
}
char *buffer = malloc(1024 * 1024);
if (!buffer) {
perror("分配缓冲区失败");
close(fd);
return -1;
}
// 填充随机视频数据
srand(time(NULL));
for (int i = 0; i < 1024 * 1024; i++) {
buffer[i] = rand() % 256;
}
// 写入文件
size_t written = 0;
while (written < file_size) {
size_t to_write = (file_size - written < 1024 * 1024) ?
file_size - written : 1024 * 1024;
write(fd, buffer, to_write);
written += to_write;
}
free(buffer);
close(fd);
printf("创建了 %.2f MB 的视频测试文件\n", file_size / (1024.0 * 1024.0));
// 测试不使用预读的播放
printf("\n--- 不使用预读的播放测试 ---\n");
memset(&player_without, 0, sizeof(player_without));
player_without.fd = -1;
if (video_player_init(&player_without, filename) == 0) {
video_player_play(&player_without, 0);
video_player_cleanup(&player_without);
}
// 测试使用预读的播放
printf("\n--- 使用预读的播放测试 ---\n");
memset(&player_with, 0, sizeof(player_with));
player_with.fd = -1;
if (video_player_init(&player_with, filename) == 0) {
video_player_play(&player_with, 1);
video_player_cleanup(&player_with);
}
unlink(filename);
return 0;
}
int main() {
return demo_video_player_scenario();
}
示例5:预读策略优化示例
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
/**
* 智能预读器
*/
typedef struct {
int fd;
off_t file_size;
off_t last_read_pos;
size_t read_pattern[10]; // 记录最近10次读取的大小
int pattern_index;
int pattern_count;
} smart_readaheater_t;
/**
* 初始化智能预读器
*/
int smart_readaheater_init(smart_readaheater_t *sr, const char *filename) {
sr->fd = open(filename, O_RDONLY);
if (sr->fd == -1) {
perror("打开文件失败");
return -1;
}
struct stat sb;
if (fstat(sr->fd, &sb) == -1) {
perror("获取文件状态失败");
close(sr->fd);
return -1;
}
sr->file_size = sb.st_size;
sr->last_read_pos = 0;
sr->pattern_index = 0;
sr->pattern_count = 0;
memset(sr->read_pattern, 0, sizeof(sr->read_pattern));
printf("智能预读器初始化完成\n");
printf("文件大小: %.2f MB\n", sr->file_size / (1024.0 * 1024.0));
return 0;
}
/**
* 分析读取模式
*/
size_t analyze_read_pattern(smart_readaheater_t *sr) {
if (sr->pattern_count < 3) {
return 1024 * 1024; // 默认1MB
}
// 计算平均读取大小
size_t total = 0;
int count = (sr->pattern_count < 10) ? sr->pattern_count : 10;
for (int i = 0; i < count; i++) {
total += sr->read_pattern[i];
}
return total / count;
}
/**
* 智能预读
*/
int smart_readahead(smart_readaheater_t *sr, off_t pos, size_t size) {
// 记录本次读取模式
sr->read_pattern[sr->pattern_index] = size;
sr->pattern_index = (sr->pattern_index + 1) % 10;
if (sr->pattern_count < 10) {
sr->pattern_count++;
}
// 分析读取模式
size_t predicted_size = analyze_read_pattern(sr);
// 预测下一个读取位置
off_t next_pos = pos + size;
// 如果下一个位置有效,则进行预读
if (next_pos < sr->file_size) {
size_t readahead_size = predicted_size * 2; // 预读两倍大小
if (next_pos + readahead_size > sr->file_size) {
readahead_size = sr->file_size - next_pos;
}
if (readahead(sr->fd, next_pos, readahead_size) == 0) {
printf("智能预读: 位置 %ld, 大小 %zu\n", next_pos, readahead_size);
return 0;
}
}
return -1;
}
/**
* 读取数据并触发智能预读
*/
ssize_t smart_read(smart_readaheater_t *sr, void *buf, size_t count, off_t offset) {
ssize_t bytes_read = pread(sr->fd, buf, count, offset);
if (bytes_read > 0) {
smart_readahead(sr, offset, bytes_read);
sr->last_read_pos = offset + bytes_read;
}
return bytes_read;
}
/**
* 演示智能预读策略
*/
int demo_smart_readahead() {
const char *filename = "smart_test.dat";
const size_t file_size = 50 * 1024 * 1024; // 50MB
smart_readaheater_t sr;
printf("=== 智能预读策略示例 ===\n");
// 创建测试文件
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
return -1;
}
char *buffer = malloc(1024 * 1024);
if (!buffer) {
perror("分配缓冲区失败");
close(fd);
return -1;
}
// 填充测试数据
for (size_t i = 0; i < 1024 * 1024; i++) {
buffer[i] = 'A' + (i % 26);
}
// 写入文件
size_t written = 0;
while (written < file_size) {
size_t to_write = (file_size - written < 1024 * 1024) ?
file_size - written : 1024 * 1024;
write(fd, buffer, to_write);
written += to_write;
}
free(buffer);
close(fd);
printf("创建了 %.2f MB 的测试文件\n", file_size / (1024.0 * 1024.0));
// 初始化智能预读器
if (smart_readaheater_init(&sr, filename) != 0) {
unlink(filename);
return -1;
}
// 模拟不同模式的读取
printf("\n开始智能预读测试:\n");
char *read_buffer = malloc(2 * 1024 * 1024); // 2MB缓冲区
if (!read_buffer) {
perror("分配读取缓冲区失败");
close(sr.fd);
unlink(filename);
return -1;
}
// 模拟顺序读取
printf("1. 顺序读取模式:\n");
for (off_t pos = 0; pos < 20 * 1024 * 1024; pos += 512 * 1024) {
ssize_t bytes_read = smart_read(&sr, read_buffer, 512 * 1024, pos);
if (bytes_read > 0) {
printf(" 读取位置 %ld, 大小 %zd\n", pos, bytes_read);
}
}
// 模拟随机读取
printf("\n2. 随机读取模式:\n");
srand(time(NULL));
for (int i = 0; i < 5; i++) {
off_t pos = (rand() % (int)(file_size - 1024 * 1024));
size_t size = 256 * 1024 + (rand() % (768 * 1024));
ssize_t bytes_read = smart_read(&sr, read_buffer, size, pos);
if (bytes_read > 0) {
printf(" 随机读取位置 %ld, 大小 %zd\n", pos, bytes_read);
}
}
free(read_buffer);
close(sr.fd);
unlink(filename);
printf("\n智能预读策略特点:\n");
printf(" - 学习读取模式\n");
printf(" - 动态调整预读大小\n");
printf(" - 适应不同的访问模式\n");
return 0;
}
int main() {
return demo_smart_readahead();
}
readahead 使用注意事项
适用场景:
- 大文件顺序访问: 读取大型文件时特别有效
- 可预测的访问模式: 顺序读取或规律性访问
- I/O密集型应用: 数据库、媒体播放器、文件传输工具
不适用场景:
- 随机访问: 频繁随机访问的文件不适合预读
- 小文件: 文件很小时预读开销大于收益
- 内存紧张: 系统内存不足时预读可能降低性能
性能考虑:
- 预读大小: 需要根据具体场景调整预读大小
- 时机选择: 合适的预读时机很重要
- 系统负载: 高负载时谨慎使用预读
错误处理:
- 检查返回值: readahead失败时不会影响正常读取
- 权限检查: 确保有足够的权限访问文件
- 文件状态: 文件必须是打开状态且支持预读
总结
readahead
是一个强大的预读工具,能够显著提高顺序访问大文件的性能。通过合理的预读策略,可以减少I/O等待时间,提高应用程序的响应速度。在实际应用中,需要根据具体的访问模式和系统环境来设计合适的预读策略。