作为文件系统的抽象,它是IO中最基本的概念,所有的磁盘操作都是基于块进行的。
操作系统效率随着系统调用次数的增多而急剧下降。每次读写操作字节数太少,会导致读写次数增多而降低效率;每次读写不是块大小的整数倍,也会因为对齐数据而降低效率。
系统调用stat可以轻松指定设备的块大小。
现实中程序很少以块为单位进行操作,程序往往是以区域、行和单个字符为单位进行操作。为了改善这种情况,程序使用用户缓冲IO。当数据写入时,它会被存储在程序地址空间的缓冲区中。当缓冲区达到一个给定的值,整个缓冲区会在一次操作中被写出。同理,读操作一次读入缓冲区大小且块对齐的数据。当应用程序执行不对齐的读请求时,缓冲区一块一块的给出数据。最后,当缓冲区为空时,另一个大的块对齐的区域又被读入。
一个程序究竟应该使用标准IO或是自创的用户缓冲,还是直接使用系统调用,这些都需要设计者仔细权衡程序的需求和行为后决定。
在标准IO中,一个打开的文件叫做“流”(其实流就是一个指向FILE的指针??)。
文件通过fopen打开以供读写操作。参数mode描述以怎样的方式打开指定文件。它可以是以下的字符串之一:r、r+、w、w+、a、a+。
函数fdopen将一个已经打开的文件描述符转成一个流。
fclose函数关闭一个给定的流。fcloseall函数关闭所有的和当前进程相关联的流,包括标准输入、标准输出、标准错误。
C标准库实现了多种从流中读取数据的方法。其中最常用的是三种:单字节的读取、单行的读取、和二进制数据的读取。
fgetc可以用来从流中读取单个字符。这个函数从流中读取下一个字符并把该无符号字符强制转换为int型返回。ungetc允许程序员偷窥流,如果你不需要该字符时,可以把它放回。如果多个字符被放入流中,它们会以倒序的方式返回。
函数fgets从一个给定的流中读取一个字符串。这个函数从流中读取size-1个字符到str中,当所有字符读入时,空字符被存入字符串末尾。当读到EOF或者换行符时读入结束。如果读到一个换行符,“\n”被存入str。
调用fread用来读二进制数据。
所有的机器设计都有数据对弃的要求。C变量的存储和访问都要是地址对齐的。在处理结构体,手动执行内存管理,向磁盘存储二进制数据,进行网络通信时,对齐都非常重要。
函数fputc用来写入单个字符。函数fputs用来往给定的流中写入一个完整的字符串。fwrite函数用来写入二进制数据。
函数fseek是标准IO最常用的定位函数,操纵流指向文件中由offset和whence指定的位置。函数fsetpos将流的位置设置到pos处。函数rewind将位置重置到流的初始位置,并且清空错误标记。ftell函数返回当前流的位置。标准输入输出还提供了fgetpos函数,成功时,fgetpos返回0,并且将当前流的位置设置为pos。
标准IO库提供了fflush函数,将用户缓冲区写入内核,并且保证所有的数据都通过write写出。如果stream是空的,所有进程打开的流会被清洗掉。
本章所提到的所有调用的缓冲区都是通过C函数库来维护的,他们保留在用户空间中,而不是内核空间。这样可以提高效率,程序保留在用户空间中,并且运行用户的代码,不执行系统调用。只有当磁盘或者其他介质必须被访问时系统调用才会被执行。fflush只是把用户缓冲的数据写入到内核缓冲区。这并不保证数据能够写入物理介质。
函数ferror测试是否在流上设置了错误标志,错误标志由其它响应错误的标准IO函数设置。函数feof测试文件结尾标志是否被设置。函数clearerr为流清空错误和文件结尾标志。
函数fileno用来获得流的文件描述符。
标准IO实现了三种用户缓冲,而且为开发者提供了一个用来控制缓冲区大小和类型的接口:
不缓冲:没有执行用户缓冲,数据直接提交到内核。标准错误默认是不缓冲的。
行缓冲:缓冲以行为单位执行。
块缓冲:缓冲以块为单位执行。默认的所有和文件相关的流都是块缓冲的。标准IO称块缓冲为全缓冲。
函数setvbuf设置流的缓冲类型模式。
线程的定义是共享同一地址空间的多个进程。如果不采取数据同步措施或将数据线程私有化,线程可以任何时间修改共享数据。支持线程的操作系统提供加锁机制(保证相互排斥的程序结构)来保证线程不会互相干扰。
标准IO的函数本质上是线程安全的。在内部实现中,设置了一把锁,一个锁计数器,和为每个打开的流创建的所有者线程。一个线程要想执行任何IO请求,必须首先获得锁而且成为所有者线程。
当然在实际应用中,许多应用程序需要比单独的函数调用更强的原子性。
函数flockfile会等待流被解锁,然后获得锁,增加锁计数,成为流的所有者线程,然后返回。
函数funlockfile减少与流相关的锁计数。
如果锁计数器达到了0,当前的线程放弃流的所有权,另一个线程现在能获得锁。
函数ftrylockfile是flockfile的非堵塞版本。如果流当前加了锁,ftrylockfile不做任何处理,并立即返回一个非零值。如果流当前没有加锁,它获得锁,增加锁计数,成为流的所有者线程,并且返回0。
linux提供了一些不加锁的标准IO函数,这些函数类似通常的标准IO函数,但是不执行任何锁操作。这样可以通过程序员的手动加锁,来提高运行的效率。