第14章高级I/O
14.1 非阻塞I/O
高级I/O:非阻塞I/O、记录锁、系统V流机制、I/O多路转接、readv和writev函数以及存储映射I/O
低速系统调用可能会永远阻塞进程:
1) 某些文件(管道、终端设备、网络设备)的数据不存在,读操作会导致调用者永远阻塞
2) 数据不能立即被上述同样类型的文件接受,写操作会导致调用者永远阻塞
3) 在某种条件发生之前,打开某些类型的文件会被阻塞
4) 对已经加上强制性记录锁的文件进行读写
5) 某些ioctl操作
6) 某些进程间通信函数
对于给定的文件描述符有两种方法对其指定非阻塞I/O
1) 调用open获得文件描述符,指定O_NONBLOCK标志
2) 对于已经打开的文件描述符,调用fcntl,打开O_NONBLOCK文件状态标志
14.2 记录锁
功能:当某个进程正在读/写文件的某部分时,可以阻止其他进程修改同一文件区。
Int fcntl(int filedes.,int cmd,/*structflock *flockptr */);
Cmd:F_GETLK、F_SETLK、F_SETLKW
Struct flock{
Short l_type; //共享读锁,独占性写锁,解锁
Off_t l_start; //加锁或者解锁文件区域的其实字节偏移量
Short l_whence;//SEEK_SET,SEEK_CUR,orSEEK_END
Off_t l_len; //文件区域的字节长度
Pid_t l_pid;//能阻塞当前进程的锁的进程号,返回F_GETLK
}
F_GETLK:判断flockptr所描述的锁是否被另一把锁排斥
F_SETLK:设置flockptr所描述的锁
F_SETLKW:F_SETLK的阻塞版本
通过将l_start设置为0,whence设置为SEEK_SET可以锁住整个文件,多个进程访问同一个文件时,新锁会替换旧锁,当加读锁时,文件描述符必须为读打开,当加写锁时,文件描述符必须写打开
死锁产生的情况:当两个进程相互等待对方持有并锁定的资源时
锁的隐含继承与释放:
1) 进程终止,则它所建立的全部锁会释放;当关闭任意文件描述符,其引用的文件上的全部锁释放
2) Fork产生的子进程不继承父进程设置的锁
3) 执行exec后,新程序可以继承原执行程序的锁。
建议性锁和强制性锁
强制性锁会对每个read,write,open系统调用都会进行检查,检查调用进程对访问的文件是否违背了某一把锁的作用
14.4 STREAMS
STREAMS(流)是系统V提供的构造内核设备驱动程序和网络协议包的一种通用方法,流首可以压入处理模块,可以通过ioctl命令实现,所有的STREAMS设备都是字符特殊文件。
流首和用户进程的输入和输出都基于消息,并使用read,write,ioctl,getmsg,getpmsg,putmsg和putpmsg交换消息
struct strbuf{
int maxlen;
int len;
char *buf;
}
消息的类型分为三种,M_DATA(I/O用户数据),M_PROTO(协议控制信息),M_PCPROTO(高优先级协议控制信息),每个STREAMS模块有两个输入队列,流的方向正好相反。
STREAMS ioctl操作:
int isastream(int filedes); //判断一个描述符是否引入了流
Int ioctl(int filedes,int cmd,…) ;//对设备的I/O通道进行管理
int putmsg(int filedes,const struct strbuf *ctlptr,const structstrbuf *dataptr,int flag);//将STREAMS消息写入流
int putpmsg(int filedes,const struct strbuf *ctlptr,const structstrbuf *dataptr,int band,int flag);//可以设置优先级
int getmsg(int filedes,struct strbuf *restrict ctlptr,structstrbuf *restrict dataptr,int *restrict flagptr);//返回流首读队列的下一个消息
int getpmsg(int filedes,struct strbuf *restrict ctlptr,structstrbuf *restrict dataptr,int *restrict bandptr,int *restrict flagptr);
1)写模式:request设置为I_GWROPT(该流的当前写模式在第三个参数中返回),若request设置为I_SWROPT(则第三个参数成为该流的新的写模式)
写模式值包括
SNDZERO:0长度写不发送消息
SNDPIPE:当流已出错后,若调用write或者putmsg则调用进程发送SIGPIPE信号
2) 读模式:默认是字节流模式(忽略流中消息的边界),调用ioctl时request设置为I_GRDOPT和I_SRDOPT与写模式作用相似。
读模式值包括:
RNORM:普通,字节流模式
RMSGN:消息不丢弃模式,某次读取一部分,则剩余一部分消息仍保留在流中
RMSGD:消息丢弃模式,某次读一部分,则另一部分丢弃
14.5 I/O多路转接
异步I/O基本思想是进程告诉内核,当一个描述符准备好时,用一个信号通知它。
I/O多路转接:先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行I.O时,返回告诉进程哪些描述符已经准备好进行I/O。
#include <sys/select.h>
Int select(int maxfdp1,fd_set *restrictreadfds,fd_set *restrict writefds,fd_tset *restrict exceptfds,struct timeval*restrict tvptr);
tvptr:NULL一直等待直到捕捉到一个信号或者描述符中的一个已经准备好;tvptr->tv_sec==0&&tvptr->tv_usec==0完全不等待(测试所有描述符并立即返回)
readfds,writefds,exceptfds可读,可写或者处于异常条件的各个描述符
maxfdp1;在三个fd_set中找到最大的描述符+1
返回值:-1出错,0没有准备好的描述符,其他则是准备好的文件描述符
Poll函数类似于select,与STREAMS系统息息相关
#include <poll.h>
Int poll(struct pollfd fdarray[],nfds_tnfds,int timeout);
struct poolfd{
int fd;
short events;//告诉内核描述符关心的事件
short revents; //返回时,内核设置revents成员说明描述符发生了什么事件
}
Nfds:第一个数组包含多少个元素
当一个描述符被挂断后(POLLHUP),就不能再写向该描述符,但可以读
异步I/O:系统V中的异步I/O信号是SIGPOLL;BSD系统中的异步I/O信号是SIGIO和SIGURG
Readv(散布读)和writev(聚集写)
#include <sys/uio.h>
Ssize_t readv(int filedes,const structiovec *iov,int iovcnt);
Struct iovec{
Void *iov_base; //缓冲区起始地址
Size_t iov_len; //缓冲区长度
}
Ssize_t writev(int filedes,const structiovec *iov,int iovcnt);(适合一次写大量数据)
Ssize_t readn(int filedes,void *buf,size_tnbytes); //从文件中读N个字节数据
Ssize_t written(int filedes,void*buf,size_t nbytes);//从文件中写N个字节数据
存储映射I/O
将文件与缓冲区映射在一起,读/写缓冲区相当于读/写文件
#include<sys/mman.h>
Void *mmap(void *addr,size_t len,intprot,int flag,int filedes,off_t off);
addr:映射区的起始地址
filedes:被映射的文件的描述符
len:映射的字节数
off:映射字节在文件中的起始偏移量
prot:对映射存储区域的保护要求,PROT_READ(映射区可读),PROT_WRITE(映射区可写),PROT_EXEC(映射区可执行),PROT_NONE(映射区不可访问),不超过open的权限
flag:设置不同标志
int mprotect(void *addr,size_t len,int prot);//更改现有映射区的权限
int msync(void *addr,size_t len,int flags);//flags有两种选择,MS_ASYNC标志简化被写页的调度,如果希望返回之前等待写操作完成则设置为MS_SYNC标志,将共享存储区中的已修改数据冲洗至文件中
int munmap(caddr_t addr,size_t len);//解除映射关系