Skip to content

文件IO基础 ​

文件 I/O 指的是对文件的输入/输出操作,说白了就是对文件的读写操作; Linux 下一切皆文件,文件作为 Linux 系统设计思想的核心理念,在 Linux 系统下显得尤为重要,所以对文件的 I/O 操作既是基础也是最重要的部分。

一个简单的文件 IO示例 ​

一个通用的 IO 模型通常包括打开文件、读写文件、关闭文件这些基本操作, 主要涉及到 4 个函数: open()、 read()、 write()以及 close()

c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
    char buff[1024];
    int fd1, fd2;
    int ret;

    /* 打开源文件 src_file(只读方式) */
    fd1 = open("./src_file", O_RDONLY);
    if (-1 == fd1)
        return fd1;

    /* 打开目标文件 dest_file(只写方式) */
    fd2 = open("./dest_file", O_WRONLY);
    if (-1 == fd2) {
        ret = fd2;
        goto out1;
    }

    /* 读取源文件 1KB 数据到 buff 中 */
    ret = read(fd1, buff, sizeof(buff));
    if (-1 == ret)
        goto out2;

    /* 将 buff 中的数据写入目标文件 */
    ret = write(fd2, buff, sizeof(buff));
    if (-1 == ret)
        goto out2;

    ret = 0;

out2:
    /* 关闭目标文件 */
    close(fd2);

out1:
    /* 关闭源文件 */
    close(fd1);

    return ret;
}

从源文件 src_file 中读取 1KB 数据,然后将其写入到目标文件 dest_file 中,在进行读写操作之前,首先调用 open 函数将源文件和目标文件打开,成功打开之后再调用 read 函数从源文件中读取 1KB 数据,然后再调用 write 函数将这 1KB 数据写入到目标文件中

文件描述符 ​

调用 open 函数会有一个返回值, 譬如示例代码 2.1.1 中的 fd1 和 fd2, 这是一个 int 类型的数据,在 open函数执行成功的情况下, 会返回一个非负整数, 该返回值就是一个文件描述符(file descriptor) , 这说明文件描述符是一个非负整数

当调用 open 函数打开一个现有文件或创建一个新文件时,内核会向进程返回一个文件描述符, 用于指代被打开的文件,所有执行 IO 操作的系统调用都是通过文件描述符来索引到对应的文件

在 Linux 系统下,我们可以通过 ulimit 命令来查看进程可打开的最大文件数,用法如下所示:

bash
ulimit -n

该最大值默认情况下是 1024,也就意味着一个进程最多可以打开 1024 个文件,当然这个限制数其实是可以设置的,

open 打开文件 ​

在 Linux 系统中要操作一个文件,需要先打开该文件,得到文件描述符,然后再对文件进行相应的读写操作(或其他操作),最后在关闭该文件; open 函数用于打开文件,当然除了打开已经存在的文件之外,还可以创建一个新的文件

c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

在 Linux 系统下,可以通过 man 命令(也叫 man 手册) 来查看某一个 Linux 系统调用的帮助信息, man命令可以将该系统调用的详细信息显示出来

bash
man 2 open #查看 open 函数的帮助信息

在应用程序中使用 open 函数时,需要包含 3 个头文件“#include<sys/types.h>”、“#include <sys/stat.h>”、“#include <fcntl.h>”。 函数参数和返回值含义如下:

- pathname: 字符串类型,用于标识需要打开或创建的文件,可以包含路径
- flags: 调用 open 函数时需要提供的标志, 包括文件访问模式标志以及其它文件相关标志

open 函数使用示例 ​

使用 open 函数打开一个已经存在的文件(例如当前目录下的 app.c 文件),使用只读方式打开:

c
int fd = open("./app.c", O_RDONLY)
if (-1 == fd)
  return fd;

使用 open 函数打开一个已经存在的文件(例如当前目录下的 app.c 文件),使用可读可写方式打开:

c
int fd = open("./app.c", O_RDWR)
if (-1 == fd)
  return fd;

使用 open 函数打开一个指定的文件(譬如/home/dengtao/hello) , 使用可读可写方式,如果该文件是一个符号链接文件,则不对其进行解引用,直接返回错误:

c
int fd = open("/home/dengtao/hello", O_RDWR | O_NOFOLLOW);
if (-1 == fd)
  return fd;

将文件权限设置如下:文件所属者拥有读、写、执行权限;同组用户与其他用户只有读权限。使用可读可写方式打开:

c
int fd = open("/home/dengtao/hello", O_RDWR | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH);
if (-1 == fd)
  return fd;

write 写文件 ​

调用 write 函数可向打开的文件写入数据,其函数原型如下所示

c
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

函数参数和返回值含义如下:

- fd: 文件描述符。与 write 函数的 fd 参数意义相同。
- buf: 指定用于存储读取数据的缓冲区。
- count: 指定需要读取的字节数。

close 关闭文件 ​

可调用 close 函数关闭一个已经打开的文件,其函数原型如下

c
#include <unistd.h>
int close(int fd);

数参数和返回值含义如下:

- fd: 文件描述符,需要关闭的文件所对应的文件描述符。
- 返回值: 如果成功返回 0,如果失败则返回-1。

显式关闭不再需要的文件描述符往往是良好的编程习惯,会使代码在后续修改时更具有可读性,也更可靠,进而言之,文件描述符是有限资源,当不再需要时必须将其释放、归还于系统。

lseek ​

对于每个打开的文件, 系统都会记录它的读写位置偏移量,我们也把这个读写位置偏移量称为读写偏移量

读写偏移量用于指示 read()或 write()函数操作时文件的起始位置,会以相对于文件头部的位置偏移量来表示,文件第一个字节数据的位置偏移量为 0。

c
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence)

函数参数和返回值含义如下:

- fd: 文件描述符。
- offset: 偏移量,以字节为单位。
- whence: 用于定义参数 offset 偏移量对应的参考值, 该参数为下列其中一种(宏定义)

使用示例: ​

将读写位置移动到文件开头处:

c
off_t off = lseek(fd, 0, SEEK_SET);
if (-1 == off)
  return -1;

将读写位置移动到文件末尾:

c
off_t off = lseek(fd, 0, SEEK_END);
if (-1 == off)
  return -1;

将读写位置移动到偏移文件开头 100 个字节处:

c
off_t off = lseek(fd, 100, SEEK_SET);
if (-1 == off)
  return -1;

获取当前读写位置偏移量:

c
off_t off = lseek(fd, 0, SEEK_CUR);
if (-1 == off)
  return -1;

函数执行成功将返回文件当前读写位置