低级输入输出
标准I/O函数提供了丰富便捷的输入输出函数,但有时并不需要标准I/O函数提供的数据转换和缓冲处理,某些特殊操作需要UNIX的输入输出系统调用,这些系统调用习惯上成为低级I/O函数。(我觉得理解为底层I/O函数更贴切)
低级I/O函数对文件描述字进行操作,其中有些函数是实现标准I/O函数的初等函数,另外一些执行低级控制操作。
文件描述字的打开、创建和关闭
打开创建
open()、create()用于打开或创建一个文件描述字
参数flags给出文件的打开方式,mode是可选参数,给出文件的访问方式。
参数flags说明:
open()返回的文件描述字是唯一的,不为由正在运行的其他进程所共享。若两个进程同时打开一个文件,系统保证它们有各自的文件描述字。如果两进程都写这个文件,它们将按自己的文件位置来写,写入的数据不会交错的记录在文件内
而是一个被另一个覆盖。 为避免覆盖,可以利用文件锁来协调。
fd=craete(filename)
等价于
fd=open(filename, O_WRONLY|O-CREATE|O_TRUNC, mode);
函数close用于关闭已打开的文件描述字
关闭文件描述字意味下面一系列动作:
- 释放描述字fledes,此描述字可被后继的open()再次使用;
- 释放进程在此文件上占有的所有文件锁;
- 当与管道或IFIO相连的所有文件描述字均被关闭时,废弃任何还在管道或IFIO上的数据。
进程终止时也会自动关闭该进程打开的所有文件,因此可利用该特征而不明显的调用close()关闭文件。
类似标准流,每个进程预先打开三个文件描述字:0、1、2,分别打开标准输入、标准输出、标准错误输出。符号常数STDIN_FILENO、STDOUT_FILENO和STDERROR_FILENO分别表示这三个文件描述字。
内核为管理文件的打开和关闭使用三种数据结构
一个进程打开两个文件的内核数据结构:
- 每个活跃进程在系统的进程表中有一个登记项,称为进程表项。进程表项中有一张进程打开文件表,此表可看成是一个数组,它的每一个元素对应一个文件描述字(FD),每当打开一个文件时,系统便在数组中为它指定一个元素,open()返回的文件描述字就是该数组元素的索引。
数组元素记录文件描述字的信息:- 文件描述字标签
- 指向系统打开文件表项的指针
- 每当open()成功打开一个文件时,便在系统文件表中建立一个打开文件表项。
每个表项代表一个一打开的文件,包含信息:- 文件状态标签。标签由open()的第二个参数指定。
- 当前文件位置
- 指向该文件的的指针
- 每一个打开文件都有一个vnode结构。vnode是内核中表示文件的数据结构,不论文件属于什么类型的文件系统,内核都可以通过与文件相连的vnode来访问文件。
一个文件一个vnode,vnode包含与文件相关的所有维护信息,其中包括vnode信息和文件的inode信息(inode记录着文件的属性、大小、文件驻存的设备等信息)。
两进程打开同一文件的内核数据结构:
三个数据结构明确几点:
1)文件描述字总是从最小可用描述字开始分配。
2)子进程 总是继承父进程的所有描述字。
3)每一次write()完成后,系统打开文件表项中当前位置增加所写字节数。
若这导致当前文件位置超过了文件的大小,则用当前文件位置更新inode中的当前文件大小,此时称文件被扩展。
4)如果文件是用O_APPEND标志打开的,该标志将被设置在系统文件打开表项的文件状态标签中。每当对此文件进行write()时,系统打开表项中当前文件位置将首先移动到inode给出的当前文件大小所在的位置,
从而强制write()只能在当前文件末尾处添加数据。
5)如果一个文件位置被lseek()定位到文件末尾,所发生的只是用inode的当前文件大小设置为系统打开文件表的当前位置。
6)一个进程或不同进程的多个文件描述字可以指向同一文件打开表项,这对应于单次打开文件但复制了多个文件描述字的情形,如dup()或fork()基继承。
read和write函数
对文件进行基本输入输出操作的函数:
每次调用read或write 都会使系统访问磁盘,频繁操作效率比较低,写入输入输出缓冲区就变得尤为重要。
设置文件描述字的文件位置
lseek()可以设置一个描述字相连文件的位置
dup()和dup2()函数
用同一个open()打开的文件可以有多个文件描述字与它相连,这种描述字叫重复描述字。使用dup或dup2或fcntl()函数。
dup()复制描述字old至一个新的描述字,新描述字保证是当前未打开的最小编号可用描述字。dup2()函数复制描述字old至编号为new的描述字,如果new已经打开,它将首先关闭。如果new等于old,dup2返回new但不关闭它。
两函数执行结果返回新的文件描述字,与参数old给定的描述字引用同一个打开的文件,及共享同一个系统打开文件表项。
进程调用dup(1)后的内核数据结构:
重复一个文件描述字的主要用途
实现输入输出重定向,即改变一个特定文件描述字对应的文件或管道。当使用管道进行进程间的通信时,这两函数非常有用。
fdopen()和fileno()函数
文件描述字函数时流函数的初等函数,每一个流与一个文件描述字相连。
fdopen()函数创建一个流,fileno()函数得到一个文件描述字。
文件控制函数fcntl()
fcntl()提供了进一步管理低级文件描述字的各种手段,用它可以对已打开的文件描述字执行各种控制操作。例如,重复一个文件描述字,查询或设置文件描述字标签,操作文件锁等。
fcntl()命令常数名
非阻塞IO
read()、write()等系统调用默认都是阻塞的,调用必须等待操作完成,及读写到数据才返回。某些情形需要非阻塞的操作。
对于给定的文件描述字,有两种方法指定非阻塞IO
- 在open()时指定O_NONBLOCK文件状态标志;
- 在已打开的文件描述字,调用fcntl()函数设置O_NONBLOCK文件状态标志;
readv()、writev()
只需一次系统调用就可以实现在文件和进程的多个缓冲区之间传送数据,免除了多次系统调用或复制数据的开销。
readv()称为散布读,即将文件中若干连续的数据读入内存分散的缓冲区中。
writev()称为聚集写,即收集内存中分散的若干缓冲区中的数据写在文件的连续区域中。
fsync()和fdatasync()函数
数据输出示意图:
写操作 内核缓冲区到真正的物理存贮磁盘存在时间间隔,使用下列函数及时同步。
思考
- 打开文件时,如果希望总是创建一个文件,应当使用什么标志?如果希望每次写出的数据都实际写到物理存储设备,需要使用什么标志?
- open()调用成功总是返回当前可用的编号_____的描述字。对同一个文件使用不同open()打开的文件描述字具有_____的文件位置,由dup()重复的文件描述字具有_______的文件位置。
- 用fcntl()设置文件状态标签时,为什么只能设置O_APPEND、O_NONBLOCK而不能设置其他标志?
- 如何设置非阻塞I/O?