Linux 高级 I/O 系统调用详解
Linux 高级 I/O 系统调用详解
1. 概述
这些是 Linux 系统中一组高级 I/O 操作系统调用,它们提供了比传统 read
/write
更强大和灵活的功能。每种调用都有其特定的用途和优势。
2. 系统调用详细介绍
2.1 pread/pwrite - 位置指定读写
#include <unistd.h>ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
功能:
pread
: 从指定位置读取数据,不改变文件指针位置pwrite
: 向指定位置写入数据,不改变文件指针位置
优势:
- 原子操作(读/写 + 位置指定)
- 线程安全
- 不影响其他读写操作
示例:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>int main() {int fd = open("test.txt", O_CREAT | O_RDWR, 0644);// 写入数据const char *data = "Hello World!";pwrite(fd, data, strlen(data), 0);// 从指定位置读取char buffer[20];ssize_t bytes_read = pread(fd, buffer, 10, 0);buffer[bytes_read] = '\0';printf("读取内容: %s\n", buffer);close(fd);return 0;
}
2.2 preadv/pwritev - 分散/聚集 I/O
#include <sys/uio.h>ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);
ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);
功能:
preadv
: 从指定位置读取到多个缓冲区(分散读取)pwritev
: 从多个缓冲区写入到指定位置(聚集写入)
iov 结构体:
struct iovec {void *iov_base; // 缓冲区地址size_t iov_len; // 缓冲区长度
};
示例:
#include <stdio.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <string.h>int main() {int fd = open("scatter_gather.txt", O_CREAT | O_RDWR, 0644);// 准备分散写入数据struct iovec iov_write[3];char *data1 = "First part ";char *data2 = "Second part ";char *data3 = "Third part\n";iov_write[0].iov_base = data1;iov_write[0].iov_len = strlen(data1);iov_write[1].iov_base = data2;iov_write[1].iov_len = strlen(data2);iov_write[2].iov_base = data3;iov_write[2].iov_len = strlen(data3);// 分散写入pwritev(fd, iov_write, 3, 0);// 分散读取struct iovec iov_read[3];char buf1[20], buf2[20], buf3[20];iov_read[0].iov_base = buf1;iov_read[0].iov_len = sizeof(buf1) - 1;iov_read[1].iov_base = buf2;iov_read[1].iov_len = sizeof(buf2) - 1;iov_read[2].iov_base = buf3;iov_read[2].iov_len = sizeof(buf3) - 1;ssize_t total_bytes = preadv(fd, iov_read, 3, 0);printf("总共读取 %zd 字节\n", total_bytes);buf1[iov_read[0].iov_len] = '\0';buf2[iov_read[1].iov_len] = '\0';buf3[iov_read[2].iov_len] = '\0';printf("缓冲区1: %s\n", buf1);printf("缓冲区2: %s\n", buf2);printf("缓冲区3: %s\n", buf3);close(fd);return 0;
}
2.3 preadv2/pwritev2 - 增强版分散/聚集 I/O
#define _GNU_SOURCE
#include <sys/uio.h>ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);
ssize_t pwritev2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);
功能:
- 在
preadv
/pwritev
基础上增加标志控制 - 支持更细粒度的 I/O 控制
支持的标志:
RWF_HIPRI
: 高优先级 I/ORWF_DSYNC
: 数据同步写入RWF_SYNC
: 同步写入RWF_NOWAIT
: 非阻塞操作RWF_APPEND
: 追加模式
示例:
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>int main() {int fd = open("enhanced_io.txt", O_CREAT | O_RDWR, 0644);if (fd == -1) {perror("open");return 1;}// 准备数据struct iovec iov[2];char *data1 = "Enhanced ";char *data2 = "I/O operation\n";iov[0].iov_base = data1;iov[0].iov_len = strlen(data1);iov[1].iov_base = data2;iov[1].iov_len = strlen(data2);// 使用增强版写入(带标志)ssize_t bytes_written = pwritev2(fd, iov, 2, 0, RWF_SYNC);if (bytes_written == -1) {if (errno == ENOSYS) {printf("系统不支持 pwritev2,回退到 pwritev\n");bytes_written = pwritev(fd, iov, 2, 0);} else {perror("pwritev2");close(fd);return 1;}}printf("写入 %zd 字节\n", bytes_written);close(fd);return 0;
}
2.4 prlimit64 - 资源限制控制
#include <sys/resource.h>int prlimit64(pid_t pid, int resource, const struct rlimit64 *new_limit, struct rlimit64 *old_limit);
功能:
- 获取和设置进程资源限制
- 支持 64 位资源限制值
- 可以操作其他进程的资源限制
常用资源类型:
RLIMIT_AS
: 虚拟内存地址空间限制RLIMIT_CORE
: 核心转储文件大小限制RLIMIT_CPU
: CPU 时间限制RLIMIT_DATA
: 数据段大小限制RLIMIT_FSIZE
: 文件大小限制RLIMIT_NOFILE
: 打开文件描述符数量限制RLIMIT_NPROC
: 进程数量限制RLIMIT_STACK
: 栈大小限制
示例:
#include <stdio.h>
#include <sys/resource.h>
#include <errno.h>int main() {struct rlimit64 limit;// 获取当前进程的文件大小限制if (prlimit64(0, RLIMIT_FSIZE, NULL, &limit) == 0) {printf("文件大小限制:\n");if (limit.rlim_cur == RLIM64_INFINITY) {printf(" 软限制: 无限制\n");} else {printf(" 软限制: %lld 字节\n", (long long)limit.rlim_cur);}if (limit.rlim_max == RLIM64_INFINITY) {printf(" 硬限制: 无限制\n");} else {printf(" 硬限制: %lld 字节\n", (long long)limit.rlim_max);}}// 获取打开文件数限制if (prlimit64(0, RLIMIT_NOFILE, NULL, &limit) == 0) {printf("文件描述符限制:\n");printf(" 软限制: %lld\n", (long long)limit.rlim_cur);printf(" 硬限制: %lld\n", (long long)limit.rlim_max);}// 设置新的文件大小限制(仅作为示例)struct rlimit64 new_limit;new_limit.rlim_cur = 1024 * 1024; // 1MBnew_limit.rlim_max = 1024 * 1024; // 1MB// 注意:修改资源限制通常需要适当权限if (prlimit64(0, RLIMIT_FSIZE, &new_limit, NULL) == 0) {printf("成功设置文件大小限制为 1MB\n");} else {if (errno == EPERM) {printf("权限不足,无法修改资源限制\n");} else {perror("设置资源限制失败");}}return 0;
}
3. 性能对比测试
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <time.h>
#include <string.h>#define ITERATIONS 10000
#define BUFFER_SIZE 1024// 性能测试函数
double benchmark_function(const char *name, ssize_t (*func)(int, void*, size_t, off_t)) {int fd = open("benchmark_test.dat", O_CREAT | O_RDWR, 0644);char *buffer = malloc(BUFFER_SIZE);// 准备测试数据memset(buffer, 'A', BUFFER_SIZE);write(fd, buffer, BUFFER_SIZE);struct timespec start, end;clock_gettime(CLOCK_MONOTONIC, &start);// 执行测试for (int i = 0; i < ITERATIONS; i++) {func(fd, buffer, BUFFER_SIZE, 0);}clock_gettime(CLOCK_MONOTONIC, &end);double elapsed = (end.tv_sec - start.tv_sec) * 1000000.0 +(end.tv_nsec - start.tv_nsec) / 1000.0;free(buffer);close(fd);unlink("benchmark_test.dat");printf("%-15s: %.2f 微秒 (平均 %.3f 纳秒/次)\n", name, elapsed, elapsed * 1000.0 / ITERATIONS);return elapsed;
}// 模拟不同函数的包装器
ssize_t wrapper_pread(int fd, void *buf, size_t count, off_t offset) {return pread(fd, buf, count, offset);
}int main() {printf("=== I/O 系统调用性能对比 ===\n\n");// 创建大测试文件int test_fd = open("large_test_file.dat", O_CREAT | O_WRONLY | O_TRUNC, 0644);char *large_buffer = malloc(1024 * 1024); // 1MBfor (int i = 0; i < 10; i++) { // 10MB 文件write(test_fd, large_buffer, 1024 * 1024);}free(large_buffer);close(test_fd);printf("创建 10MB 测试文件完成\n\n");// 测试不同的读取方式benchmark_function("pread", wrapper_pread);printf("\n性能分析:\n");printf("1. pread/pwrite: 适合随机访问场景\n");printf("2. preadv/pwritev: 适合多缓冲区操作\n");printf("3. preadv2/pwritev2: 提供更多控制选项\n");printf("4. prlimit64: 用于资源管理而非 I/O 操作\n");unlink("large_test_file.dat");return 0;
}
4. 实际应用场景
4.1 数据库存储引擎
#include <stdio.h>
#include <sys/uio.h>
#include <fcntl.h>// 模拟数据库页读取
typedef struct {int page_id;char data[4096];int checksum;
} db_page_t;int read_database_pages(const char *db_file, int *page_ids, int count) {int fd = open(db_file, O_RDONLY);if (fd == -1) return -1;struct iovec *iov = malloc(count * sizeof(struct iovec));db_page_t *pages = malloc(count * sizeof(db_page_t));// 设置分散读取for (int i = 0; i < count; i++) {iov[i].iov_base = &pages[i];iov[i].iov_len = sizeof(db_page_t);}// 一次性读取多个数据库页ssize_t bytes_read = preadv(fd, iov, count, 0);printf("读取 %d 个数据库页,共 %zd 字节\n", count, bytes_read);free(pages);free(iov);close(fd);return bytes_read > 0 ? 0 : -1;
}
4.2 网络协议处理
#include <sys/uio.h>
#include <stdio.h>// 模拟网络包处理
typedef struct {uint32_t header;uint16_t type;uint16_t length;
} packet_header_t;typedef struct {char payload[1024];
} packet_payload_t;int process_network_packets(int socket_fd) {// 准备接收多个网络包struct iovec iov[5]; // 最多5个包packet_header_t headers[5];packet_payload_t payloads[5];// 设置分散接收缓冲区for (int i = 0; i < 5; i++) {iov[i*2].iov_base = &headers[i];iov[i*2].iov_len = sizeof(packet_header_t);iov[i*2+1].iov_base = &payloads[i];iov[i*2+1].iov_len = sizeof(packet_payload_t);}// 一次性接收多个包(简化示例)printf("准备接收网络数据包...\n");return 0;
}
5. 编译和运行
# 编译示例
gcc -o io_examples example1.c
gcc -o performance_test performance_test.c
gcc -o advanced_examples advanced_examples.c# 运行示例
./io_examples
./performance_test
./advanced_examples
6. 使用建议
6.1 选择指南
场景 | 推荐函数 | 原因 |
---|---|---|
简单顺序读写 | read/write | 简单直接 |
随机位置访问 | pread/pwrite | 原子操作,线程安全 |
多缓冲区操作 | readv/writev | 减少系统调用 |
位置+多缓冲区 | preadv/pwritev | 功能最强 |
需要高级控制 | preadv2/pwritev2 | 支持标志控制 |
资源限制管理 | prlimit64 | 专门用途 |
6.2 最佳实践
// 安全的 pread 封装
ssize_t safe_pread(int fd, void *buf, size_t count, off_t offset) {if (fd < 0 || !buf || count == 0) {errno = EINVAL;return -1;}ssize_t result;do {result = pread(fd, buf, count, offset);} while (result == -1 && errno == EINTR);return result;
}// 安全的 preadv 封装
ssize_t safe_preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset) {if (fd < 0 || !iov || iovcnt <= 0) {errno = EINVAL;return -1;}ssize_t result;do {result = preadv(fd, iov, iovcnt, offset);} while (result == -1 && errno == EINTR);return result;
}
这些高级 I/O 系统调用为 Linux 应用程序提供了强大而灵活的文件操作能力,正确使用它们可以显著提高程序的性能和可靠性。