天天看点

Linux文件IO(二)标准IO

块做为文件系统的抽象,它是I/O 中最基本的概念——所有的磁盘操作都是基于块进行的。因此,当请求以块大小整数倍对齐地址时, I/O 效率是最理想的。操作效率随着系统调用次数的增多而急剧下降,例如,每次读一字节读1024次与一次读1024字节相比,显然后者效率更优。如果长度不是block的整数倍,即使每次以大于块的长度进行一系列的操作,其效率也不是最理想的。例如块的大小是1K ,每次以1130字节的长度操作数据要比每次1024字节的速度慢。

用户态缓冲I/O

需要对普通文件执行许多轻量级I/O请求的程序通常使用用户缓冲I/O。用户缓冲I/O是在用户空间而不是在内核中完成的,它可以在程序中设定,也可以调用标准库透明地执行。实际应用中,块大小一般是512字节,1024字节,2048字节,或4096字节。效率的大规模提升只是通过将每次操作的数据设置为块大小整数倍或约数获得的。通过用系统调用stat()可以轻松指定设备的块大小。

但问题是程序很少以块为单位进行操作。程序往往以区域,行,和单个字符为单位进行操作,而不是抽象的块。如前所述,为了改善这种情况,程序使用用户缓冲I/O。当数据被写入时,它会被存储在程序地址空间的缓冲区中。当缓冲区规模达到一个给定的值(缓冲区大小时),整个缓冲区会在一次操作中被写出。

C标准库中提供了标准I/O库(通常简单称作stdio),其中实现了一个跨平台用户缓冲的解决方案。标准I/O例程并不直接操作文件描述符。取而代之的是它们用自己唯一的标志符,即大家熟知的文件指针(filepointer)。在C标准库里,文件指针映射到一个文件描述符。文件指针由FILE类型的指针表示,FILE类型定义在<stdio.h>中。在标准I/O中,一个打开的文件叫做”流”(stream)。流可以被打开用来读(输入流),写(输出流),或者二者兼有(输入输出流)。

打开文件

FILE fopen(const char path, constchar mode);

FILE fdopen(int fd, const char* mode);

关闭流

int fclose(FILE* stream);

int fcloseall(void);

从流中读取数据

int fgetc(FILE stream);

int ungetc(int c, FILE stream);

char fgets(char str, int size, FILE stream);

size_t fread(void buf, size_t size, size_t nr, FILE* stream);

向流中写数据

数据对齐所有的机器设计都有数据对齐的要求。程序员倾向于把内存想成一个简单的字节数组。但是处理器并不以字符大小对内存进行读写。相反,处理器以特定的粒度(例如2,4,8或16字节)来访问内存。因为每个处理的地址空间都从0地址开始,进程必须从一个特定粒度的整数倍开始读取。因此,C变量的存储和访问都要是地址对齐的。用另一种说法就是一个int需要被存储在能被4整除的内存地址中。访问不对齐的数据在不同的体系结构上有不同程度的性能损失。一些处理器能够访问不对齐的数据,但是会有一个很大性能损失。有些的处理器根本不能够访问非对齐的数据,而且企图这么做会导致硬件异常。更糟的是,一些处理器为了强制地址对齐会丢弃了低位的数据,从而导致不可预料的行为。

int fputc(int c,FILE stream);

int fputs(const char str,FILE stream);

size_t fwrite(void buf, size_t size, size_t nr, FILE* stream);

定位流

int fseek(FILE stream, long offset, int whence);

int fsetpos(FILE stream, fpos_t pos);

int fgetpos(FILE stream, fpos_t pos);

long ftell(FILE stream);

清洗一个流

效率提高的原因——程序保留在用户空间中,并且运行用户的代码,不执行系统调用。只有当磁盘或其它介质必须被访问时系统调用才会被执行。fflush()只是把用户缓冲的数据写入到内核缓冲区。效果和没有用户缓冲区一样,而且write()是被直接调用的。但这并不保证数据能够写入物理介质——如果需要的话,使用fsync()这一类函数。

int fflush(FILE* stream); // 将用户缓冲区写入内核

错误和文件结束

一些标准I/O接口,例如fread(),向调用者传递失败信息的能力很差,因为它们没有提供区分错误和EOF的机制。在一些场合中调用这些函数时,需要区分给定的流出现了错误还是到达了文件结尾。标准I/O为此提供了两个函数。函数ferror()测试是否在流上设置了错误标志:

int ferror(FILE stream);

int feof(FILE stream);

void clearerr(FILE* stream);

获得关联的文件描述符

int fileno(FILE* stream);

控制缓冲

标准I/O实现了三种用户缓冲,而且为开发者提供了一个用来控制缓冲区大小和类型的接口。不同的用户缓冲提供不同功能,并适用于不同的场合。下面是一些选项:

不缓冲 没有执行用户缓冲。数据直接提交到内核。因为这和用户缓冲对立,这个选项通常不用。标准错误默认是不缓冲的。

行缓冲 缓冲以行为单位执行。每当遇到换行符,缓冲区被提交到内核。行缓冲对输出到屏幕的流有用。因此,它是终端的默认缓冲方式(标准输出默认为行缓冲)。

块缓冲 缓冲以块为单位执行。这是本章一开始讨论的缓冲类型,而且它适用于文件。默认的所有和文件相关的流都是块缓冲的。标准I/O称块缓冲为全缓冲。

setbuf()函数设置流的缓冲类型模式:

int setvbuf(FILE stream, char buf,int mode,size_t size);

线程安全

线程就是在同一个进程中执行的多个实例。线程的定义是共享同一地址空间的多个进程。如果不采取数据同步措施或将数据线程私有化,线程可以任何时间修改共享数据。支持线程的操作系统提供加锁机制(保证相互排斥的程序结构)来保证线程不会互相干扰。标准I/O使用这些机制。而且,这些机制通常还不能满足需求。例如,有时候你想给一组调用加锁,将临界区(一段独立运行的代码)的范围从一个I/O操作扩大到几个。而有些情况下,你可能想取消锁机制来提高效率。标准I/O的函数本质上是线程安全的。

POSIX费线程安全函数:

http://kimi.it/506.html

标准I/O为独立操纵和流关联的锁提供了一系列的函数。通常,取消锁会导致各种各样的问题。但一些程序可能显式地将所有的I/O操作都由一个线程来完成。在这种情况下,没有必要增加锁的开销。

void flockfile(FILE stream);

void funlockfile(FILE stream);

int ftrylockfile(FILE stream);

Linux提供了一系列的函数,类似于通常的标准I/O接口,但是不执行任何锁操作。他们实际上是不加锁的标准I/O:除了它们不检查或获得指定流上的锁,这些函数与相对的加锁函数执行相同操作。

fgetc_unlocked、fgets_unlocked、fread_unlocked、fputc_unlocked、fputs_unlocked、fwrite_unlocked、fflush_unlocked、feof_unlocked、ferror_unlocked、fileno_unlocked、clearerr_unlocked

继续阅读