标签:
UNIX将系统所有的内容都视为文件,其中文件类型包括如下几种:
所有的操作系统都提供多种服务的入口点,由此程序向内核请求服务。各种版本的UNIX实现都提供良好定义、数量有限、直接进入内核的入口点,这些入口点被称为系统调用(system call)。库函数是指为了迎合程序员使用,而编制的通用库函数,虽然这些函数可能会调用一个或多个内核的系统滴啊用,但是它们并不是内核的入口点。例如,printf函数会调用write(系统调用)输出一个字符串。
从实现者的角度来看,系统调用和库函数之间有根本的区别,但从用户角度来看,其区别并不重要。但是需要注意的是:我们可以替换库函数,而通常不能替换系统调用。
在UNIX下可用如下的几种I/O模型:
阻塞式模型I/O是指当调用此类函数时,就会发生阻塞,直至函数返回。如下图所示:
阻塞式模型又可以分为两种模型:不带缓冲的函数和带缓冲的函数(标准IO)。
不带缓冲是指每个read和write都调用内核中的一个系统调用。其中有:open、read、write、lseek以及close。这些不带缓冲的I/O函数不是ISO C的组成部分,而是POSIX.1和Single UNIX Specification的组成部分。
标准I/O库是一类带缓冲的函数,其是ISO C的标准,标准I/O库是对上述的不带缓冲的系统调用函数进行封装和调用,其不属于UNIX中的系统函数,但标准I/O不仅在UNIX中能得到使用,在其它系统也能使用。
(一)缓冲
标准I/O提供了3种类型的缓冲:
1) 全缓冲: 即只在填满缓冲区后才进行实际I/O操作。
对于驻留在磁盘上的文件通常是全缓冲的,
2) 行缓冲:是指当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。
这允许我们一次输出一个字符,但只有在写了一行之后才进行实际I/O操作。当涉及一个 终端时(如标准输入和标准输出),通常使用行缓冲。
3) 不带缓冲:标准I/O库不对字符进行缓冲存储,即马上进行I/O操作。
例如标准I/O函数fputs写15个字符到不带缓冲的流中,我们就期望这15个字符立即输出。其中标准错误流stderr通常是不带缓冲的。
小结:标准错误时不带缓冲的,打开至终端设备的流是行缓冲的,其他流是全缓冲的。
(二)标准I/O函数
标准I/O函数也是先打开(open)对流文件,然后进行读或写,其中一旦打开了流,则可进行3中不同类型的非格式化I/O操作:
系统调用分成两类:"低速"系统调用和其它。但低速的系统调用可能会使进程永远阻塞的一类系统调用,包括:
非阻塞I/O是指我们发出的open、read和write等I/O操作,并使这些操作不会永远阻塞。如果这样的操作不能完成,则调用立即出错返回,表示该操作如继续执行将阻塞。如下图所示:
前三次调用recvfrom时没有数据可返回,因此内核立即返回一个EWOLDBLOCK错误,第四次调用recvfrom时已有一个数据报准备好,它被复制到应用进程缓冲区,于是recvfrom成功返回。
对于给定的描述符(文件描述符),有两种为其指定费阻塞I/O的方法:
非阻塞式I/O可以应用到管道、FIFO、套接字、终端、伪终端以及其他一些类型的设备上。其中的文件描述符可以是由open函数的返回值,也可以是网络编程的socket值,如《UNP》343对TCP设置为非阻塞的方式:
I/O多路复用是指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。主要由select,poll和epoll来实现这种I/O多路复用的机制。但select,poll,epoll本质上都是同步I/O(即会阻塞等待),因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
其中需先了解两种通知模式
如下表总结了I/O多路复用、信号驱动I/O以及epoll所采用的通知模型:
I/O模式 |
水平触发 |
边缘触发 |
select(),poll() |
l |
|
信号驱动I/O |
l | |
epoll() |
l |
l |
select函数关注的问题是:在指定的时间内所关心的描述符集是否准备就绪。
poll的功能和select类似,只是修改了select的函数原型,poll不是为每个条件(可读性、可写性和异常条件)构造一个描述符集,而是构造一个pollfd结构的数组,每个数组元素指定一个描述符编号以及我们对该描述符感兴趣的条件。
pollfd结构
poll函数原型为:
poll和select共同的问题是性能较差:遍历所有的文件描述符,当监听描述符个数增加时,监听效率降低,并且select和poll每次都要在用户态和内核态拷贝监听的描述符参数。
epoll是Linux所独有的,所以epoll的移植性没有select和poll好,但epoll既支持水平模式,又支持触发模式。
epoll API的核心数据结构称作epoll实例,它和一个打开的文件描述符相关联。通过这个描述符实现如下的目的:
epoll API由一下3个系统调用组成。
1) epoll_create函数:创建epoll实例
参数size指定了内部数据结构的划分初始大小,而不是最大上限(从Linux2.6.8版就忽略该值)。
2) epoll_ctl函数:修改epoll兴趣列表
参数fd指定感兴趣列表中的哪一个文件描述符的设定。op值可以是EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL分表对epoll兴趣列表进行增加、修改和删除操作。ev是指向epoll_event的指针,结构体的定义如下:
其中的data是如下的一个联合体类型:
其中ev为文件描述符fd所做的设置如下:
3) epoll_wait函数:事件等待
系统调用epoll_wait()返回epoll实例中处于就绪状态的文件描述符信息。单个epoll_wait()调用能返回多个就绪态文件描述符的信息。
evlist所指向的结构体数组中返回的就绪文件描述符信息,maxevents是指定的evlist数组的大小。
在数组evlist中,每个元素返回的都是单个就绪态文件描述符的信息。events字段返回了在该描述符上已经发生的事件掩码。data字段返回的是我们在描述符上使用epoll_ctl()注册感兴趣的事件时在ev.data中所指定的值。
timeout用来确定epoll_wait()的阻塞行为,有如下几种。
epoll解决了select和poll的几个性能上的缺陷:
信号驱动I/O是指请求数据的进程可以利用信号,向内核声明一个信号处理例程,让内核在描述符就绪时发送SIGIO信号(默认情况下)通知进程;而进程在向内核声明后,能够处理其他的任务,当I/O操作可执行时通过接受信号来获得通知。如下图所示:
要使用信号驱动I/O,程序需要按照如下步骤来执行:
fcntl(fd, F_SETOWN, pid);
flags= fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags |ASYNC | NONBLOCK);
从历史上信号驱动I/O有时也称为异步I/O,但是,如今的异步I/O(POSIX AIO规范)机制是进程请求内核执行一次I/O操作,内核启动该操作之后立刻将控制权还给调用进程,并且当内核在整个操作完成(包括将数据从内核复制到进程的缓冲区)或错误发生时,该进程会得到通知。如下图所示:
其中信号驱动I/O与异步I/O的区别是:信号驱动I/O是由内核通知我们何时可以启动一个I/O操作(即何时就绪);而异步I/O模型是通知我们I/O操作何时完成。
存储映射I/O 能将一个磁盘文件映射到存储空间中的一个缓冲区上,于是,当从缓冲区中读取数据时,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区时,相应字节就自动写入文件。这样,就可以在不适用read和write的情况下执行I/O。
映射分为两种类型:
并且进程之间可以共享存储映射区域,所以又可以将映射的区域分为私有映射和共享映射:
为了使用这种功能,应首先告诉内核将一个给定的文件映射到一个存储区域中。这是由mmap函数实现的。
当进程终止时,会自动解除存储映射区的映射,货值直接调用munmap函数也可以解除映射区。
标签:
原文地址:http://www.cnblogs.com/hlwfirst/p/5004611.html