美文网首页
高级io(三)

高级io(三)

作者: 千里山南 | 来源:发表于2016-03-06 19:57 被阅读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第三个参数是一个整型值,则其值就是该流新的写方式。目前只定义了两个写方式值。

  • SNDZERO 对管道和FIFO的0长写会造成顺流传送一个0长消息。按系统默认,0长写不发送消息。
  • SNDPIPE 在留上已出错后,若调用write或putmsg,则向调用进程发送SIGPIPE信号。

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则设置读方式
读方式有三种:

  • RNORM 普通,字节流方式。
  • RMSGN 消息不删除方式。读从流中取数据至读到所要求的字节数,或者到达消息边界。如果某次读只用了消息的一部分,则余下部分仍留在流中,以供下一个读取。
  • RMSGD 消息删除方式。这与不删除方式的区别是,如果某次读只用消息的一部分。则余下部分就被删除,不再使用。

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

  • RPROTNORM 协议-普通方式。read出错返回,errno设置为EBADMSG.默认方式
  • RPROTDAT 协议-数据方式。 read将控制部分作为数据返回给调用者。
  • RPROTDIS 协议-删除方式。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;
}
有三种情况:

  • tvptr == NULL
    永远等待。如果捕捉到一个信号则终端此无限期等待。当所指定的描述符中的一个已准备好或捕捉到一个信号。如果捕捉到一个信号则select返回-1,errno设置为EINTR
  • tv_sec == 0 && tv_usec == 0
    完全不等待。测试所有指定的描述符并立即返回。这是得到多个描述符的状态而不阻塞select函数的轮询方法
  • tv_sec != 0 || tv_usec != 0
    等待指定的秒数和微秒数。当指定的描述符之一已准备好,或当指定的时间值已经超过时立即返回。如果在超时时还没有一个描述符准备好,则返回值是0,与第一种情况一样,这种等待可被捕捉到的信号中断

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

  • FD_ZERO(fd_set *fdset)
  • FD_SET(int fd, fd_set *fdset)
  • FD_CLR(int fd, fd_set *fdset)
  • FD_ISSET(int fd, fd_set *fdset)

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

  • 返回-1表示出错。这是可能发生的,例如在所指定的描述符都没有准备好时捕捉到一个信号
  • 返回0表示没有描述符准备好。若指定的描述符都没指定好,而且指定的时间已经超过,则发生这种情况
  • 返回一个正值,说明了已经准备好的描述符,在这种情况下,单个描述符集中,仍旧打开的位是对应于已经准备好的描述符位。

对于准备好的说明:

  • 对于读集中的一个描述符的read不会阻塞,则次描述符是准备好的
  • 对于写集中的一个描述符的write不会阻塞,则次描述符是准备好的
  • 若对于异常条件集(execptfds)中的一个描述符有一个未决异常条件,则此描述符是准本好的。异常条件主要包括:在网络连接上到达指定波特率外的数据;在处于数据包方式的伪终端上发生了某些条件。
    应当理解一个描述符阻塞与否并不影响select是否阻塞。也就是说,如果希望读一个非阻塞描述符,并且以超时值为5秒调用select则select最多阻塞5秒。如果指定一个无限的超时值,则select阻塞到对该描述符数据准备好,或捕捉到一个信号。
    如果在一个描述符上碰到了文件结束,则select认为该描述符是可读的,然后调用read,它返回0

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的最后一个参数说明我们想要等待多少时间。

  • timeout==INFTIM 永远等待。当指定的的描述符中的一个已准备好,或捕捉到一个信号则返回。如果捕捉到一个信号,则poll返回-1,errno设置为EINTR
  • timeout == 0不等待。测试所有描述符并立即返回。这是得到很多描述符的状态而不阻塞poll函数的轮询方法。
  • timeout > 0 等待timeout毫秒。当指定的描述符之一已准备好,或指定的时间值已经超过时立即返回。如果已超时但是还没有一个描述符准备好,则返回0.

应当理解文件结束与挂断之间的区别。如果正在终端输入数据,并键入文件结束字符,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.第三个参数是下列中一个或者多个构成的整型值

  • S_INPUT 非高优先级的消息已到达
  • S_RDNORM 一普通消息已到达
  • S_RDBAND 一0优先波段消息已到达
  • S_BANDURG 此常数说明为S_RDBAND,则当一非0优先波段消息到达时产生SIGURG信号而非SIGPOLL
  • S_HIPRI 一高优先级消息已到达
  • S_OUTPUT 写队列不再满
  • S_WRNORM 与S_OUTPUT一样
  • S_WRBAND 可发送一非0优先波段消息
  • S_MSG 包含SIGPOLL信号的刘信号消息以到达
  • S_ERROR M_ERROR消息已到达
  • S_HANGUP M_HANGUP消息已到达
    除了调用ioctl以说明产生SIGPOLL信号的条件,也应该为该信号建立一个信号处理程序。对于SIGPOLL的默认动作是终止该进程,所以在调用ioctl之前建立信号处理程序。

4.3+BSD

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

  • 调用signal或sigaction为该信号建立一个信号处理程序。
  • 以命令F_SETOWN调用fctnl来设置进程ID和进程组ID,它们将接收对于该描述符的信号。
  • 以命令F_SETFL调用fcntl设置O_ASYNC状态标志,使在该描述符上可以进行异步io

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的流设备具有下列两种性质

  • 一次read操作所返回的数据可能小于所要求的数据,即使还没达到文件尾端。这不是一个错误,应当继续读该设备。
  • 一次write草错的返回值也可能少于指定输出的字节数。这可能是由若干因素造成的,例如,下游模块的流量控制限制。这也不是错误,应当继续写余下的数据至该设备。

在读写磁盘文件时没有这两种性质。
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参数影响映射存储区的多种属性:

  • MAP_FIXED 返回值必须等于addr.因为这不利于可移植性,所以不鼓励使用此标志。如果未指定此标志,而且addr非0,则内核只把addr视为何处设置映射区的一种建议。通过将addr指定为0可以获得最大可移植性。
  • MAP_SHARED 这一标志说明了本进程对映射区所进行的存储操作的配置。此标志指定存储操作修改映射文件,也就是,存储操作相当于对该文件write.必须指定本标志或下一个标志。
  • MAP_PRIVATE 本标志说明,对映射区的存储操作导致创建该映射文件的一个副本。所有后来对该营社区的存放都是存放该副本,而不是原始文件。

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得到好处,因为它处理的是存储空间而不是读写文件,所以常常简化算法。

相关文章

  • 高级io(三)

    2016-03-06 流ioctl操作 之前提到过ioctl函数,它能做其他io函数不能处理的事情。流系统中继续采...

  • 高级IO

    非阻塞IO,记录锁,系统V流机制,IO多路转接,readv和writev存储映射IO(mmap) pipe/soc...

  • 高级IO

    本篇文章主要涉及的内容有阻塞和非阻塞IO,同步异步IO,还有网络套接字IO,包括select,poll,epoll...

  • Linux 高级IO

    [TOC] Linux 高级IO 涉及到一些IO的高级用法 文件描述符重定向 dup 函数从当前可用的文件描述符中...

  • 高级io(一)

    2016-03-01 非阻塞io 系统调用分为低速调用系统和其他,低速系统调用时可能会使进程永远阻塞的一类系统调用...

  • 高级io(二)

    2016-03-02 流 流是系统V提供的构造内核设备驱动程序和网络协议包的一种通用方法流在用户进程和设备驱动程序...

  • linux高级环境编程-高级IO

    本文主要理清非阻塞IO,记录锁,IO多路转接,异步IO,readv和writev函数以及存储映射IO。学习 1、同...

  • 标准io和文件io,套接字高级io

    概念 文件I/O称之为不带缓存的IO(unbuffered I/O)。不带缓存指的是每个read,write都调用...

  • JAVA高级面试——IO

    一.基础知识 IP地址和端口号 (1).一个通信实体不能有两个通信程序使用同一个端口号 一个端口号只能有一个通信实...

  • Java高级- IO流

    13.1.File类的使用 File类的使用1.File类的一个对象,代表一个文件或一个文件目录(俗称: 文件夹)...

网友评论

      本文标题:高级io(三)

      本文链接:https://www.haomeiwen.com/subject/vvyakttx.html