我们可以发出open,read,write这样的IO操作,并使它们永远不会阻塞,如果无法做到,则立即返回出错。
两种方法获得非阻塞IO:
1. open打开使指定O_NONBLOCK;
2. 对于一个已经打开的描述符,调用fcntl增加上述标志位。
记录锁(record locking)的名称是一种误用,因为UNIX系统内核根本没有使用文件记录这种概念。更适合的术语是字节范围锁(byte-range locking),因为它锁定的只是文件的一个区域。
这种功能用于支持对数据库的维护。
int fcntl(int fd, int cmd, struct flock *lock);
struct flock {
short l_type;/*F_RDLCK, F_WRLCK, or F_UNLCK*/
off_t l_start;/*相对于l_whence的偏移值,字节为单位*/
short l_whence;/*从哪里开始:SEEK_SET, SEEK_CUR, or SEEK_END*/
off_t l_len;/*长度, 字节为单位; 0 意味着缩到文件结尾*/
pid_t l_pid;/*returned with F_GETLK*/
};
锁的兼容性规则只对不同进程有效,同一进程对自己已加锁的区间再加锁,则会替换旧锁。
系统可以按要求组合或分裂相邻区。
如果对一个文件描述符设置了执行时关闭,当作为exec的一部分关闭该文件描述符时,将释放相应文件的所有锁。
int select( int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr);
/*
maxfdp1:是一个整数值,是指集合中所有文件描述符的范围,即3个集合中所有文件描述符的最大值加1,不能错!
readfds:(可选)指针,指向一组等待可读性检查的描述符。
writefds:(可选)指针,指向一组等待可写性检查的描述符。
exceptfds:(可选)指针,指向一组等待错误检查的描述符。
timeout:select()最多等待时间,设为NULL则阻塞,设为0则不阻塞,设为正数则等待指定时间。
*/
readfds, writefds, exceptfds可以实现为位图。利用一组函数来设置、清除、测试指定fd。 返回时,-1表示出错,一般是因为接收到某信号;0表示没有描述符准备好,此时3个集合都置0;一个正数表示准备好的3个集合的数量之和,此时3个集合将准备好的位置1。注意,如果同一描述符出现在多个集合中,则累加计数。
pselect变体的区别,使用timespec结构可以实现更精准的超时时间(它以秒和纳秒表示,而timeval以秒和微秒表示);它的超时时间是const,所以不能改变时间值;它还可以使用信号屏蔽字。 poll提供的功能与select类似:一个描述符碰到文件尾端,它仍是可读的。
一个描述符是否阻塞与select是否阻塞没有关系
int poll(struct pollfd fd[], nfds_t nfds, int timeout);
参数:
struct pollfd{
int fd; //文件描述符
short events; //请求的事件
short revents; //返回的事件
};
events和revents是通过对代表各种事件的标志进行逻辑或运算构建而成的。events包括要监视的事件,poll用已经发生的事件填充revents。poll函数通过在revents中设置标志肌肤POLLHUP、POLLERR和POLLNVAL来反映相关条件的存在。不需要在events中对于这些标志符相关的比特位进行设置。如果fd小于0, 则events字段被忽略,而revents被置为0.标准中没有说明如何处理文件结束。文件结束可以通过revents的标识符POLLHUN或返回0字节的常规读操作来传达。即使POLLIN或POLLRDNORM指出还有数据要读,POLLHUP也可能会被设置。因此,应该在错误检验之前处理正常的读操作。
2. 第二个参数nfds:要监视的描述符的数目。
3. 最后一个参数timeout:是一个用毫秒表示的时间,是指定poll在返回前没有接收事件时应该等待的时间。如果 它的值为-1,poll就永远都不会超时。如果整数值为32个比特,那么最大的超时周期大约是30分钟。
然而,select和poll都不是线程安全的,显而易见。
传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是“活跃”的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对“活跃”的socket进行操作—这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有“活跃”的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个“伪”AIO,因为这时候推动力在os内核。
用readv,writev进行散布读和聚集写。
管道、FIFO以及某些设备(特别是终端和网络)有以下两种性质:
1. 一次read操作返回的数据少于要求的数据
2. 一次write操作的返回值少于指定输出的值
利用readn, writen将按需地多次调用read,write直至完成。
存储映射I/O是一种基于内存区域的高级I/O操作,它将磁盘文件与进程地址空间中的一个内存区域相映射。当从这段内存中读数据时,就相当于读磁盘文件中的数据,将数据写入这段内存时,则相当于将数据直接写入磁盘文件。这样就可以在不使用基本I/O操作函数read和write的情况下执行I/O操作。
实现存储映射I/O的核心操作是通过mmap系统调用将一个给定的磁盘文件映射到一个存储区域中
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
prot用来指定对映射区域的保护要求,但是它的保护范围不能超过文件open时指定的打开权限。比如以只读(PROT_READ)方式打开一个文件,那么以读写(PROT_READ|PROT_WRITE)方式保护内存区域是不合法的。flags用来指定内存区域的多种属性,两个典型的取值是MAP_SHARED和MAP_PRIVATE。MAP_SHARED标志指定了进程对内存区域的修改会影响到映射文件,且多个共享该映射区的进程都可以看见。而当对flags指定MAP_PRIVATE时,进程会为该映射内存区域创建一个私有副本,对该内存区的所有操作都是在这个副本上进行的,此时对内存区域的修改并不会影响到映射文件。
off的值和addr的值通常被要求是系统虚拟存储页长度的倍数,将两者都指定为0可以省去这一麻烦而交由系统处理。
mprotect()用于更改一个现有映射的权限,msync()用于将页冲洗到被映射的文件中,可以选择同步或异步方式,munmap()用于解除一个映射,但它并不能影响被映射的对象。