简单了解linux 文件I/O
作者:
| 更新日期:之前一直没系统的阅读linux书籍,过年回家看了几章UNIX环境高级编程,发现挺好的,现在记录一下文件I/O 相关知识点。
本文首发于公众号:天空的代码世界,微信号:tiankonguse
背景
常见的文件IO函数时 open, create, close, read, lseek, write 这六个函数,大家都经常见到的。
但是提起 dup, dup2, fcntl, ioctl 这些函数, 很多人就不认识了。
这里就重点介绍一下这些不认识的函数吧, 对于常见的函数只是简单的提一下吧。
基础知识
对于内核而言,所有打开的文件都是通过文件描述符引用。
当打开或者创建一个文件的时候,内核向进程返回一个文件描述符(非负整数)。
后续对文件的操作只需通过该文件描述符,内核记录有关这个打开文件的信息(file结构体)。
一个进程启动时,默认打开了3个文件,标准输入、标准输出、标准错误,对应文件描述符是0(STDIN_FILENO)、1(STDOUT_FILENO)、2(STDERR_FILENO),这些常量定义在unistd.h头文件中。
open 函数
open 函数可以打开或创建一个文件。
#include<fcntl.h>
int open(const char *path, int flags, ... /* mode_t mode */);
path 是文件名字, flags是打开文件的选项。
选项大概有下面这些
- O_RDONLY 打开一个供读取的文件
- O_WRONLY 打开一个供写入的文件
- O_RDWR 打开一个可供读写的文件
- O_APPEND 写入的所有数据将被追加到文件的末尾
- O_CREAT 打开文件,如果文件不存在则建立文件
- O_EXCL 如果已经置O_CREAT且文件存在,则强制open() 失败
- O_TRUNC 在open() 时,将文件的内容清空
- O_CLOEXEC 在进行exec进程替换时关闭打开的文件描述符。
- O_NONBLOCK 非阻塞模式
create 函数
create 函数创建一个新文件。
#include<fcntl.h>
int create(const char *path, int flags, mode_t mode);
参数说明请参考 open 函数。
close 函数
close 关闭一个打开的文件,参数为文件描述符。
#include<unistd.h>
int close(int filedes);
lseek 函数
lseek 显示的位打开的文件设置偏移量,打开时偏移量为0。
#include<unistd.h>
off_t lseek(int filedes, off_t offset, int whence);
参数简单解释
- whence 为 SEEK_SET 时, 偏移量 offset 相对于文件开始处计数。
- whence 为 SEEK_CUR 时, 偏移量 offset 相对于当前的偏移量开始计数, offset 可以为正或负
- whence 为 SEEK_END 时, 偏移量 offset 相对于文件末尾处开始计数, offset 可以为正或负
read 函数
read 函数从打开的文件读数据。
#include<unistd.h>
off_t read(int filedes, void* buf, size_t nbytes);
write 函数
write 函数从打开的文件写数据。
#include<unistd.h>
off_t write(int filedes, const void* buf, size_t nbytes);
文件共享
如果一个进程或线程操作一个文件, 那没什么问题。
但是多个进程和线程同时操作一个文件时,就需要共享打开的文件了。
内核中IO的数据结构
内核使用三种数据结构表示打开的文件。
- 进程在进程表中中都有一个记录项:文件描述符和指向一个文件表项的指针
- 内核为所有打开的文件维持一张文件表;文件状态标志,文件当前偏移量,指向该文件v节点表项的指针
- 每个打开的文件都有一个v节点结构:文件信息。
两个进程打开同一个文件,会共用一个 v节点表。
当多个进程同时写一个文件时,就会发生意想不到的问题了。
例如后来新增的 pread 和 pwrite 就是将seek 操作和io操作合并为一个原子操作的扩展。
dup 复制文件描述符函数
复制一个现有的文件描述符
#include<unistd.h>
int dup(int filedes);
int dup2(int filedes, int filedes2);
dup 复制当前文件描述符,返回最小的可用的文件描述符。
dup2 将当前文件描述符复制到指定的文件描述符,指定的文件描述符若一打开,则先关闭。
复制文件描述符后, 他们共享同一个文件表。
sync 同步函数
sync 主要用于刷新缓存。
#include<unistd.h>
int fsync(int filedes);
int fdatasync(int filedes);
void sync(void);
sync 函数将所有修改过的块缓冲区排入写队列后立即返回。
fsync 函数只对指定的文件的搜索修改的块缓冲写入到队列,并等待写到磁盘后返回。
fdatasync 函数将所有数据写入磁盘后返回。fsync 只是将数据写到磁盘,文件属性信息写到磁盘之前就返回了。
fcntl 函数
fcntl 函数改变已经打开的文件的性质
#include<unistd.h>
int fcntl(int filedes, int cmd, .../* int arg */);
fcntl 的具体功能如下
- 复制文件描述符 F_DUPFD(long)
- 设置/获取文件描述符标志 F_GETFD (void)= F_SETFD (long)
- 设置/获取文件状态标志 F_GETFL (void) F_SETFL (long)
- 获取/设置文件锁 F_GETLK F_SETLK,F_SETLKW
- 获取/设置异步IO所有权 F_GETOWN, F_SETOWN
ioctl 函数
ioctl 函数时 IO 操作的杂物箱, 上面函数不能实现的功能,都在这里面实现。
比如操作磁带,获取和设置终端窗口大小以及终端的高级功能。
参考资料
本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。