1. 文件类型

输入输出操作的对象为外部的硬件设备、内存、硬盘上面的文件等等,linux系统将这些输入输出操作的对象都抽象成文件的概念例如外部的硬件设备对应硬盘上的一个设备文件,所以真正输入输出操作的对象就是文件。

linux(和UNIX)的文件类型:

(1)       普通文件:一些常规文件、可执行程序和任何其它你想要的东西。

(2)       目录

(3)       符号链接文件:文件中的内容是所指向的文件全路径,不包括字符串结束字符’\0’。

下面的两种文件类型是设备文件类型,对于外部的硬件设备或虚拟的软件设备,都抽象成一个个文件。设备文件有两种:

(4)       字符设备文件:例如像终端,有一些字符设备可以绕过内核缓冲区,在一次I/O操作中输入输出少许的字节数。

(5)       块设备文件:进行I/O操作的物理记录是块大小,即一次可以进行多个字节的I/O。例如磁盘驱动器和磁带驱动器,对这些设备的访问要经过内核的缓冲器。

(6)       有名管道:进程间通信时用到管道,管道有两种:无名管道、有名管道。无名管道存在于内核中,在磁盘上看不到对应的文件,主要是用于有祖先关系的进程之间进行通信(例如兄弟进程、父子进程等)。如果两个进程之间没有亲缘关系要实现通信,可以通过有名管道,有名管道是一个存在于磁盘上的文件,当两个没有亲缘关系的进程要实现通信,可以各自以读或写的方式打开该磁盘上的有名管道文件,对该有名管道文件的写入内容并不会写到磁盘上,而只是写入内核缓存。

(7)       socket(套接字):可用于进程间通信,例如unix域套接口绑定的地址结构就是文件名,该文件类型就是socket。对于系统日志守护进程syslogd也会创建一个这种类型的文件,然后其他应用程序调用syslog函数的时候,其实就是利用这个设备和syslogd在通信。

2. I/O模型

(1)阻塞I/O:若等待数据未准备好,进程进入睡眠等待状态。

(2)非阻塞I/O:若等待数据未准备好,立即返回EWOULDBLOCK或EAGAIN错误。

(3)I/O复用(I/O multiplex):通过select和poll函数来实现I/O多路复用,可以同时处理多个描述字的I/O,并且可以设置I/O操作的超时时间。

(4)信号驱动:设置描述符为非阻塞I/O,这样每当有数据需要I/O,就会发送SIGIO信号给I/O进程,而对于I/O进程可以捕捉SIGIO信号,在信号处理中进行I/O。

几种I/O模型的比较:I/O复用可以同时处理多个描述符的I/O,提高了I/O的吞吐量、处理效率,信号驱动属于异步I/O处理,虽然也提高了I/O的吞吐量、处理效率,但是相对于I/O复用(I/O multiplex)它不可以同时处理多个描述符的I/O。而且信号驱动由于属于异步I/O机制,所以要在用户层设置一个I/O缓冲区,需要将所要读写的数据预先放入该缓冲区,我们可以将信号驱动的I/O操作抽象成是两个线程在并发执行,其中一个线程执行I/O操作即担当信号处理进行I/O操作 的工作,另一个线程就是原先的进程,(虽然信号处理也是在同一个进程环境中执行,不过为了便于理解,我们可以这样抽象形容,对于异步操作是可以这样抽象形容)。由于是异步操作(两个并发线程操作)涉及到公共缓冲区的读写操作,所以需要对该公共缓冲区设置互斥锁,对于信号驱动通过设置信号屏蔽来代替互斥锁。

前面三个I/O模型是同步I/O操作模型,信号驱动模型是异步I/O操作模型。

采用I/O复用模型注意的问题:(1)当复用模型用于处理多个描述符的I/O时,当其中的一个描述符出现错误要返回时,应注意其它描述符是否完成了I/O操作,若未完成,则不能退出,要继续其它描述符的I/O操作。(2)若其中的一个文件描述符因为I/O而进入阻塞,从而使其它描述符的I/O操作停滞不前,为了解决这个问题,可以采取1.设置描述符为非阻塞I/O。 2.对I/O操作设置超时。(可参看《unix网络编程》P142”拒绝服务型攻击”)

采用信号驱动I/O模型注意的问题:(1)当涉及到对公共缓冲区进行读写操作时,为了维护数据的一致性,需要设置信号屏蔽。

3. 设置I/O超时(《unix网络编程》第13 高级I/O函数)

(1)调用alarm,当到达alarm所指定的超时时间,就会向本进程发送SIGALRM信号,中断I/O操作,使I/O操作返回EINTR错误。

(2)使用select多路复用函数来设置超时。

(3)对于插口描述符,通过插口选项SO_SNDTIMEO、SO_RCVTIMEO设置I/O超时时间。

如果要为某个描述符的多次I/O操作(不是一次I/O操作)设置一个超时,如果用SIGALRM信号来解决时,要注意信号是代码执行到哪个地方才发生信号递送(可参看《unix网络编程》图18-6)。

4. I/O系统调用函数

输入输出操作所调用的函数有read、write、readv、writev、recv、send、recvfrom、sendto、recvmsg、sendmsg,前面四个I/O系统调用函数read、write、readv、writev对所有的描述字符都是适用的,而后面的recv、send、recvfrom、sendto、recvmsg、sendmsg系统调用函数只适用于网络插口描述符的I/O操作。对于recv、send、recvfrom、sendto、recvmsg、sendmsg函数都有一个flag标志,对于该标志的用法可参看《unix网络编程》图13-7。

对于标准输入输出函数scanf、printf、getchar、putchar、gets、puts等等是输入输出在应用层的扩展,它封装了上面所描述的底层系统调用函数,引入了缓存机制。

5.        设置描述符的I/O属性

对于描述符的I/O属性,例如阻塞式还是非阻塞式、异步I/O、设置SIGIO信号的接收者,可以通过fcntl、ioctl、setsockopt(设置插口选项只针对插口描述符,其它一般的文件描述符不适用)来设置。

fcntl函数:fcntl(fd,F_SETFL,O_ASYNC)设置异步I/O方式。fcntl(fd,F_SETOWN,pid)设置SIGIO或SIGURG信号的接收者,pid>0接收者为一个进程pid,pid<0接收者为进程组|pid|。

ioctl函数:ioctl(fd,FIONBIO,1)设置非阻塞I/O,ioctl(fd,FIOASYN,1)设置异步I/O。ioctl(fd,FIOSETOWN,pid)设置SIGIO或SIGURG信号的接收者,pid>0接收者为一个进程pid,pid<0接收者为进程组|pid|。如果是插口描述符,还可以通过ioctl(fd,SIOCSPGRP,&gid)来设置SIGIO或SIGURG信号的接收进程组gid。