一、文件IO
大多数UNIX文件IO只需用到5个函数:open、read、write、lseek以及close。
这些函数经常被称为不带缓存的IO(unbuffered IO),不带缓存是指每个read和write都调用内核中的一个系统调用。
这些不带缓存的IO函数不是ANSI C的组成部分,但是是POSIX.1和XPG3的组成部分。
1、对于内核而言,所有打开文件都由文件描述符引用。
2、由open返回的文件描述符一定是最小的未用描述符数字。
3、可用close函数关闭一个文件,关闭一个文件时也释放该进程加在该文件上的所有记录锁。
当一个进程终止时,它所有的打开文件都由内核自动关闭。
4、每个文件都有一个与其相关联的“当前文件位移量”,用以度量从文件开始处计算的字节数。
可以调用lseek显式地定位一个打开文件。
5、用read函数从打开文件中读取数据。如read成功,则返回读到的字节数,如已到达文件的尾端,则返回0。
6、用write函数向打开文件写数据,若成功返回已写的字节数,若出错为-1。
对Unix内核而言,文本文件和二进制代码文件并无区别。
7、IO效率
当Buffersize定义为文件系统的快长时,系统CPU时间最小,继续增加缓存长度对此文件并无影响。
二、文件共享
1、Unix支持在不同进程间共享打开文件。内核使用了三种数据结构。
1)每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表。与每个文件描述符相关联的是:
文件描述符标志
指向一个文件表项的指针
2)内核为所有打开文件维持一张文件表,每个文件表项包含:
文件状态标志(读、写、增写、同步、非阻塞等)
当前文件位移量
指向该文件v节点表项的指针
3)每个打开文件(或设备)都有一个v节点结构。v节点包含了文件类型和对此文件进行各种操作的函数的指针信息。
2、这三张表的关系对于在不同进程之间共享文件的方式非常重要。
3、文件描述符标志和文件状态标志在作用范围方面的区别,前者只用于一个进程的一个描述符,而后者则适用于指向该给定文件表项
的任何进程中的所有描述符。
三、原子操作以及文件控制
1、打开文件时设置O_APPEND标志。
2、dup和dup2函数可以用来复制一个现存的文件描述符。
3、fcntl函数可以改变以及打开文件的性质。
4、ioctl函数是IO操作的杂物箱。不能用其他函数表示的IO操作通常都能用ioctl表示。
终端IO是ioctl的最大使用方面。
5、比较新的系统都提供名为/dev/fd的目录,其目录项名为0、1、2等的文件。打开文件/dev/fd/n等效于复制描述符n。
四、标准IO库
1、标准IO库,不仅在Unix而且在很多操作系统上都实现此库,它由ANSI C标准说明。
标准IO库处理很多细节,例如缓存分配,以优化长度执行IO等。
2、对于标准IO库,它们的操作是围绕流进行的。当用标准IO库打开或创建一个文件时,我们已使一个流与一个文件相结合。
当打开一个流时,标准IO函数fopen返回一个指向FILE对象的指针。
该对象通常是一个结构,包含了:用于实际IO的文件描述符,指向流缓存的指针,缓存长度,当前缓存字符数,出错标志等。
为了引用一个流,需将FILE指针作为参数传递给每个标准IO函数。
五、缓存
1、标准IO提供缓存的目的是尽可能减少使用read和write调用的数量,它也对每个IO流自动地进行缓存管理。
2、标准IO提供了三种类型的缓存:
1)全缓存。在这种情况下,当填满标准IO缓存后才进行实际IO操作。对于驻在磁盘上的文件通常是由标准IO库实施全缓存的。
术语flush说明标准IO缓存的写操作。
2)行缓存。在这种情况下,当在输入和输出中遇到新行符时,标准IO库执行IO操作。
当流涉及一个终端时(例如标准输入和标准输出),典型地使用行缓存。
3)不带缓存。标准IO库不对字符进行缓存。如果用标准IO函数写若干字符到不带缓存的流中,则相当于用write系统调用
函数将这些字符写到相关联的打开文件上。
标准出错流stderr通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新行字符。
3、ANSI C要求下列缓存特征:
1)当且仅当标准输入和标准输出并不涉及交互作用设备时,它们才是全缓存的。
2)标准出错绝不会是全缓存的。
4、可以通过setbuf和setvbuf函数来更改缓存类型。
5、任何时候,我们都可以使用函数fflush强制刷新一个流。
此函数使该流所有未写的数据都被传递至内核。
六、流
1、打开流,有如下三个函数:
fopen、freopen、fdopen
2、调用fclose函数关闭一个打开的流。
3、一旦打开了流,则可在三种不同类型的非格式化IO中进行选择,对其进行读、写操作。
1)每个一个字符的IO。一次读或写一个字符,如果流是带缓存的,则标准IO函数处理所有缓存。
2)每次一行的IO。使用fgets和fputs一次读或写一行。每行都以一个新行符终止。
3)直接IO。fread和fwrite函数支持这种类型的IO。
4、输入函数
以上三个函数可用于一次读一个字符:
getc、fgetc、getchar getchar等同于getc(stdin)
不管是出错还是到达文件尾端,这三个函数都返回同样的值。为了区分这两种不同的情况,必须调用ferror或feof。
在大多数实现的FILE对象中,为每个流保持了两个标志:
出错标准、文件结束标志
调用clearerr则清除这两个标志。
5、从一个流读之后,可以调用ungetc将字符再送回流中。EOF不能回送。
6、输出函数
对应于上面所述的每个输入函数都有一个输出函数:
putc、fputc、putchar putchar(c)等同于putc(c, stdout)
7、每次一行IO
下面两个函数提供每次输入一行的功能:
fgets、gets
下面两个函数提供每次输入一行的功能:
fputs、puts
七、标准IO的效率
1、系统调用与普通的函数调用相比是很花费时间的。
2、标准IO库与直接调用read和write函数相比并不慢很多。
八、二进制IO
使用下列两个函数执行二进制IO操作:
fread、fwrite
九、定位流
1、有两种方法定位标准IO流
1)ftell和fseek。
2)fgetpos和fsetpos。
十、格式化IO
1、执行格式化输出处理的是三个printf函数:
printf 将格式化数据写到标准输出
fprintf 写到指定的流
sprintf 将格式化的字符送入数组buf中
2、执行格式化输入处理的是三个scanf函数:
scanf
fscanf
sscanf
十一、实现细节
1、在Unix中,标准IO库最终都要调用IO例程。每个IO流都有一个与其关联的文件描述符,可以通过fileno函数获得其描述符。
2、标准IO库提供了两个函数以帮助创建临时文件
tmpnam、tmpfile
十二、总结
东西比较多也比较杂,但是保持一个清晰的头脑是最重要的。
多思考一下遇到的问题,以及如何解决这些问题,可以明白现在的实现机制已经是多么好的一种方式了。