文件描述符
所有打开的文件都通过文件描述符引用。操作(读写)该文件描述符就相当于操作该文件。
文件描述符是一个非负的整数。
同时文件描述符0,1,2都已经对应标准输出(输出在终端上),标准输入(从标准终端输入),标准错误。
对应STDIN_FILENO
,STDOUT_FILENO
,STDERR_FILENO
。
这三个常量定义在<unistd.h>
中。
open和openat
#include <fcntl.h>
int open(const char * path ,int flag, mode_t =NULL)
int openat(int fd,const char * path ,int flag, mode_t =NULL)
两者的主要区别是,open
只能相对于程序运行的目录,而openat
可以使相对于fd
指定的目录进行打开操作,同时fd
的特殊值AT_FDCWD
指的是当前目录。
同时可以避免TOCTTOU
,意思是如果一个操作需要两个文件操作才能完成,那么中间可能cpu调度的原因,第一个文件操作已经被更改,从而后一个操作也会不正确。可以用来获取特殊权限。
flag
这个参数主要指定了打开模式:
一下五个只能出现一个:
-
O_RDONLY
只读打开,0 -
O_WDONLY
只写打开,1 -
O_RDWE
读写打开,2 -
O_EXEC
只 执行打开。 -
SEARCH
只搜索打开,用于目录
一下这些常量是可选的使用|
运算与上面的结合。
-
O_APPEND
每次写入到尾端 -
O_CLOEXEC
当调用exec()
函数成功后,文件描述符会自动关闭 -
O_CREAT
如果打开的文档不存在,就自动创建,同时需要指定mode_t
参数 -
O_DICECTORY
如果打开的不是目录jiubaocuo -
O_EXCL
如果同时指定O_CREAT
和O_EXCL
,当文件存在的时候就出错。 O_NOCTTY
-
O_NOFOLLOW
如果打开的是一个连接符号,就出错 -
O_NONBLOCK
非阻塞打开,如果没读到数据就出错返回。 -
O_SYNC
写入数据是,需要等到数据真正写入到硬盘时才返回,包括文件属性的更新。
ext4下,同步写和延时写差距不大?hfs下差距很大。和系统有关? -
O_DSYNC
写入数据是,需要等到数据真正写入到硬盘时才返回,如果不影响读取刚写入的数据,则不需要等待文件属性更新。 -
O_RSYNC
读操作会等待所有对该文件的写操作完成以后才执行。 -
O_DICECT
看不懂
creat()
#include <fnctl.h>
int creat(const char* path,mode_t mode)
//出错返回-1
缺点是都是以只写操作打开该文档,所以如果要读,需要关闭该文件描述符重新打开。
一般使用open(path,O_RDWR|O_CREAT,mode)
代替。
close()
#include <unistd.h>
int close(int fd);
//成功返回0 ,出错返回-1
关闭文档,释放所有记录锁。
进程关闭以后,会关闭所有该进程所打开的文件。
lseek()
#include <unistd.h>
off_t lseek(int fd,off_t offset,int whence);
//出错返回-1,否则返回新的稳健偏移量
whence
偏移的起点
- SEEK_SET
将文件的偏移量设置为距文件开头offset个字节,也就是说,起点是开头 - SEEK_CUR
偏移起点文当前起点,offset可正可负 - SEEK_END
偏移起点为文件的总长度。也就是说起点在末尾。
如果将文件描述符对一个管道FIFO或者套接字使用,那么返回-1
,并且,errno为ESPIPE
因为这三个都是流式的,只能读写,不能设置偏移量。
如果将偏移量定位在结尾之后开始写入数据,那么中间的空洞被读为0。
read()
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t n);
//成功返回读到的字节数,到文件尾返回0,出错返回-1
ssize_t是一个带符号的整数。而size_t
是一个不带符号的整数。
读到的字节小于设置的字节数原因:
- 读到末尾
本次返回读到的数据,下一次返回0 - 从终端设备读,每次只能一行
- 从网络中读是,网络的缓存机制可能造成
- 管道和FIFO中时,管道中的字节数少于所需数量
- 信号中断前已经读到数据
读文件从文件当前偏移量开始,成功返回之前,改变偏移量。
write()
#include <unistd.h>
ssize_t write(int fd,const buf*,size_t n);
//成功返回写入的字节数,错误返回-1
从偏移量位置开始写。
如果设置了O_APPEND
则每次写操作之前将文件偏移量设置在文件的结尾处。
根据局部性原理,系统会预读比所指定数量多的数据,并假设它们很快会被用到。
文件共享
多进程间操作同一文件。
文件描述符标志和文件状态标志
- 文件描述符标志
是体现进程的文件描述符的状态,fork进程时,文件描述符被复制;
目前只有一种文件描述符:FD_CLOEXEC
。指明是否复制进新的进程。 - 文件状态标志
是体现进程打开文件的一些标志,fork时不会复制file 结构,而是两个进程文件描述符指向同一个file(当FD的exec标志为0时)
内和使用三种结构表示打开的文件
- 内核的记录项:文件描述符标志,文件表项,V节点表项
每个进程在进程表中都有一个记录项,每个描述符占一项。
每一项都有一个标志,该标志表示该文件描述符的特性,可以使用fnctl()
获取和改变。
一个指向文件表项的指针(指向内核中) - 内核的文件表
内核记录文件的状态标志,文件表项中记录着文件状态标志:阻塞,同步的方式。在该进程中打开的文件的偏移量(不同进程中可以不同)。还有一个指向V节点表项的指针。 - V节点结构
V节点表项是所有进程共有一份,表示文件的信息:长度,修改时间等。
应该是存在硬盘上。
不同进程公用一份。
原子操作的IO
读写
#include <unistd.h>
ssize_t pread(int fd,void *buf,size_t n,off_t offset);
ssize_t pwrite(int fd,const void *buf,size_t n,off_t offset);
//返回值同以前
但是有用吗?offset
的值如何确定?
复制文件描述符
#include <unistd.h>
int dup(int fd);
int dup2(int fd,init fd2);
//出错返回-1
第二个函数,在fd2
出复制fd1
,如果fd2
处已经有一个文件打开了,那么先关闭,在打开。是一个院子操作。
如果相同,那么步步操作,直接返回。
复制以后,两个文件描述符共享一个文件表项。
缓冲
#incldue <unistd.h>
int sync(void);
int fsync(int fd);
int fdataasync(int fd)
//错误返回-1
- sync
将所有修改过的缓存,排入写队列。也就是实际写入硬盘,但是不等待写入结束。
对所有文件
系统周期性执行该函数。 - fsync
对指定fd,将修改过的缓存写入硬盘,并且修改文件属性,结束后返回 - fdatasync
对指定fd,将修改过的缓存写入硬盘,结束后返回,可以不用等待修改文件属性结束。
fcntl()
#include <fcntl.h>
int fcntl(int fd, int cmd,...)
第三个参数是一个整数(在get时,不需要该参数,设为0),或是一个指针
功能如下:
- 复制文件描述符
cmd = F_DUPFD 或 F_DUPFD_CLOEXEC
cmd = F_DUPFD
返回未用最小的整数作为新的描述符返回,但是该文件描述符有自己独立的文件描述符标志,同时清除了O_CLOEXEC
标志。而dup()
打开的是共享的。
cmd = F_DUPFD_CLOEXEC同
dup2`,需要第三个参数 - 设置获取文件描述符状态
cmd = F_GETFD 和 F_SETFD
目前可设置和获取的只有一种,是否在新的线程中复制一份。
set时需要第三个参数。0不关闭,1关闭。 - 设置获取文件状态标志
cmd = F_GETFL 和 F_SETFL
指的是,读写,阻塞,写模式(append),同步读写.
也就是打开open
的参数。
在获取的时候,由于读写的五中模式互斥,所以需要使用一个屏蔽字O_ACCMODE与运算,取得访问方式位,然后挨个比较
int val = fcntl(fd,F_GETFL,0);
switch (val & O_ACCMODE)
case O_RDONLY:
case .....
而其余可选的状态标志,不需要。直接进行与运算
if (val & O_APPEN)
{
}
但是设置的时候只能是堵塞模式,缓存模式。设置的时候需要先获取,然后进行|=
然后再设置。不然会清除之前的设置。
- 设置获取异步IO
cmd = F_GETOWN 和 F_SETOWN
- 获取设置记录所
cmd = F_GETLK F_SETLK F_SETLKW
/dev/fd/n
打开文件,等于复制文教描述符。
int fd=open("/dev/fd/1",O_RDWR);
虽然设置了读写,但是只能使用之前打开的模式,比如之前是写模式,那么使用这种方式打开也不能读。