APUE学习笔记-高级I/O

本章将围绕以下内容讨论:非阻塞I/0,记录锁,I/O多路转接(select和poll函数),异步I/O,readv和writev函数以及存储映射I/O(mmap)。

1.非阻塞I/O

对于一个给定的描述符,有两种为其指定非阻塞I/O的方法

  • 如果调用open获得描述符,则可指定O_NONBLOCK标志。
  • 对于已经打开的一个描述符,则可调用fcntl,由该函数打开O_NONBLOCK文件状态标志。
2.记录锁

记录锁(record locking)的功能是:当第一个进程正在读或修改文件的某个部分时,使用记录锁可以阻止其他进程修改同一文件区。

(1)fcntl记录锁

对于记录锁,cmd是F_GETLK, F_SETLK或F_SET_LKW。第三个参数是一个指向flock结构的指针。

3.I/O多路转接

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

这种形式的阻塞I/O到处可见。但是如果必须从两个描述符读,又将如何呢?在这种情况下,我们不能在任一个描述符上进行阻塞读,否则可能会因为被阻塞在一个描述符的读操作上而导致另一个描述符即使有数据也无法处理。所以为了处理这种情况需要另一种不同的技术。

一种比较好的技术是使用I/O多路转接(I/O multiplexing)。为了使用这种技术,先构造一张我们感兴趣的描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I/O时,该函数才返回。poll, pselect和select这3个函数使我们能够执行I/O多路转接。在从这些函数返回时,进程会被告知哪些描述符已准备好可以进行I/O。

(1)函数select和pselect

在所有POSIX兼容的平台上,select函数使我们可以执行I/O多路转接。

1)传给select的参数告诉内核:

  • 我们所关心的描述符;
  • 对于每个描述符我们所关心的条件(是否想从一个给定的描述符读,是否想写一个给定的描述符,是否关心一个给定描述符的异常条件);
  • 愿意等待多长时间(可以永远等待、等待一个固定的时间或者根本不等待);

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

  • 已准备好的描述符的总数量
  • 对于读、写或异常这3个条件中的每一个,哪些描述符已准备好。

使用这种返回信息,就可调用相应I/O函数(一般是read或write),并且确知该函数不会阻塞。

a)其中,最后一个参数tvptr,它指定愿意等待的时间长度,单位为妙和微妙。有以下3中情况:

  • tvptr == NULL:永远等待
  • tvptr->tv_sec == 0 && tvptr->tv_usec == 0:根本不等待。测试所有指定的描述符并立即返回。这是轮询系统找到多个描述符状态而不阻塞select函数的方法。
  • tvptr->tv_sec != 0 || tvptr->tv_usec != 0:等待指定的秒数和微妙数

b)中间3个参数是指向描述符集的指针。这3个描述符集说明了我们关心的可读、可写或处于异常条件的描述符集合。

c)第一个参数maxfdp1的意思是“最大文件描述符编号值加1”。考虑所有3个文件描述符集,在3个描述符集中找出最大描述符编号值,然后加1,这就是第一个参数值。

3)POSIX.1也定义了一个select的变体,称为pselect。

除下列几点外,pselect和select相同:

  • select的超时值用timeval结构指定,但pselect使用timespec结构(timespec结构以秒和纳秒表示超时值,而非秒和微妙);
  • pselect的超时值被声明为const,这保证了调用pselect不会改变此值。
  • pselect可使用可选信号屏蔽字。若sigmask为NULL,那么在信号有关的方面pselect的运行状态和select相同。否则,sigmask指向一信号屏蔽字,在调用pselect时,以原子操作的方式安装该信号屏蔽字。在返回时,恢复以前的信号屏蔽字。

(2)函数poll

poll函数类似于select,虽然poll函数是System V引入的,但是poll函数可用于任何类型的文件描述符。

1)与select不同,poll不是为每个条件(可读性、可写性和异常条件)构造一个描述符集,而是构造一个pollfd结构的数组,每个数组元素指定一个描述符编号以及我们对该描述符感兴趣的条件。

2)fdarray数组中的元素由nfds指定。

3)poll最后一个参数指定的是我愿意等待多长时间。如同select一样,有3中不同的情形。

  • timeout == -1:永远等待
  • timeout == 0:不等待。
  • timeout > 0:等待timeout秒
4.异步I/O

使用上一节说明的select和poll可以实现异步形式的通知。关于描述符的状态,系统并不主动告诉我们任何信息,我们需要进行查询(调用select或poll)。

(1)System V异步I/O

System V的异步I/O信号是SIGPOLL。为了对一个STREAMS设备启动异步I/O,需要调用ioctl,将它的第二个参数(request)设置成I_SETSIG。

(2)BSD异步I/O

(3)POSIX异步I/O

1)这些异步I/O结构使用AIO控制块来描述I/O操作。aiocb结构定义了AIO控制块。该结构至少包括下面这些字段:

2)在进行异步I/O之前需要先初始化AIO控制块,调用aio_read函数来进行异步读操作,或调用aio_write函数来进行异步写操作。

当这些函数返回成功时,异步I/O请求便已经被操作系统放入等待处理的队列中了。

3)要想强制所有等待中的异步操作不等待而写入持久化的存储中,可以设立一个AIO控制块并调用aio_fsync函数:

4)为了获知一个异步读、写或者同步操作的完成状态,需要调用aio_error函数。

返回值有4种情况:

  • 0:异步操作成功完成。需要调用aio_return函数获取操作返回值
  • -1:对aio_error的调用失败。这种情况下,errno会告诉我们为什么。
  • EINPROGRESS:异步读、写或同步操作仍在等待。
  • 其他情况:其他任何返回值是相关的异步操作失败返回的错误码。

5)如果异步操作成功,可以调用aio_return函数来获取异步操作的返回值。

6)执行I/O操作时,如果还有其他事务要处理而不想被I/O操作阻塞,就可以使用异步I/O。然而,如果在完成了所有事务时,还有异步操作未完成,可以调用aio_suspend函数来阻塞进程,直到操作完成。

7)当还有我们不想再完成的等待中的异步I/O操作时,可以尝试使用aio_cancel函数来取消它们。

函数可能会返回以下4个值中的一个:

  • AIO_ALLDONE:所有操作在尝试取消它们之前已经完成
  • AIO_CANCELED:所有要求的操作已被取消
  • AIO_NOTCANCELED:至少有一个要求的操作没有被取消。
  • -1:对aio_cancel的调用失败,错误码将存储在errno中。
5.函数readv和writev

(1)readv和writev函数用于在一次函数调用中读、写多个非连续缓冲区。有时也将这两个缓冲区称为散布读(scatter read)和聚集写(gather write)。

1)这两个函数的第二个参数是指向iovec结构数组的一个指针:

2)iov数组中的元素由iovcnt指定,其最大值受限于IOV_MAX。

3)writev函数从缓冲区中聚集输出数据的顺序是:iov[0]、iov[1]直至iov[iovcnt-1]。writev返回输出的字节总数,通常应等于所有缓冲区长度之和。

4)readv函数则将读入的数据按上述同样的顺序散布到缓冲区中。readv总是先填满一个缓冲区,然后再填下一个。readv返回读到的字节总数。如果遇到文件尾端,已无数据可读,则返回0。

6.函数readn和writen

管道、FIFO以及某些设备有下列两种性质:

  • 一次read操作所返回的数据可能少于所要求的数据,即使还没有达到文件尾端也可能是这样。这不是一个错误,应当继续读该设备。
  • 一次write操作的返回值也可能少于指定输出的字节数。这可能是某个因素造成的,例如,内核输出缓冲区变慢。这也不是错误,应当继续写余下的数据。

readn和writen函数的动能是分别读、写指定的N字节数据,并处理返回值可能小于要求值的情况。这两个函数只是按需多次调用read和write直至读、写了N字节数据。

7.存储映射I/O

存储映射I/O(memory-mapped I/O)能将一个磁盘文件映射到存储空间中的一个缓冲区上,于是,当从缓冲区中取数据时,就相当于读文件中的相应字节。类似,将数据存入缓冲区时,相应字节就自动写入文件。

(1)为了使用这种功能,应首先告诉内核将一个给定的文件映射到一个存储区域中。这是有mmap函数实现的。

1)addr参数用于指定映射存储区的起始地址。通常将其设置为0,表示由系统选择该映射区的起始地址。

2)fd参数是指定要被映射文件的描述符。len参数是映射的字节数。off是要映射字节在文件中的起始偏移量。

3)prot参数指定了映射存储区的保护要求,如下所示:

  • PROT_READ:映射区可读
  • PROT_WRITE:映射区可写
  • PROT_EXEC:映射区可执行
  • PROT_NONE:映射区不可访问

4)flag参数影响映射存储区的多种属性:

  • MAP_FIXED:返回值必须等于addr。
  • MAP_SHARED:这一标志描述了本进程对映射区所进行的存储操作的配置。
  • MAP_PRIVATE:本标志说明,对映射区的存储操作导致创建该映射文件的一个私有副本。

(2)调用mprotect可以更改一个现有映射的权限。

(3)如果共享映射中的页已修改,那么可以调用msync将该页冲洗到被映射的文件中。msync函数类似于fsync,但作用于存储映射区。

(4)当进程终止时,会自动解除存储映射区的映射,或者直接调用munmap函数也可以解除映射区。关闭映射存储区是使用的文件描述符并不解除映射区。

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注