Linux 应用编程中最基础的知识:文件 I/O(Input、Outout)。
文件 I/O 指的是对文件的输入/输出操作,就是对文件的读写操作;Linux 下一切皆文件,文件作为 Linux 系统设计思想的核心理念,在 Linux 系统下显得尤为重要,所以对文件的 I/O 操作既是基础也是最重要的部分。
一个通用的 IO 模型通常包括打开文件、读写文件、关闭文件这些基本操作,主要涉及到 4 个函数:open()、read()、write()以及 close()。
文件IO
tips:我们在 Linux 系统下,可以通过 man 命令查看某函数的用法和帮助信息以及头文件引用信息。man 命令后面跟着两个参数,数字 1 表示查看 Linux 命令,数字 2 表示查看系统调用函数,数字 3表示查看标准 C 库函数,最后一个参数表示需要查看的系统调用函数名。举例:
man 2 open //查看 open 函数的帮助信息
打开文件
#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 内核提供的标准文件 IO 中,主要以文件描述符fd作为操作句柄,去操作文件。调用open函数打开文件后会获得一个文件描述符fd,fd是一个int 类型的数据。一个进程可以打开多个文件,但是在 Linux 系统中,一个进程可以打开的文件数是有限制,我们可以通过 ulimit -n 命令来查看进程可打开的最大文件数,可以自己设置最大值。该最大值默认情况下是 1024。
所以对于一个进程来说,文件描述符是一种有限资源,文件描述符是从 0 开始分配的,譬如说进程中第一个被打开的文件对应的文件描述符是 0、第二个文件是 1、第三个文件是 2、第 4 个文件是 3……以此类推,所以由此可知,文件描述符数字最大值为 1023(0~1023)。每一个被打开的文件在同一个进程中都有一个唯一的文件描述符,不会重复,如果文件被关闭后,它对应的文件描述符将会被释放,那么这个文件描述符将可以再次分配给其它打开的文件、与对应的文件绑定起来。
在程序中,调用 open 函数打开文件的时候,分配的文件描述符一般都是从 3 开始,因为0、1、2 这三个文件描述符已经默认被系统占用了,分别分配给了系统标注输入(0)、标注输出(1)以及标准错误(2)。
写文件
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
读文件
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
关闭文件
#include <unistd.h>
int close(int fd);
标准IO
不仅是 Linux,很多其它的操作系统都实现了标准 I/O 库。标准 I/O 虽然是对文件 I/O 进行了封装,但事实上并不仅仅只是如此,标准 I/O 会处理很多细节,譬如分配 stdio 缓冲区、以优化的块长度执行 I/O 等,这些处理使用户不必担心如何选择使用正确的块长度。
标准 I/O 库是标准 C 库中用于文件 I/O 操作(譬如读文件、写文件等)相关的一系列库函数的集合,通常标准 I/O 库函数相关的函数定义都在头文件<stdio.h>中,所以我们需要在程序源码中包含<stdio.h>头文件。
标准 I/O 库函数是构建于文件 I/O 这些系统调用之上的,譬如标准 I/O 库函数 fopen()就利用系统调用 open()来执行打开文件的操作、fread()利用系统调用 read()来执行读文件操作、fwrite()则利用系统调用 write()来执行写文件操作等等。
打开文件
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
读写文件
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
标准 I/O 和文件 I/O 的区别如下:
⚫ 虽然标准 I/O 和文件 I/O 都是 C 语言函数,但是标准 I/O 是标准 C 库函数,而文件 I/O 则是 Linux 系统调用;
⚫ 标准 I/O 是由文件 I/O 封装而来,标准 I/O 内部实际上是调用文件 I/O 来完成实际操作的;
⚫ 可移植性:标准 I/O 相比于文件 I/O 具有更好的可移植性,通常对于不同的操作系统,其内核向应用层提供的系统调用往往都是不同,譬如系统调用的定义、功能、参数列表、返回值等往往都是不一样的;而对于标准 I/O 来说,由于很多操作系统都实现了标准 I/O 库,标准 I/O 库在不同的操作系统之间其接口定义几乎是一样的,所以标准 I/O 在不同操作系统之间相比于文件 I/O 具有更好的可移植性。
⚫ 性能、效率:标准 I/O 库在用户空间维护了自己的 stdio 缓冲区,所以标准 I/O 是带有缓存的,而文件 I/O 在用户空间是不带有缓存的,所以在性能、效率上,标准 I/O 要优于文件 I/O。
所有文件 I/O 函数都是围绕文件描述符进行的,调用 open()函数打开一个文件时,即返回一个文件描述符 fd,然后该文件描述符就用于后续的 I/O 操作。对于标准 I/O 库函数来说,它们的操作是围绕 FILE 指针进行的,当使用标准 I/O 库函数打开或创建一个文件时,会返回一个指向 FILE 类型对象的指针(FILE *),使用该 FILE 指针与被打开或创建的文件相关联,然后该 FILE 指针就用于后续的标准 I/O 操作。
·················· END ··················
点击关注公众号