unix c编程:不带缓冲的文件 I/O

时间:2022-06-18 21:57:20

1 文件描述符

文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。将其作为参数传送给 read 或 write等操作。

UNIX 系统 shell 使用文件描述符 0 与进程的标准输入相关联,文件描述符 1 与进程的标准输出相关联,文件描述符 2 与进程的标准出错输出相关联。

2 不带缓冲的 I/O 函数

术语不带缓冲指的是每个 read 或 write 都调用内核中的一个系统调用,即user态不存在缓冲区存在。

2.1 open 函数 

#include <fcntl.h>  
int open(const char *pathname, int oflag, ... /* mode_t mode */); 

对于 open 函数而言,仅当创建新文件时才使用第三个参数。

pathname 是要打开或创建文件的名字。

oflag 参数用于说明此函数的多个选项。用下列一个或多个常量进行“或”运算构成 oflag 参数(这些常量定义在 <fcntl.h> 头文件中)。

  O_RDONLY  只读打开

  O_WRONLY  只写打开

  O_RDWR   读、写打开

这三个常量中必须指定一个且只能指定一个。下列常量则是可选的:

  O_APPEND  每次写时都追加到文件的尾端。

  O_CREAT  若文件不存在,则创建它。使用此选项时,需要第三个参数 mode,用于指定该新文件的访问权限位。

  O_EXCL  如果同时指定了O_CREAT,而文件已经存在,则会出错。用此可以测试一个文件是否存在,如果不存在,则创建此文件。

  O_TRUNC  如果此文件存在,而且为只写或者读写成功打开,则将其长度截短为 0.

  O_NOCTTY  如果 pathname 指的是终端设备,则不将该设备分配为此进程的控制终端。

  O_NONBLOCK  如果指的是一个 FIFO、一个块特殊文件或一个字符特殊文件,则此选项为文件的本地打开操作和后续的 I / O 操作设置非阻塞模式。

mode 参数,当指定了O_CREAT 选项时起作用

第三个参数取下面列表中的这些常量的“或”运算结果用于指定新文件的访问权限(这些常量定义在 <sys/stat.h>中)。

S_IRUSR 用户 -读
S_IWUSR 用户 -写
S_IXUSR 用户 -执行
S_IRGRP 组 -读
S_IWGRP 组 -写
S_IXGRP 组 -执行
S_IROTH 其他 -读
S_IWOTH 其他 -写
S_IXOTH 其他 -执行








open 函数的 oflag 参数还支持三个可选常量,用来解决多线程之间的read,write同步操作

  O_DSYNC  使每次 write 等待物理 I/O 操作完成,但是,如果写操作并不影响读取刚写入的数据,则不等待文件属性被更新。

  O_RSYNC  使每一个以文件描述符作为参数的 read 操作等待,直至任何对文件同一部分进行的未决写操作都完成。

  O_SYNC  使每次 write 都等待物理 I/O 操作完成,包括由 write 操作引起的文件属性更新所需的I/O。

当文件用O_DSYNC 标志打开时,在重写其现有的部分内容时,文件时间属性不会同步更新。与此相反,如果文件是用O_SYNC 标志打开时,那么对该文件的每一次 write 操作都将在 write 返回之前更新文件时间。

2.2 close 函数

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

当一个进程终止时,内核自动关闭它所有打开的文件。

2.3 read 函数

调用 read 函数从打开的文件中读数据。

#include <unistd.h>  
ssize_t read(int filedes, void *buf, size_t nbytes);  

读操作从文件的当前偏移量处开始,在成功返回之前,该偏移量将增加实际读到的字节数。

2.4 write 函数

调用 write 函数想打开的文件写数据。

#include <unistd.h>  
ssize_t write(int filedes, const void *buf, size_t nbytes);  

对于普通文件,写操作从文件的当前偏移量处开始。如果在打开该文件时,制定了O_APPEND 选项,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处。在一次成功写之后,该文件的偏移量增加实际写的字节数。

对于32位系统来说,每一个块大小是4K,因此当一次写入数据大小是4K的倍数时,理论上写入操作花费时间最短。如果每次写入小于4K,写入内容越少,与内核交互越多(user态到kernel态),花费时间越多!

2.5 lseek 函数

调用 lseek 显示地为一个打开的文件设置其偏移量。

#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 可为正或负。

管道、FIFO 和网络套接字都不支持设置偏移量,如果一个文件描述符引用的是这三者之一,则 lseek 函数返回 -1,并将 errno 设置为 ESPIPE。

文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞。位于文件中但没有写过的字节都被读为 0.

2.6 pwrite,pread 函数

原子操作,避免出现位置定位偏移。

ssize_t pread(int fd,void *buf,size_t nbytes,off_t offset);   
ssize_t write(int fd,const void *buf,size_t nbytes,off_t offset);   

相当于执行lseek+read/write 操作,避免分开执行造成进程间定位不准确,出现问题。
注意:执行之后,文件偏移地址不发生改变!

2.7 dup,dup2 函数

复制一个文件描述符。

#include<unistd.h>  
int dup(int fd);  
int dup2(int fd1,int fd2)

2.7 sync,fsync,fdatasync函数

当向文件写入数据时,内核会先复制到缓冲区,然后排入队列,之后写入磁盘。

sync: void sync(void)将所有修改过的块缓冲区排入队列(不等待写入磁盘)
fsync:int fsync(int fd) 将fd修改排入队列,写入磁盘后返回,并更新文件属性
fdatasync:int fdatasync(int fd)与fsync类似,但是不更新文件属性

2.7 fcntl,ioctl函数

fantl : get/set 文件当前属性

ioctl : 与硬件驱动节点配合使用,调用kernel匹配的ioctl函数

总结:

主要涉及操作:file创建,读写,定位,线程间状态控制,写入同步,属性获取/修改



参考: http://www.cnblogs.com/lienhua34/p/3936086.html
github demo: demo地址链接