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/O
  • RWF_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;  // 1MB
    new_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);  // 1MB
    for (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 应用程序提供了强大而灵活的文件操作能力,正确使用它们可以显著提高程序的性能和可靠性。

此条目发表在未分类分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注