Linux IO操作

Linux IO操作是系统编程的基础。从基本的文件读写到高级的IO模型,理解IO机制对于编写高效程序至关重要。本文将系统讲解Linux IO操作的原理和实践。

一、文件描述符

1.1 什么是文件描述符

文件描述符(File Descriptor)是一个非负整数,用于标识打开的文件、管道、套接字等。

标准文件描述符

名称 说明
0 STDIN_FILENO 标准输入
1 STDOUT_FILENO 标准输出
2 STDERR_FILENO 标准错误

1.2 文件描述符的限制

# 查看软限制
ulimit -n

# 查看系统限制
cat /proc/sys/fs/file-max

# 临时修改限制
ulimit -n 65535

二、基本IO函数

2.1 open() - 打开文件

#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

常用flags

标志 说明
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
O_CREAT 不存在则创建
O_TRUNC 截断为0
O_APPEND 追加模式
O_NONBLOCK 非阻塞

示例

#include <fcntl.h>
#include <unistd.h>

int fd = open("file.txt", O_RDWR | O_CREAT, 0644);
if (fd == -1) {
    perror("open");
    return -1;
}

2.2 read() - 读取文件

#include <unistd.h>

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

返回值

  • 成功:返回读取的字节数
  • 到达文件末尾:返回0
  • 失败:返回-1

示例

char buf[1024];
ssize_t n = read(fd, buf, sizeof(buf));
if (n == -1) {
    perror("read");
}

2.3 write() - 写入文件

#include <unistd.h>

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

示例

char *msg = "Hello World";
ssize_t n = write(fd, msg, strlen(msg));
if (n == -1) {
    perror("write");
}

2.4 close() - 关闭文件

#include <unistd.h>

int close(int fd);

2.5 完整示例

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd = open("test.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 写入
    char *msg = "Hello Linux IO!";
    write(fd, msg, strlen(msg));

    // 定位到文件开头
    lseek(fd, 0, SEEK_SET);

    // 读取
    char buf[100];
    ssize_t n = read(fd, buf, sizeof(buf) - 1);
    buf[n] = '\0';
    printf("读取到: %s\n", buf);

    close(fd);
    return 0;
}

三、文件定位

3.1 lseek() - 移动文件指针

#include <unistd.h>

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

whence参数

说明
SEEK_SET 文件开头
SEEK_CUR 当前位置
SEEK_END 文件末尾

示例

// 移动到文件开头
lseek(fd, 0, SEEK_SET);

// 移动到文件末尾
lseek(fd, 0, SEEK_END);

// 从当前位置向后移动100字节
lseek(fd, 100, SEEK_CUR);

// 获取当前偏移
off_t pos = lseek(fd, 0, SEEK_CUR);

四、文件属性操作

4.1 stat() - 获取文件信息

#include <sys/stat.h>

int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);

struct stat结构体

struct stat {
    dev_t     st_dev;     // 设备ID
    ino_t     st_ino;     // inode号
    mode_t    st_mode;    // 文件类型和权限
    nlink_t   st_nlink;   // 硬链接数
    uid_t     st_uid;     // 用户ID
    gid_t     st_gid;     // 组ID
    off_t     st_size;    // 文件大小
    time_t    st_atime;   // 访问时间
    time_t    st_mtime;   // 修改时间
    time_t    st_ctime;   // 状态改变时间
};

示例

struct stat sb;
if (stat("file.txt", &sb) == 0) {
    printf("文件大小: %ld 字节\n", sb.st_size);
    printf("inode号: %lu\n", sb.st_ino);

    if (S_ISREG(sb.st_mode)) {
        printf("这是一个普通文件\n");
    }
}

五、IO模型

5.1 阻塞IO

默认模式,操作完成才返回。

// 阻塞读取
char buf[1024];
read(fd, buf, sizeof(buf));  // 等待数据

5.2 非阻塞IO

立即返回,通过轮询检查状态。

// 设置非阻塞
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

// 非阻塞读取
while (1) {
    int n = read(fd, buf, sizeof(buf));
    if (n == -1) {
        if (errno == EAGAIN) {
            // 暂无数据,稍后重试
            usleep(1000);
            continue;
        } else {
            perror("read");
            break;
        }
    }
    // 处理数据
}

5.3 IO多路复用

使用select、poll、epoll监控多个文件描述符。

// 使用select
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);

struct timeval timeout = {5, 0};
int ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
if (ret > 0 && FD_ISSET(fd, &readfds)) {
    read(fd, buf, sizeof(buf));
}

六、缓冲机制

6.1 标准IO vs 系统IO

特性 标准IO 系统IO
缓冲
效率
移植性

6.2 使用setvbuf设置缓冲

#include <stdio.h>

// 全缓冲
setvbuf(fp, buf, _IOFBF, BUFSIZ);

// 行缓冲
setvbuf(fp, buf, _IOLBF, BUFSIZ);

// 无缓冲
setvbuf(fp, NULL, _IONBF, 0);
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐