文件IO简述-总结自APUE

c文件表项

Linux针对打开的文件维护了3张表:

  1. 文件描述符表:这个表每个进程都有一张,表项的索引是文件描述符,内容是指向文件表项指针的指针
  2. 文件表项:这个表用于维护已经打开的文件集合,所有进程共享这一张表,表的内容包括:1)当前文件偏移量(内核为每个进程都会维护这样一个偏移量)、2)引用计数(每有一个进程打开该文件,对应文件引用就会递增,关闭该文件,引用就会递减,当引用为0的时候,内核才会关闭文件)、3)指向v-node表的指针
  3. v-node表:这个表项包括了i-node节点,用于维护文件的元信息,如所有者、访问权限(读、写、执行)、类型(是文件还是目录)、内容修改时间、inode修改时间、上次访问时间、对应的文件系统存储块的地址,等等

文件打开关闭

打开文件:open、openat

open

#include <fcntl.h>
int open(const char *path, int oflag, int perms(可选参数))
返回值:成功返回文件描述符,失败返回-1
oflag包括:
必选参数:这些参数就是一个bit,可以通过位或的方式传递多个参数
O_RDONLY:以只读方式打开
O_WRONLY:以只写方式打开
O_RDWR:以读写方式打开
可选参数:
O_CREAT:文件不存在则创建,需要执行perm参数,指明创建文件的权限    
O_TRUNC:若文件已存在,则删除文件中的所有数据,并置文件大小为0
O_APPEND:以追加的方式打开文件,打开后文件指针指向文件末尾

openat

#include <fcntl.h>
int open(int fd, const char *path, int oflag,)
返回值:成功返回文件描述符,失败返回-1
oflag:同open
path:如果path是绝对路径,那么忽略fd参数,如果是相对路径,那么打开fd对应文件位置+相对路径的文件    

关闭文件:close

#include <unistd.h>
int clode (int fd);
作用:关闭一个文件同时释放加在该文件上的所有记录锁,进程终止时,内核自动关闭它所有打开的文件

修改文件偏移量:lseek

lseek的作用是修改文件的偏移量,并且返回最新的偏移量,偏移量指的是文件游标相对于文件开始处的以字节为单位的偏移。每次读或写的时候,会从当前位置开始操作,然后会修改偏移量,从而会影响下一次操作。lseek使用的注意事项:

  1. 进程1、2如果打开同一个文件,进程1修改了偏移量之后,进程2不感知,因为偏移量每个进程维护一份、
  2. lseek之后,会影响read读取的位置,read会从lseek修改之后的偏移位置开始读取数据
  3. read了之后,偏移位置不会复位,导致第二次read的时候,读出来的数据会是空(前提是第一次read把数据读到了文件末尾)
  4. 每次打开文件的时候,偏移位置会置0,除非以O_APPEND的方式打开

函数定义如下:

off_t lseek(int fd, off_t offset, int whence);
fd:操作文件的fd
offset:相对于whence的偏移量
whence包括:
SEEK_SET:相对于文件头部进行偏移
SEEK_CUR:相对于当前位置进行偏移
SEEK_END:相对于文件尾部进行偏移,偏完指针可以超过原始文件大小 
返回值:正常返回文件偏移量,即当前游标相对于文件开始处的偏移位置。如果fd对应管道、FIFO、网络套接字,则返回-1

例如

//查询当前偏移量
off_t currpos = lseek(fd, 0, SEEK_CUR);

文件读写

读数据:read

read函数用于读取文件中的内容,读取的结果放在用户态的缓存中,每次读取都是从当前偏移量处开始读,读后都会更新偏移量,所以每次读取都是会接着读。

ssize_t read(int fd, void *buf, size_t nbytes);
fd:文件的描述符
buf:读过来的数据放在哪个缓存中
nbytes:buf缓存的大小,一般设置成4096效率最高,因为块设备的块大小是4K,大于这个值的话,会有部分时间损耗在跨块的访问上    
读文件时:成功,返回字节数;读到末尾,返回0;错误,返回-1;如,有30,读100,第一次读返回30,第二次读返回0.

写文件:write

write函数用于给文件内容中写数据,从偏移位置开始写,所以如果要从指定位置开始写,需要调用lseek先设置一下偏移量之后再写入数据,写完后,结束位置之后的数据都会清空。比如从起始位置写一个字符串到文件,那么文件的内容会被覆盖掉。

write每一次写都会修改偏移量,所以多次写会接着往后写。

ssize_t write(int fd, const void * buf, size_t nbytes);
fd:文件的描述符
buf:待写入的数据
nbytes:待写入数据的大小
返回值:写入成功的话,返回实际写入的数据字节数目,写入失败的话,返回负数,并设置errno    

写文件的原子操作:追加。如果先定位再写,多进程不安全,解决办法,定位和写作为原子操作,使用O_APPEND,每次写的时候,内核会把偏移设置成当前文件长度。

读数据:pread

pread的作用是先调用lseek设置一个偏移量,再调用read,read完了之后的偏移量是offset。

ssize_t pread(int fd, void *buf, size_t nbytes, off_y offset);
fd:文件的描述符
buf:读过来的数据放在哪个缓存中
nbytes:buf缓存的大小    
offset:偏移量
返回值:成功,返回字节数;读到末尾,返回0;错误,返回-1

写数据:pwrite

pwrite的作用是先lseek,再write,write了之后的偏移量是offset。

ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
fd:文件的描述符
buf:待写入的数据
nbytes:待写入数据的大小
返回值:写入成功的话,返回实际写入的数据字节数目,写入失败的话,返回负数,并设置errno  

文件描述的复制:dup/dup2

dup和dup2都是用于创建新的文件描述符,新旧描述符指向同一个文件表项,因此共享文件偏移量。区别在于dup产生的新的文件描述符为最小未用到的描述符,dup2产生的新的描述编号由人为指定

#include <unistd.h>
int dup (int oldfd); 
oldfd:老的描述符;
返回值:新的描述符,如果出错的话,返回负数
#include <unistd.h>
int dup2 (int oldfd, int newfd); 
oldfd:老的描述符;
newfd:若参数newfd为一已打开的文件描述符, 则newfd 所指的文件会先被关闭.
返回值:新的描述符,如果出错的话,返回负数

数据落盘函数

我们写文件的时候,数据先写到内核的缓冲区中,当缓冲区满的时候,内核会把这部分数据加入输出队列中,然后周期性把输出队列中的数据落盘,这个时候才才发生实际的磁盘IO,这种方式也叫做延迟写。对于数据强一致性要求的场景,unix提供了sync、fsync与fdatasync三个函数,供应用程序手动执行数据持久化。

在调用write函数之后,数据只是写到了缓冲区函数就返回了,并不能保证实际写到了磁盘中,如果这时候发生OS崩溃,那么这部分数据就会丢失。这个时候需要调用fsync,它会阻塞的等待磁盘IO结束之后再返回。

sync

sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束

#include <unistd.h>
void sync(void);

fsync

fsync的功能是确保文件fd所有已修改的内容已经正确同步到硬盘上,该调用会阻塞等待直到设备报告IO完成。

#include <unistd.h>
int fsync(int fd);
返回值:若成功则返回0,若出错则返回-1,同时设置errno以指明错误.

fdatasync

由于fsync是把元数据和实际数据都落盘,而元数据和实际数据通常在磁盘的不同位置,所以调用fsync的时候通常会有两次IO操作,效率低下,fdatasync则实现如下功能:

  1. 立即同步实际数据
  2. 必要条件下同步元数据,必要条件指的是文件大小被修改。针对日志落盘的场景,由于每append一条日志一定会修改文件大小,所以效率会很低,处理方式就是一开始就把日志文件的大小扩展的很大,然后append日志的时候文件大小不变,这样每次只需要1次IO即可。
#include <unistd.h>
int fdatasync(int fd);

修改文件描述符的属性

fcntl

fcntl用于修改或者获取已经打开的文件的属性。

#include<unistd.h>
#include<fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd ,struct flock* lock);
返回值:出错返回-1,正常根据cmd返回不同的结果
fd:被操作的文件描述符
cmd:具体做什么操作
    F_GETFL: 获取文件状态标志,需要用屏蔽字的方式获取,如,res & O_ACCMODE, res & O_APPEND等。
	F_SETFL: 设置文件表项中的文件状态标志
	F_SETFD: 设置文件描述符 

ioctl

不能用fcntl函数表示的IO操作,由ioctl完成

#include <unistd.h>
#include <sys/ioctl.h>
int ioctl (int, fd, int request …);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值