文件 I/O
注意:文章中使用到的源码都在我的 github 中找到,传送门:SourceCode
前言
- 本章主要讲解了文件 I/O 操作的一些函数–打开文件、读文件、写文件等等。讲解了大多数 Unix 文件 I/O 操作主要需要五个函数:open、read、write、lseek 以及 close。而这些函数都是
不带缓冲 I/O (Unbuffer I/O)
,不带缓冲指的是每个 read 或者 write 都会调用一个内核中的系统调用。其次讲解了多进程间的数据共享和原子操作。- 不带缓冲 I/O 操作与带有缓冲 I/O 操作的区别:
- 内核存储缓冲区:以 write 与 fwrite 为例,前者为不带缓冲 I/O ,而后者则带有缓冲。首先要明确一个概念就是当我们向文件写入数据时无论调用这两个哪个接口最终都会将数据写到 Unix 系统在内核中设定的一个缓冲存储器,只有到这个缓冲存储器满了或者内核需要对其刷新时,才会真正的把数据排入输出队列写到文件中去。
- 假设内核缓存时 100 个字节,调用 write 每次写 10 个字节,当写第 10 次时,才会导致内核缓存区满从而将数据写到硬盘中,而在这个过程中相当于足足调用了 9 次系统调用却没有达到实质的效果,反而增加了用户态与内核态切换的开销。而带有缓冲 I/O 操作的 fwrite 会维护一个处于用户态的缓冲区,它会优化调用系统接口的频率,减少多余开销。
- 不带缓冲 I/O 操作与带有缓冲 I/O 操作的区别:
文件描述符
- 文件表述符主要通过 open() 或者 creat() 函数返回。注意这个 creat 没有 e,主要还是 Unix 历史原因,感兴趣的可以去百度下。
对于内核来说,想要使用 read、write 等对文件的操作必须通过文件描述符,它是一个非负整数,每个程序都有一个自己的文件描述符表,从 0 开始每次 +1,一般每个进程对会有 shell 默认的 0,1,2 三个文件描述符,分别是标准输入、标准输出、标准错误输出。当进程再通过 open 函数打开文件返回的文件描述符就是 3。
一般魔数 0、1、2 都应该使用宏定义 STDIN_FILENO
、STDOUT_FILENO
和 STDERR_FILENO
。这些宏定义在头文件 <unistd.h> 中。
/* Standard file descriptors. */
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */
- 有一个问题,当进程同时打开同一个文件两次会发生什么?
- 答案就是你会得到两个文件描述符,因此在使用完成时要注意分别对两个文件描述符 close(),更有意思的是,这两个文件描述符的 seek 都是不同的,详情可以参考这篇BLOG,有一点需要注意的是,当你在父进程打开一个文件后,fork 出来的子进程由于是将父进程所有资源都复制的所以也包含这个文件描述符,注意关闭。
open 函数
#include <fcntl.h>
int open(const char *pathname,int flags,.../*, mode_tmode*/) ;
int openat(int dirfd, const char *pathname, int flags,.../*, mode_tmode*/);
//返回:若成功为文件描述符,若出错为- 1
openat 函数的目的是加强路径的选择,可以使用相对路径来进行操作。
example_openat
首先通过 open 上一级目录文件,然后将目录文件描述符传递给 openat 并创建一个 stderr.out 文件写入数据。同时再用 openat 再次打开文件观察文件描述符增长规律。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
int main()
{
char* dir_path = "./..";
char* relative_path = "stderr.out";
char buf[20] = {
"0"};
int dir_fd;
int fd;
int flags;
int fd1;
mode_t mode;
dir_fd = open(dir_path, O_RDONLY);
if (dir_fd < 0)
{
perror("open");
exit(EXIT_FAILURE);
}
flags = O_CREAT | O_RDWR;
mode = 0777;
fd = openat(dir_fd, relative_path, flags, mode);
if (fd < 0)
{
perror("openat");
exit(EXIT_FAILURE);
}
if (write(fd, "HELLO", 5