高级io(三)

2016-03-06  本文已影响172人  千里山南

2016-03-06

流ioctl操作

之前提到过ioctl函数,它能做其他io函数不能处理的事情。流系统中继续采用了该函数。
int isastream(int filedes)
该函数用于判断一个描述符是否引用一个流。它通常是用一个只对流设备才有效的ioctl函数来进行测试的。
ioctl(fd, I_CANPUT, 0) 测试由第三个参数说明的优先波段是否可写。如果该ioctl执行成功,则它对所涉及的流并未作任何改变。
当ioctl对并不引用特殊设备的描述符进行操作时,Unix内核都返回ENOTTY

如果ioctl的参数request是I_LIST则系统返回该留上所有模块名字,包括最顶端的驱动程序。其中第三个参数应当是指向str_list结构的指针。
struct str_list {
int sl_nmods;
struct str_mlist *sl_modlist;
}
应将sl_modlist 设置为指向str_mlist结构数组的第一个元素,将sl_nmods设置为该数组中的相数。
struct str_mlist {
char l_name[FMNAMESZ+1];
}
如果ioctl的第三个参数是0,则该函数返回的是模块数,而不是模块名。我们将先用这种方式确定模块数,然后再分配所要求str_mlist结构数。

write至流设备

write至流设备产生一个M_DATA消息。流中顶部的一个处理模块规定了可顺流传送的最小、最大数据包长度。如果。如果write的数据长度超过最大值,则流首将这一数据分解成最大长度的若干数据包。最后一个数据包的长度则小于最大值。对于管道FIFO,为与以前版本兼容,系统的默认处理方式是忽略0长write。可以用ioctl设置实现管道和FIFO的流写方式以更改这种默认处理方式。

写方式

可以用ioctl取得和设置一个流的写方式。如果将request设置为I_GWROPT,第三个参数为指向一个整形变量的指针,则该流的当前写方式就在该整形量中返回。如果将request设置为I_SWROPT第三个参数是一个整型值,则其值就是该流新的写方式。目前只定义了两个写方式值。

getmsg 和 getpmsg函数

int getmsg(int filedes, struct strbuf *ctlptr, struct strbuf *dataptr, int *flagptr);
int getpmsg(int filedes, struct strbuf *ctlptr, struct strbuf *dataptr, int bandptr, int *flagptr);
flagptr和bandptr是指向整型的指针,在调用之前,这两个指针所指向的整型单元中应设置成所希望的消息类型,在返回时,此整型量设置为所读到的消息类型。
如果flagptr指向的整型单元的值是0,则getmsg返回流首读队列中的下一个消息。如果下一个消息是高优先权消息,在返回时,flagptr所指向的整型单元设置为RS_HIPRI。如果希望只接受高优先权消息,则在调用getmsg之前必须将flagptr所指向的整型单元设置为RS_HIPRI
getpmsg使用了一个不同的常数集。并且它使用bandptr指明特定的优先波段。
这两个函数有很多条件来确定返回给调用者何种消息:flagptr和bandptr所指向的值;流队列中消息的类型;是否指明非空dataptr和ctlptr;ctlptr->maxlen和dataptr->maxlen的值。

读方式

如果读到流中消息的记录边界将会怎样?如果调用read而流中下一个消息有控制信息又将如何?对第一种情况的默认处理方式被称为字节流方式。在这种方式中,read从流中取数据直至满足了要求,或已经没有数据。在这种方式中,忽略流中消息的边界。对第二种情况的默认处理是,如果在队列的前端有控制消息,则read出错返回。可以改变两种默认处理方式。
调用ioctl时,若将request设置为I_GRDOPT,第三个参数又是一个子项一个整型单元的指针,则对该流的当前读方式在该整型单元中返回。如果request设置为I_SRDOPT则设置读方式
读方式有三种:

在读方式中还可以指定另外三个常数,以便设置在读到流中包含协议信息的消息时read的处理方法

IO多路转接

当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中使用阻塞io

while((n=read(STDIN_FILENO, buf, BUFSIZ)) > 0)
    if (write(STDOUT_FILENO, buf, n) != n)
        err_sys("write error");

这种形式的阻塞io很常见。但是如果必须读两个描述符时怎么处理?
对于有多个输入的设备,可以设置多个进程,每个进程处理一条数据通路。但这种情况会较多的进程间通信,使程序变得复杂。
另一种处理方式是非阻塞io读数据。其基本思想是将两个输入描述符都设置为非阻塞的,对第一个描述符发一个read。如果该输入上有数据,则读数据并处理它,如果无数据则read立即返回。然后对第二个描述符同样处理。这种轮询的方式会导致浪费CPU时间。
还有一种技术称为异步IO。其基本思想是进程告诉内核,当一个描述符已准备好可以进行io时用一个信号通知它。这种技术有两个问题,第一并非所有的系统都支持这种机制。第二个问题是这种信号对每个进程而言只有1个。如果该信号对两个描述符都起作用,那么在接到此信号时进程无法判别是哪一个描述符已准备好可以进行io.
一种比较好的技术是使用io多路转接。其基本思想是:先构造一张有关描述符的表,然后调用一个函数,它要到这些描述符中的一个已准备好进行io时才返回,返回时告诉进程哪一个描述符准备好可以进行io。

select函数

select函数可以执行io多路转接,传向select的参数高数内核:

从select返回时,内核告诉我们

使用这种返回值,就可调用相应的io函数,并且确知该函数不会阻塞
int select(int maxfdpl, fd_set *readfds, fs_set *writefds, fd_set *exceptfds, struct timeval *tvptr)
对于struct timeval {
long tv_sec;
long tv_usec;
}
有三种情况:

中间三个参数是指向描述符的指针。这三个描述符集说明了我们关心的可读、可写或处于异常条件的各个描述符。每个描述符集存放在一个fd_set数据类型中。
对fd_set数据类型可以进行的处理是:分配一个这种类型变量;将这种类型的变量赋予同类型另一个变量;对于这种类型的变量使用下列宏

select 中间三个参数中的任意一个可以是空指针,这表示对相对条件并不关心。如果所有三个指针都是空指针,则select提供了较sleep更精确的计时。select第一个参数maxfdp1意思是"最大fd加1"。在三个描述符集中找出最高描述符编号值,然后加1,这就是第一个参数值。也可以将第一个参数值设置为FD_SETSIZE,它说明了最大的描述符。但对大多数应用程序而言太大了,大多数应用程序只用3~10个描述符。如果将第三个参数设置为最高描述符编号值加1,内核只需在此范围内寻找打开的位。
select有可能的返回值有

对于准备好的说明:

poll函数

poll与流系统紧紧相关
int poll(struct pollfd fdarry[] , unsigned long fds, int timeout)
与select不是为每个条件构造一个描述符集,而是构造一个pollfd结构数组,每个数组元素制定一个描述符编号,及其所关心的条件。
struct {
int fd;
short events;
short revents;
}

应将events成员设置为下列表中的一个或者多个。通过这些值告诉内核我们队该描述符关心什么,返回时,内核设置revents成员,以说明对该描述符发生了什么事件。

名称 说明
POLLIN 可读除高优级外的数据,不阻塞
POLLRDNORM 可读普通数据, 不阻塞
POLLRDBAND 可读0优先波段数据,不阻塞
POLLPRI 可读高优先级数据,不阻塞
POLLOUT 可与普通数据,不阻塞
POLLWRNNORM 与POLLOUT相同
POLLWRBAND 可写非0优先波段数据,不阻塞
POLLERR 已出错
POLLHUP 已挂起
POLLNVAL 次描述符并不引用一打开文件

当一个描述符被挂断后,就不能再写向该描述符。但仍可能从该描述符读到数据。
poll的最后一个参数说明我们想要等待多少时间。

应当理解文件结束与挂断之间的区别。如果正在终端输入数据,并键入文件结束字符,POLLIN被打开,于是就可读文件结束指示。POLLHUP在revents中没有打开。如果读调制解调器,并且电话线已挂断,则在revents中将接到POLLHUP
与select一样,不论一个描述符是否阻塞,并不影响poll是否阻塞

异步IO

使用select和poll可以实现异步IO.关于描述符状态,系统并不主动告诉我们任何信息,我们需要主动地进行查询。信号机构提供一种异步形式的通知某种事件已发生的方法。
SVR4和4.3+BSD所支持的异步io的一个限制是每个进程只有一个信号。如果要对几个描述符进行异步io,那么在进程接收到该信号时并不知道这一信号对应于哪一个描述符。

svr4

在系统V中异步io是流系统的一部分。它只对流设备起作用。svr4异步io信号是SIGPOLL.
为了对一个流设备启动异步IO需要调用ioctl而第二个参数则为I_SETSIG.第三个参数是下列中一个或者多个构成的整型值

4.3+BSD

异步IO是两个信号SIGIO和SIGURG的组合。前者是通用异步IO信号,后者则只被用来通知进程在网络连接上到达了非规定波特率的数据。
为了接收SIGIO信号,需执行下列三步

readv和writev函数

readv和writev函数用于在一个函数中读、写多个非连续缓存。有时也将这两个函数称为散布读和聚集写
ssize_t readv(int filedes, const struct iovec iov[] , int iovcnt);
ssize_t writev(int filedes, const struct iovec iov[], int iovcnt);
这两个函数的第二个参数是指向iovec结构数组的一个指针
struct iovec {
void *iov_base;
size_t iov_len;
}
writev以顺序iov[0], iov[1]至iov[iovcnt-1]从缓存中聚集输出数据。writev返回输出的字节总数,它应等于所有缓存长度之和。
readv将读入的数据按上述同样顺序散布到混村中。readv总是先填满一个缓存,然后再填写下一个。readv返回读得的总字节数。如果遇到文件结尾,已无数据可读,则返回0.

readn 和 writen

某些设备,特别是终端、网络和svr4的流设备具有下列两种性质

在读写磁盘文件时没有这两种性质。
readn和writen的功能是读写指定n字节数据,并处理返回值小于要求值的情况。这两个函数只是按需多次调用read和write函数直至读写了N字节数据。
在要讲数据写到上面提到的设备上时,就可以调用writen,但是仅当先就知道要接收数据的数量时才调用readn

存储映射io

存储映射IO使一个磁盘文件与存储空间中的一个缓存相映射。于是当从缓存中取数据,就相当于读文件中的相应字节。与其类似,将数据存入缓存,则相应字节就自动地写入文件。这样就可以在不使用read和write的情况下执行io
为了使用这种功能,应该首先告诉内核将一个给定的文件映射到一个存储区域中。这是由mmap函数实现的。
caddr_t mmap(caddr_t addr, size_t len, int proc, int flag, int filedes, off_t off)
数据类型caddr_t通常定义为char*.addr参数用于指定映射存储区的起始地址。通常将其设置为0,这表示由系统选择该映射区的其实地址。此函数的返回地址是:该映射区的起始地址。
filedes指定要被映射文件的描述符。在映射该文件到一个地址空间之前,先要打开该文件。len是映射的字节数。off是要映射字节在文件中的起始位移量。proc参数说明映射存储区的保护要求
PROT_READ 区域可读;PROT_WRITE 区域可写;PROT_EXEC区域可执行;PROT_NONE区域可存取。
对于映射存储区所指定的保护要求与文件的open方法匹配。若文件是只读打开的那么对映射存储区局不能是指定PROT_ERITE.
flag参数影响映射存储区的多种属性:

off和addr的值,通常应当是系统虚存页长度的倍数。
因为映射文件的启动位移量受系统虚存页长度的限制,那么如果映射区的长度不是页长度的整数倍时,如文件长12字节,系统页长512字节,则系统通常提供512字节的映射区,气候500字节被设置为0.可以修改这500字节,但任何变动都不会再文件中反映出来。
与映射存储区相关有两个信号:SIGSEGGV和SIGBUS。信号SIGSEGV通常用于指示进程试图存取它不能存取的存储区。如果进程企图存取数据到mmap指定为只读的映射存储区,那么也产生此信号。如果存取映射区的部分文件,而在存取时这一部分已经不在,则产生SIGBUS信号。
fork之后,子进程集成存储映射区,但是由于同样的理由exec后新程序不集成此存储映射区。
进程终止时,或调用munmap之后,存储映射区就被自动去除。关闭文件描述符filedes并不解除映射区。
int munmap(caddr_t addr, size_t len);
munmap并不影响被映射的对象,也就是说调用munmap并不使映射区的内容写到磁盘文件上。对于MAP_SHARED区磁盘文件的更新,在写到存储映射区时按内核虚存算法自动进行。
将一个普通文件复制到另一个普通文件中时,存储映射IO比较快。但是有一些限制,如不能用其在某些设备之间进行复制,并且对被复制的文件进行映射后,也要注意该文件长度是否改变。尽管如此许多应用程序会从存储映射io得到好处,因为它处理的是存储空间而不是读写文件,所以常常简化算法。

上一篇 下一篇

猜你喜欢

热点阅读