文章目录
高级I/O包括:非阻塞I/O、记录锁、I/O多路复用(select和poll)、异步I/O、readv、writev函数以及存储映射函数(mmap)。
一、非阻塞I/O
对于一个给定的描述符,有两种为其指定非阻塞I/O的方法。
(1)如果调用open获得描述符,则可指定O_NONBLOCK标志
(2)对已经打开的描述符,可调用fcntl,由该函数打开O_NONBLOCK文件标志
对于非阻塞情况下返回的errno为EAGAIN或EWOULDBLOCK(在较新版本的UNIX下,两者等价),表示由于资源限制/不满足条件返回的错误,即再试一次。
比如调用read时,暂时无数据可读;调用write时,缓冲区暂时没有空余空间;都会直接返回,并设置errno。
二、记录锁
记录锁(record locking)的功能是:当一个进程正在读或修改文件的某个部分时,使用记录锁空余阻止其他进程修改同一文件区。
#include <fcntl.h>
int fcntl(int fd, int cmd, .../* struct flock* flockptr */);
//成功,返回值依赖于cmd;失败,返回-1
对于记录锁,cmd是F_GETLK, F_SETLK, F_SETLKW,第三个参数时指向flock结构的指针
三、I/O多路转接
3.1 函数select和pselect
传给select的参数告诉内核:
- 关心的描述符
- 对于每个描述符所关心的条件(读?写?异常?)
- 愿意等待的时间
从select返回时,内核告诉我们:
- 已准备好的描述符的总数量
- 对于各个条件,分别有哪些描述符已准备好
#include <sys/select.h>
int select(int maxdpl, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* tvptr);
//返回准备就绪的描述符数量;超时,返回0;出错,返回-1
(1)最后一个参数 tvptr,指定愿意等待的时间(s/μs),三种情况:
- tvptr == NULL:永远等待,直到捕捉到信号或有描述符准备好
- tvptr->tv_sec == 0 && tvptr->tv_usec == 0 :不等待,测试所有指定描述符并立即返回
- tvptr->tv_sec != 0 && tvptr->tv_usec != 0 :等待指定的时间
(2)中间三个参数是指向 描述符集(fd_set) 的指针。每个描述符集存储在一个fd_set数据类型中,它可以为每一个可能的描述符保持一位。我们可以认为它只是一个很大的字节数组。
这三个参数中若为空指针,则表示对相应条件并不关心。
(3)参数maxfdp1意思是“最大描述符编号值加1”
在三个描述符集中找出最大的描述符编号,然后+1;也可将其设为FD_SETSIZE,这是<sys/select.h>中的一个常量,它指定最大描述符数(经常是1024)。
POSIX也定义了一个select的变体,称为pselect
3.2 函数poll
poll函数类似于select,但是程序接口有所不同
#include <poll.h>
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
//返回准备就绪的描述符数量;超时,返回0;出错,返回-1
与select不同,poll不是为每一个条件(可读、可写、异常)构造一个描述符集,而是构造一个pollfd结构的数组,每个数组元素指定一个描述编号以及我们对该描述符感兴趣的条件:
struct pollfd{
int fd;
short events;
short revents;
};
fdarray数组中的元素由nfds指定。
应将events成员设置为图中所示值的一个或几个;返回时,revents成员由内核设置,用于说明描述符发生了哪些事情。
注:poll没有更改events成员,而select修改其参数以指示哪一个描述符已准备好了
当一个描述符被挂断(POLLHUP)后,就不能再写该描述符,但仍可以从该描述符中获取数据。
与select一样,一个描述符是否阻塞不会影响poll是否阻塞。
四、异步I/O
select和poll可以实现异步形式的通知。信号机制提供了一种以异步形式通知某种事件已发生的方法。
4.1 System V异步I/O
4.2 BSD异步I/O
4.3 POSIX异步I/O
五、函数readv和writev
readv和writev用于在一次函数中读、写多个非连续缓冲区。有时将其称为散布读(scatter read)和聚集写(gather write)
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec* iov, int iocnt);
ssize_t writev(int fd, const struct iovec* iov, int iocnt);
//成功,返回已读或已写的字节数;出错,返回-1
struct iovec{
void* iov_base; //starting address of buffer
size_t iov_len; //size of buffer
};
iov数组中的元素数由iocvnt指定。
wirtev按顺序从缓冲区中输出数据;readv将读入的数据按顺序散布到缓冲区中。readv总是先填满一个缓冲区,再填写下一个。
六、函数readn和writen
通常,在读写一个管道、网络设备或终端时,会发生未读完,或未写完等等。
调用readn或writen读、写指定的N字节数据,并处理返回值可能小于要求值的情况。其原理就是按需多次调用read和write,直至读、写了N字节数据。
#include "apue.h"
ssize_t readn(int fd, void* buf, size_t nbytes);
ssize_t writen(int fd, void* buf, size_t nbytes);
七、存储映射I/O(mmap)
存储映射I/O(memeory-mapped I/O)能将一个磁盘文件映射到存储空间的一个缓冲区上。对缓冲区的操作就相当于对文件操作,这样就可以在不使用read和write的情况下执行I/O
#include <sys/mman.h>
void* mmap(void* addr, size_t len, int port, int flag, int fd, off_t off);
//成功,返回映射区地址;出错,返回MAP_FAILED
-
addr用于指定映射存储区的起始地址。通常将其设为0,表示由系统选择起始地址
-
fd指定要被映射文件的描述符(需先打开)。len参数是映射的字节数,off是要映射字节在文件中的起始偏移量
-
port指定映射存储区的保护要求:
-
flag
(1)MAP_FIXED
(2)MAP_SHARED
(3)MAP_PRIVATE
当进程终止时,会自动解除存储映射区的映射,或者直接调用munmap函数也可以解除映射区。
int munmap(void* addr, size_t len);