c文件表项
Linux针对打开的文件维护了3张表:
- 文件描述符表:这个表每个进程都有一张,表项的索引是文件描述符,内容是指向文件表项指针的指针
- 文件表项:这个表用于维护已经打开的文件集合,所有进程共享这一张表,表的内容包括:1)当前文件偏移量(内核为每个进程都会维护这样一个偏移量)、2)引用计数(每有一个进程打开该文件,对应文件引用就会递增,关闭该文件,引用就会递减,当引用为0的时候,内核才会关闭文件)、3)指向v-node表的指针
- 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、2如果打开同一个文件,进程1修改了偏移量之后,进程2不感知,因为偏移量每个进程维护一份、
- lseek之后,会影响read读取的位置,read会从lseek修改之后的偏移位置开始读取数据
- read了之后,偏移位置不会复位,导致第二次read的时候,读出来的数据会是空(前提是第一次read把数据读到了文件末尾)
- 每次打开文件的时候,偏移位置会置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则实现如下功能:
- 立即同步实际数据
- 必要条件下同步元数据,必要条件指的是文件大小被修改。针对日志落盘的场景,由于每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 …);