【APUE】文件I/O
Linux的内核将所有外部设备都可以看做一个文件来操作。那么我们对与外部设备的操作都可以看做对文件进行操作。我们对一个文件的读写,都通过调用内核提供的系统调用;内核给我们返回一个file descriptor(简称:fd,文件描述符).
系统调用是如何完成一个I/O操作的呢? linux将内存分为内核区,用户区; linux内核给我们管理所有的硬件资源,应用程序通过调用系统调用和内核交互,达到使用硬件资源的目的;应用程序通过系统调用read发起一个读操作;这时候内核创建一个文件描述符,并通过驱动程序向硬件发送读指令,并将读的的数据放在这个描述符对应结构体的缓存区。但这个结构体是在内核内存区的。需要将这个数据读到用户区,这样完成了一次读操作.
读就绪:文件描述符的接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小;
写就绪:该描述符发送缓冲区的可用空间字节数大于等于描述符发送缓冲区低水位标记的当前大小。
从上边的分析我们知道向内核发起一个I/O操作,要经过等待fd就绪+内核数据到用户数据区复制,完成一次I/O。
各种I/O模型比较:
1、阻塞I/O:进程被阻塞在I/O系统调用上(read、write、sendto、recvfrom等等)直到I/O条件就绪(可读/写或异常),处理后从I/O系统调用返回进程。
2、非阻塞I/O :这种方式通过指定系统调用read/write的参数为非阻塞,告知内核fd没就绪时,不阻塞进程,而是返回一个错误码,这时应该使应用进程死循环轮询,直到fd就绪
3、I/O复用( I/O 多路转接模型):在这种模型下,如果请求的 I/O 操作阻塞,则它不是真正阻塞进程,而是让其中的一个函数等待,在这期间,进程还能进行其他操作。
4、多线程阻塞I/O。把要处理的描数字分配到多个线程中,每个线程独立地调用I/O系统调用,独立处理。
5、异步I/O。通过aio_read告诉内核怎么处理,我们就不管了,内核处理完了后发个信号告诉我们,也可以用其他方式在处理完通知我们。
注:
对普通文件的读写不存在阻塞问题,对和终端、网络连接等对应的文件描述符才会阻塞
阻塞操作是指,在执行设备操作时,若不能获得资源,则进程挂起直到满足可操作的条件再进行操作。非阻塞操作的进程在不能进行设备操作时,并不挂起。被挂起的进程进入sleep状态,被从调度器的运行队列移走,直到等待的条件被满足。
同步I/O模型:第1~4 I/O模型。这5种I/O模型,说到底,I/O调用还是要用户进程自己调用,被阻塞。
异步I/O模型:第5种I/O模型。I/O调用由内核执行,进程不被阻塞。
文件描述符
对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读或写一个文件时,使用open或create返回的文件描述符标识该文件,将其作为参数传送给read或write。
按照惯例,UNIX系统shell使用文件描述符0与进程的标准输入相关联,文件描述符1与标准输出相关联,文件描述符2与标准错误输出相关联。
在依从POSIX的应用程序中,0,1,2应当替换为符号常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO.这些常量定义在<unistd.h>中。
open函数:打开或创建一个文件
#include <fcntl.h>
int open(const char *pathname,int oflag,...);//若成功则返回文件描述符,若出错返回-1
pathname是要打开或创建文件的名字,oflag参数用来说明此函数的多个选项
由open返回的文件描述符一定是最小的未用描述符数值
create函数:创建文件
#include <fcntl.h>
int creat(const char *pathname,mode_t mode);//若成功则返回文件描述符,若出错返回-1
mode为文件访问权限,create以只写方式打开所创建的文件
close函数
#include <unistd.h>
int close(int filedes);//若成功则返回0,若出错则返回-1
当一个进程终止时,内核自动关闭它所打开的文件,很多程序利用了这一功能而不是显式地用close关闭文件
lseek函数
每个打开的文件都有一个与其相关联的当前文件偏移量,用以度量从文件开始处计算的字节数,通常,读写操作都从当前文件偏移量处开始。当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0.调用lseek函数显式地为一个打开的文件设置偏移量
#include <unistd.h>
off_t lseek(int fieldes,off_t offset,int whence)//若成功则返回新的文件偏移量,若出错则返回-1
对offset的解释与参数whence值有关。
1.若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节
2.若whence是SEEK_CUR,则将该文件的偏移量设置为其当前值加offset,offset可正可负
3.若whence是SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可正可负
确定打开文件的当前偏移量:
off_t currpos;
currpos=lseek(fd,o,SEEK_CUR);
可用上述方法来确定所涉及的文件是否可以设置偏移量。如果文件描述符引用的是一个管道、FIFO或网络套接字,则lseek返回-1,并将errno设置为ESPIPE(why?)
lseek仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操作。文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,这一点是允许的,位于文件中但没有写过的字节都被读为0.文件中的空洞并不要求在磁盘上占用存储区。
read函数:从打开文件中读数据
#include <unistd.h>
ssize_t read(int filedes,void *buf,size_t nbytes);//nbytes为要求读的字节数
如read成功,则返回读到的字节数,如已到达文件结尾,则返回0,若出错,则返回-1
write函数:向打开的文件写数据
#include <unistd.h>
ssize_t write(int filedes,const void *buf,size_t nbytes);//若成功则返回已写的字节数,若出错则返回-1
该函数返回值通常与参数nbytes的值相同,否则表示出错。
参考:
http://blog.sina.com.cn/s/blog_4697cdcd0100s3uh.html
APUE第三章