不带缓冲的I/O
。术语不带缓冲
指的是在用户的进程中对其不会自动缓冲,每个read和write都调用内核中的一个系统调用。但是,所有磁盘I/O都要经过内核的块缓存区(也称为内核的缓冲区高速缓存)。唯一例外的是对原始磁盘设备的I/O。
#include <fcntl.h>
int open(const char *path, int oflag, ... /* mode_t mode */ );
int openat(int fd, const char *path, int oflag, ... /* mode_t mode */ );
Both return: file descriptor if OK, −1 on error
oflag参数
- 以下5个常量必须指定一个且只能指定一个
O_RDONLY、O_WRONLY、O_RDWR、O_EXEC(只执行打开)、O_SEARCH(只搜索打开,应用于目录,尚未支持)- 下列常量则可选
O_APPEND、O_CLOEXEC、O_CREAT(需要指定第3个参数mode)、O_EXCL、O_DIRECTORY、O_NOFOLLOW、O_NONBLOCK、O_SYNC、O_TRUNC
open和openat函数返回的文件描述符一定是最小的未用描述符数值。这一点被某些应用程序用来在标准输入、标准输出或标准错误上打开新的文件。
fd参数把open和openat函数区分开,共有3种可能性
- path参数指定的是绝对路径名,则fd参数被忽略,openat函数相当于open函数
- path参数指定的是相对路径名,则fd参数指出了相对路径名在文件系统中的开始地址。fd参数是通过打开相对路径名所在的目录来获取
- path参数指定了相对路径名,而fd参数具有特殊值
AT_FDCWD
,则路径名在当前工作目录中获取
openat函数是POSIX.1最新版本中新增的一类函数之一,希望解决两个问题:
- 让线程可以使用相对路径名打开目录中的文件,而不再只能打开当前工作目录
- 可以避免time-of-check-to-time-of-use(TOCTTOU)错误
TOCTTOU错误的基本思想是:
指计算机系统的资料与权限等状态的检查与使用之间,因为特定状态在这段时间已发生改变所产生的软件漏洞
文件名和路径名截断
#include <fcntl.h>
int creat(const char *path, mode_t mode);
Returns: file descriptor opened for write-only if OK, −1 on error
#include <unistd.h>
int close(int fd);
Returns: 0 if OK, −1 on error
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
Returns: new file offset if OK, −1 on error
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
Returns: number of bytes read, 0 if end of file, −1 on error
- 读普通文件时,到达文件尾端
- 从终端设备读时,通常一次最多读一行
- 从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数
- 从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数
- 从某些面向记录的设备(如磁带)读时,一次最多返回一个记录
- 当一信号造成中断,而已经读了部分数据量时
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
Returns: number of bytes written if OK, −1 on error
内核使用3种数据结构表示打开文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响
左边:进程级的文件描述符表
- 文件描述符标志,目前只有一个FD_CLOEXEC
- 指向一个文件表项的指针
中间:系统级的打开文件表:每次调用open打开一个文件新增一个文件表项(不同进程可打开同一个文件,导致多个文件表项)
- 文件状态标志(读、写、添写、同步、非阻塞等,受open时指定的oflag参数影响,也可通过fcntl函数指定FD_SETFL改变)
- 当前文件偏移量
- 指向该文件v节点表项的指针
右边:文件系统级的i-node表
- i节点包含文件的相关信息,如文件的所有者、文件长度、指向文件实际数据块在磁盘上所在位置的指针等。
- Linux没有v节点,而是采用了一个通用i节点。无论是v节点还是通用i节点,它们都是指向一个与文件系统相关的i节点
其他
- 完成write之后,文件表项的当前文件偏移量增加所写入的字节数。如果这导致当前文件偏移量超出了文件长度,则将i节点表项中的当前文件长度设置为当前文件偏移量
- 如果通过O_APPEND标志打开一个文件,则相应标志会设置到文件表项的文件状态标志中。每次执行写操作之前,当前文件偏移量会首先被设置为i节点表项中的文件长度
- lseek函数只修改文件表项中的当前文件偏移量,不进行任何I/O操作。
- 注意:文件描述符标志只作用于一个进程的一个文件描述符,而文件状态标志则作用于指向该文件表项的任何进程中的所有描述符
对open函数指定O_CREAT和O_EXCL
函数pread、pwrite
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
Returns: number of bytes read, 0 if end of file, −1 on error
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
Returns: number of bytes written if OK, −1 on error
调用pread相当于调用lseek后调用read,但又有区别:
- 调用pread时,无法中断其定位和读操作
- 不更新当前文件偏移量
调用pwrite相当于调用lseek后调用write,也有类似的区别
#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);
Both return: new file descriptor if OK, −1 on error
dup2可以用fd2参数指定新描述符的值
- 如果fd2已经打开,则先将其关闭
- 如果fd等于fd2,则dup2返回fd2,而不关闭它
- 否则,fd2的FD_CLOEXEC文件描述符标志就被清除,这样fd2在进程调用exec时是打开状态
dup(fd); 等价于 fcntl(fd, F_DUPFD, 0);
延迟写
。
#include <unistd.h>
int fsync(int fd); // 只对fd指定的一个文件其作用,并且等待写磁盘操作结束后才返回
int fdatasync(int fd); // 类似于fsync,但只影响文件的数据部分。而除数据之外,fsync还会同步更新文件的的属性
Returns: 0 if OK, −1 on error
void sync(void); // 只是将所有修改过的块缓冲区排入写队列,然后就返回,不等待实际写磁盘操作结束
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* int arg */ );
Returns: depends on cmd if OK (see following), −1 on error
fcntl函数有以下5种功能
- 复制一个已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)
- 获取/设置文件描述符标志(cmd=F_GETFD或F_SETFD),某个进程的文件描述符
- 获取/设置文件状态标志(cmd=F_GETFL或F_SETFL),系统打开文件表中某个文件表项的文件状态标志(参见open函数的oflag参数)
- 获取/设置异步I/O所有权标志(cmd=F_GETOWN或F_SETOWN)
- 获取/设置记录锁(cmd=F_GETLK或F_SETLK)
先通过GET获取得到val,再 val |= flags; 或 val &= ~flags; 最后通过SET将val设置到fd
#include <unistd.h> /* System V */
#include <sys/ioctl.h> /* BSD and Linux */
int ioctl(int fd, int request, ...);
Returns: −1 on error, something else if OK
[wulin@localhost ~]$ ls /dev/fd/ -l
lrwx------. 1 wulin wulin 64 7月 29 10:28 0 -> /dev/pts/0
lrwx------. 1 wulin wulin 64 7月 29 10:28 1 -> /dev/pts/0
lrwx------. 1 wulin wulin 64 7月 29 10:28 2 -> /dev/pts/0
lr-x------. 1 wulin wulin 64 7月 29 10:28 3 -> /proc/5341/fd[wulin@localhost ~]$ ls /dev/std* -l
lrwxrwxrwx. 1 root root 15 7月 29 2014 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx. 1 root root 15 7月 29 2014 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx. 1 root root 15 7月 29 2014 /dev/stdout -> /proc/self/fd/1
- filter file2 | cat file1 - file3 | lpr 首先cat读file1,然后读其标准输入(filter file2命令的输出),然后读file3
- 如果支持/dev/fd,则可以删除cat对“-”的特殊处理,于是可以: filter file2 | cat file1 /dev/fd/0 file3 | lpr
- 作为命令后参数的“-”特指标准输入或标准输出,这已被很多程序采用。但是这会带来一些问题,例如,如果用“-”指定第一个文件,那么看来就像指定了命令行的一个选项
《Unix环境高级编程》读书笔记 第3章-文件I/O,布布扣,bubuko.com
原文:http://www.cnblogs.com/DayByDay/p/3890670.html