文件描述符是一个非负整数,指向打开的文件。
文件关闭后被文件使用的描述符会被释放,等着下一次open时,被重复利用。
每个程序运行起来后,就是一个进程,系统会给每个进程分配 0 1 ˜ 023 0\~1023 01˜023的描述符范围,也就是说每个进程打开文件时,open所返回的文件描述符,是在 0 1 ˜ 023 0\~1023 01˜023范围中的某个数字。 0 1 ˜ 023 0\~1023 01˜023这个范围,其实就是文件描述符池。
1023这个上限可不可以改?
可以,但是没有必要,也不会介绍如何去改,因为一个进程基本不可能出现,同时打开1023个文件的情况,文件描述符的数量百分百够用。
进程使用的文件以文件描述符表的形式存放在进程控制块(PCB)中
#include
int open(const char *pathname,int flags[,mode_t mode]);//open and possibly create a file
如果没有mode参数,只能打开存在的文件,否则会报错;如果有mode参数,就会按照mode指定的文件权限创建文件。
返回:打开成功返回文件操作符,否则返回-1并设置errno
参数:
flags
以下三个选项必选其中之一,且是互斥的
O_RDONLY :Open for reading only.
O_WRONLY:Open for writing only.
O_RDWR:Open for reading and writing. The result is undefined if this flag is applied to a FIFO.
以下是可选组合选项,通过|
逻辑或组合
O_CREAT:若欲打开的文件不存在则自动建立该文件.
O_EXCL:如果O_EXCL与O_CREAT 被同时设置, 此指令会去检查文件是否存在,文件若不存在则建立该文件, 否则将导致打开文件错误. 此外, 若O_CREAT 与O_EXCL 同时设置, 并且欲打开的文件为符号连接, 则会打开文件失败.
O_NOCTTY:如果欲打开的文件为终端机设备时, 则不会将该终端设备当成进程控制终端机.
O_TRUNC:若文件存在并且以可写的方式打开时, 此旗标会令文件长度清为0, 而原来存于该文件的资料也会消失.
O_APPEND 当读写文件时会从文件尾开始移动, 也就是所写入的数据会以附加的方式加入到文件后面.
O_NONBLOCK:以非阻塞的方式打开文件, 也就是无论有无数据读取或等待, 都会立即返回进程之中.
如果没有设置O_NONBLOCK:
如果设置了O_NONBLOCK:
O_NDELAY 同O_NONBLOCK.
O_SYNC 以同步的方式打开文件.
O_NOFOLLOW:如果参数pathname 所指的文件为一符号连接, 则会令打开文件失败.
O_DIRECTORY:如果参数pathname 所指的文件并非为一目录, 则会令打开文件失败。
可选参数mode
,只有在创建新文件时才会生效
open返回文件描述符池中当前最小的没有用到的那一个。
进程一运行起来,0/1/2默认就被使用了,最小没被用的是3,所以返回3。
如果又打开一个文件,最小没被用的就应该是4,所以open返回的应该是4。
write(2,buf,sizeof(buf))
将buf中的数据写道屏幕上,open: Linux 的系统函数(文件io函数)
open成功后,返回的文件描述符,指向了打开的文件。
fopen:c库的标准io函数
#include
FILE* fopen (const char *path,const char *mpde);
fopen成功后,返回的是FILE*的文件指针,指向了打开的文件。
对于Linux的c库来说,fopen这个c库函数,最终其实还是open函数来打开文件的
fopen只是对open这个函数做了二次封装。
#include
int close(int fd);//close a file descriptor
参数
返回值
调用成功:返回0
调用失败:返回-1,并给errno自动设置错误号
#include
ssize_t write(int fd,const void *buf,size_t count);//write to a file descriptor
功能:向fd所指向的文件写入数据。
参数
数据中转的过程:
应用缓存(buf)—>open打开文件时开辟的内核缓存—>驱动程序的缓存—>块设备上的文件
返回值
调用成功:返回所写的字符个数
调用失败:返回-1,并给errno自动设置错误号
直接写字符串常量时,字符串常量被保存(缓存)在了常量区,编译器在翻译如下这句话时,write (fd, “hello world” , strlen ( “hello world”) )会直接将"hello world"翻译为"hello world"所存放空间的起始地址(也就是h所在字节的地址),换句话说,直接使用使用字符串常量时,字符串常量代表的其实是一个起始地址。
#include
ssize_t read(int fd,const void *buf,size_t count);//read from a file descriptor
功能:从fd指向的文件中,将数据读到应用缓存buf中
参数
数据中转的过程:应用缓存(buf)<—open打开文件时开辟的内核缓存<—驱动程序的缓存<—块设备上的文件
返回值
1)成功:返回读取到的字符的个数
2)失败:返回-1,并自动将错误号设置给errno。
#include
off_t lseek(int fd, off_t offset, int whence);
功能
调整读写的位置,就像在纸上写字时,挪动笔尖所指位置是一样的。
c库的标准io函数里面有一个fseek函数,也是用于调整读写位置的,fseek就是对lseek系统函数封装后实现的。
返回值
参数:
#include
#include
int fcntl(int fd, int cmd, ... /* arg */ );
功能
fcntl函数其实是File Control的缩写,可以通过fcntl设置、或者修改已打开文件的某些性质。
返回值
调用成功:返回值视具体参数而定
调用失败:返回-1,并把错误号设置给errno。
参数
fd:指向打开文件的文件描述符
/* arg */是多余的参数如果没有用到就写0
cmd:控制命令,通过指定不同的宏来修改fd所指向文件的性质。
F_DUPFD
复制描述符,可用来模拟dup和dup2,后面会有例子对此用法进行说明。
返回值:返回复制后的新文件描述符
#include
fd=open(FILE_NAME,O_RDWR);
//模拟dup2(fd,1);
close(1);
fd1=fcntl(fd,F_DUPFD,1);
F_GETFL、F_SETFL
获取、设置文件状态标志,比如在open时没有指定O_APPEND,可以使用fcntl函数来补设。
返回值:返回文件的状态标志
什么时候需要fcntl来补设?
当文件描述符不是你自己open得到,而是调用别人给的函数,别人的函数去open某个文件,然后再将文件描述符返回给你用,在这种情况下,我们是没办法去修改被人的函数,在他调用的open函数里补加文件状态标志。此时就可以使用fcntl来补设了,使用fcntl补设时,你只需要知道文件描述符即可。
FGETFD、FSETFD
F_GETOWN、F_SETOWN
获取或设置文件描述符所属进程,如fcntl(mousefd,F_SETOWN,getpid());
F_GETLK或F_SETLK或F_SETLKW
有两种情况:
在进程内多次open打开同一文件时,文件描述符是不同的。在同一进程里面,一旦某个文件描述符被用了,在close释放之前,别人不可能使用,所以指向同一文件的描述符不可能相同。
如果不用O_APPEND选项,open打开同一文件的多个文件描述符同时写数据时会相互覆盖:
不同文件描述符对应不同的文件表,文件表内的文件偏移量也是独立的,一个文件描述符写操作不会影响另一个文件描述符对应的文件偏移量不变,它们写的位置是重叠的,就会相互覆盖。
指定O_APPEND参数即可解决覆盖问题
必须每个open都要指定,有一个不指定就会覆盖,就先过马路一样,都要准守交通规则才能安全,开车的和行人,只要有一个不准守都会出事。
共享操作的文件描述符对应同一个V节点,V节点中的文件长度信息是大家共享的,当文件被写入数据后,文件长度就会被更新,都指定O_APPEND后,使用不同的文件描述符写数据时,都会先使用文件长度更新自己的文件位移量,保证每次都是在文件的最末尾写数据,就不会出现相互覆盖的情况。
同单进程文件共享相同,同样因为各自有独立的文件偏移量存在覆盖问题,解决方法同样为加O_APPEND标志。
#include
int dup(int oldfd);
#include
int dup2(int oldfd,int newfd);
功能
功能同dup,只不过在dup2里面,我们可以自己指定新文件描述符。如果这个新文件描述符已经被打开了,dup2会把它给关闭后,再使用。
dup2和dup的不同之处在于:
dup:自己到文件描述符池中找新文件描述符dup2:我们可以自己指定新文件描述符
返回值
参数
oldfd:被复制的已经存在的文件描述符
newfd:新的文件描述符
#include
int fd1=0,fd2=0;
fd1=open(FILE_NAME,O_RDWR|O_TRUNC);
close(1);
fd2=dup(fd1);
//或
dup2(fd,1);
函数中的文件描述符值写死了,无法修改为新的描述符,但是你又希望该函数,把数据输出到其它文件中,此时就可以使用dup、dup2对该函数中的文件描述符,进行重定位,指向新的文件,函数就会将数据输出到这个新文件。
linux命令>
就是dup、dup2的典型应用
读键盘、鼠标是阻塞的;读普通文件时,如果读到了数据就成功返回,如果没有读到数据返回0,总之不会阻塞。
可以将阻塞的读改为非阻塞的读,非阻塞读的意思就是说,如果有数据就成功读到,如果没有读到数据就出错返回,而不是阻塞。
打开文件时指定O_NONBLOCK
状态标志
fd=open("/dev/input/mouse0",O_RDONY|O_NONBLOCK);
通过fcntl
函数指定O_NONBLOCK
来实现
fcntl
来补设。fcntl
来补设O_NONBLOCK
的,此时就需要使用fcntl来重设或者补设。异步IO类似中断,读鼠标键盘 没有数据时进程干自己的事,有数据之后底层驱动给进程发一个SIGIO信号,通知进程数据准备好了,进程来处理数据。
不过使用异步IO有两个前提,
SIGIO
信号的代码,只有这样当底层数据准备好后,底层才会发送SIGIO
信号给进程。SIGIO
信号的代码,如果驱动程序是我们自己写的,发送SIGIO
的代码就需要我们自己来写。fcntl
函数。使用异步IO时应用层的设置步骤:
调用signal
函数对SIGIO
信号设置捕获函数
在捕获函数里面实现读操作,比如读鼠标。
使用fcntl
函数,将接收SIGIO
信号的进程设置为当前进程
如果不设置的,底层驱动并不知道将SIGIO
信号发送给哪一个进程
fcntl(mousefd,F_SETOWN,getpid());
使用fcntl
函数,对文件描述符增设O_ASYNC
的状态标志,让fd支持异步IO
mousefd = open("/dev/input/mouse1",O_RDONLY);
flag = fcntl(mouse_fd,F_GETFL);
flag |= O_ASYNC;//补设O_ASYNC
fcntl(mouse_fd,F_SETFL,flag) ;