在unix中可用的文件I/O函数包括打开文件,读文件,写文件等。
Unix系统中的大多数文件I/O需要用到5个函数:open,read,write,lseek,close.
这里要说明的是read,write的文件I/O都是不带缓冲的,所谓的不带缓冲意思是它们都是走的内核中的一个系统调用。
对于内核而言,所有打开的文件都是通过文件描述符进行引用,文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读或写一个文件时,使用open,create返回文件描述符标识该文件,然后把返回的文件描述符作为参数传递给read,write。
一般情况下unix系统的shell使用文件描述符0作为进程的标准输入,1为标准输出,2为标准错误输出,不同的shell版本可能有所不同,与unix内核无关。
文件描述符的范围是0~open_max 早期的unix系统规定每一个进程最多使用20个文件描述符,
open函数
由open返回的文件描述符是一个最小的未被使用的描述符数值。这一点被有些程序是现在在标准输入,输出,报错上打开新文件。比如。一个程序先关闭标准输出,然后再打开另一个文件。其实我们可以通过dup2函数来实现控制在制定的文件描述符上打开文件。
Creat函数
Creat的一个不足之处是它以只读方式打开所创建的文件。在open的老版本中,如果要创建一个临时文件,并要先写该文件,然后又要读该文件,则必须先调用creat,close,然后调用open.不过现在的新版本open已经解决了这些不便利。
Close函数
当关闭一个文件时还会释放该进程加在该文件上的所有记录锁(有关记录锁有关内容,可以查看博客关于记录锁的文章)。
当一个进程终止时,内核会自动关闭它打开的所有文件。很多程序利用了这一功能而不显示的调用close关闭打开的文件。
Lseek函数
每一个打开的文件都有一个与其相关联的“当前文件偏移量”它通常是一个非负整数,用以度量从文件开始处计算的字节数。通常,读,写操作都从当前文件偏移量处开始,并使偏移量增加所读写的自己数,当打开一个文件的最初时候文件的偏移量为0.
一般,文件的当前偏移量应当是一个非负整数,但是,某些设备也可能允许负的偏移量。但对于普通文件,则其偏移量必须是非负值。因为偏移量可能是负值,所以在比较lseek的返回值时不能检测是否小于0而是测试它是否等于-1.
Lseek仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操作。然后,该偏移量用与下一个读或是写操作。
文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件并在文件中造成一个空洞,这一定是允许的。位于文件中但没有写过的字节都被读为0.
文件中的空洞并不要求在磁盘上占用存储区。具体处理方式与文件系统的实现有关,当定位到超出文件尾端之后写时,对于新写的数据需要分配磁盘块,但是对于源文件尾端和新开始写位置之间的部分则不需要分配磁盘块。
Read函数
如read成功,则返回读到的字节数。如已到达文件结尾,则返回0.
有多重情况可使实际读到的字节数少于要求读的字节数:
·读普通文件时,在读到要求字节数之前已经到达了文件尾端。例如,若在到达文件尾端之前还有30个字节,而要求读100个字节,则read返回30,下一次再调用read时,它将返回0
· 当从终端设备读时,通常一次最多读一行
· 当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。
· 当从管道或FIFO读时,如若管道包含的字节少于所需要的数量,那么read将只返回实际可用的字节数
· 当从某些面向记录的设备(例如磁带)读时,一次最多返回一个记录。
· 当某一信号造成中断,而已经读了部分数据量时,读操作从文件的当前偏移量处开始,在成功返回之前,该偏移量将增加实际读到的字节数。
Write函数
对于普通文件,写操作从文件的当前偏移量处开始。如果在打开该文件时,制定了append选项,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处。在一次成功写之后,该文件偏移量增加实际写的字节数
文件共享
unix系统支持在不同进程间共享打开的文件。
内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
(1)每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
(a)文件描述符标志
(b)指向一个文件表项的指针。
(2)内核为所有打开文件维持一张文件表。每个文件表项包含:
(a)文件状态标志(读,写,添加,同步和非阻塞等)。
(b)当前文件偏移量
(c)指向该文件v节点表项的指针
(3)每个打开文件(或设备)都有一个v节点,v节点包含了文件类型和对比文件进行各种操作的函数的指针。对于大多数文件,v节点还包含了该文件的i节点(索引节点)。这些信息是在打开文件时从磁盘上读入内存的,所以所有关于文件的信息都是快速可供使用的。例如,i节点包含了文件的所有这,文件长度,文件所在的设备,指向文件实际数据块在磁盘上所在位置的指针(linux没有使用v节点,而是使用了通用i节点结构。虽然两种实现有所不同,但在概念上,v节点与i节点是一样的。两者都指向文件系统特有的i节点结构)。创建v节点结构的目的是对在一个计算机系统上的多文件系统类型提供支持。
如果两个独立进程各自打开了同一个文件,我们假定第一个进程在文件描述符3上打开该文件,而另一个进程则在文件描述符4上打开该文件。打开该文件的每个进程都得到一个文件表项,但对一个给定的文件只有一个v节点表项。每个进程都有自己的文件表项的一个理由是:这种安排使每个进程都有它自己的对该文件的当前偏移量。
给出了这些数据结构后,现在对前面所述的操作做进一步说明
1.在完成每个write后,在文件表项中的当前文件偏移量即增加所写的字节数。如果这使当前文件偏移量超过了当前文件长度,则在i节点表项中的当前文件长度被设置为当前文件偏移量(也就是该文件加长了)。
2.如果已append标志打开了一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有填写标志的文件执行写操作时,在文件表中的当前文件偏移量首先被设置为i节点表项中的文件长度。这使得每次写的数据都添加到文件的当前尾端处。
3.若一个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为i节点表项中的当前文件长度。
4.lseek函数只修改文件表项中的当前文件偏移量,没有进行任何I/O操作
可能有多个文件描述符项指向同一个文件表项。我们就能看到这一点。在fork后也会发生同样的情况,次时父,子进程对于每一个打开文件描述符共享同一个文件表项。
注意,文件描述符标志和文件状态标志在作用域方面的区别,前者只用于一个进程的一个描述符,而后者则适用于指向该给定文件表项的任何进程中的所有描述符。
对于多个进程读同一个文件都能正确工作。每个进程都有它自己的文件表项,其中也有它自己的当前文件偏移量。但是,当多个进程同时写同一个文件时,则可能产生预期不到的结果。
我们会在单独的文章中,描述多个个进程写的情况。