fd是阻塞还是非阻塞是可以设置的,这也直接影响系统调用函数是否阻塞还是非阻塞(直接返回错误)
对于一个给定的fd,有两种方法可以指定为非阻塞IO
任意多个进程在一个给定的字节上可以有一把共享的读锁,但是在一个给定字节上只能有一个进程有一把独占写锁
单进程加写锁,后面的锁范围会覆盖前面
F_SETLK 和 F_SETLKW 命令总是替换调用进程现有的锁(若已存在),所以调用进程决不会阻塞在自己持有的锁上,于是,F_GETLK 命令决不会报告调用进程自己持有的锁
前面已经给出了 open、fork 以及 dup 调用后的数据结构(见图 3-9 和图 8-2)
在原来的这些图上新加了 lockf 结构
建议性锁:就是内核不去维护这个锁,程序员自己要遵守这个原则
强制性锁会让内核检查每一个 open、read 和 write,验证调用进程是否违背了正在访问 的文件上的某一把锁。强制性锁有时也称为强迫方式锁
从一个fd读,然后写到另一个fd,不去设置O_NOBLOCK,就会是阻塞IO,很常见
telnet命令结构:从终端读(std-in),将数据写到网络连接上,同时从网络连接读,将所得数据写到终端上(std-out),在网络另一端,telnetd守护进程读用户的命令,发送给shell,将shell执行的结果通过telnet命令发送给用户,并显示在终端上,如同登入了远程机器了
telnet进程有两个输入,两个输出, 也就是两个fd要去读
终端用户写给telnet,网络连接返回给telnet;telnet输出给网络连接,telnet输出到终端给用户
Ps: 将两个输入描述符都设置为非阻塞的,对第一个描述符发一个 read。如果该输入上有数据,则 读数据并处理它。如果无数据可读,则该调用立即返回。然后对第二个描述符作同样的处理。在 此之后,等待一定的时间(可能是若干秒),然后再尝试从第一个描述符读。这种形式的循环称 为 轮询
这种方法的不足之处是浪费 CPU 时间。大多数时间实际上是无数据可读,因此执行 read 系统调用浪费了时间
在每次循环后要等多长时间再执行下一轮循环也很难确定
虽然轮询技术 在支持非阻塞 I/O 的所有系统上都可使用,但是在多任务系统中应当避免使用这种方法
tvptr就是等待的时间
readfds
fd0 fd1 fd2
0 0 0 ...
writefds
exceptfds
这三个可以通过下面宏来操作fd,如果是null,表示不关心
maxfdpl:最大fd编号+1,在 3 个描述符集中找出最大描述符编号值,然后加 1
或者可以设置为FD_SETSIZE,最大值,通常是1024,但是一般程序不会使用这么多fd,通常3-10个,这样内核就只需要在此范围内寻找打开的位
比如 readset 有fd(0),fd(3);write有fd(1),fd(2)
那么maxfdpl:4,也就是前四个位去寻找
maxfdp1一样
readfds,writefds,exceptfds一样
tsptr支持更加准确的时间,而且是const,pselect不能改变这个值
sigmask为了调用函数的时候可以安装这个参数的屏蔽字,返回的时候再回复以前的屏蔽字
类似select,但是接口不同
没有这个概念了:读,写,异常的fd集
pollfd:fd,感兴趣的事件,已经发生的事件
nfds unsigned long
poll没有修改events成员,与select不同,select修改参数来指示哪个fd已经准备好
与 select 一样,一个描述符是否阻塞不会影响 poll 是否阻塞。有超时设置,而且就算一个fd阻塞在那里,只要其他fd准备好了,依旧可以返回
select和poll实现了异步通知
关于描述符的状态,系统并 不主动告诉我们任何信息,我们需要进行查询(调用 select 或 poll)
而前面提到的缺点,信号不够,否则无法区分信号对应哪一个fd
SUSv4 中将通用的异步 I/O 机制从实时扩展部分调整到基本规范部分。这种机制解决了这些 陈旧的异步 I/O 设施存在的局限性。
POSIX异步IO接口会有问题
System V 的异步 I/O 信号是 SIGPOLL
异步 I/O 是信号 SIGIO 和 SIGURG 的组合
这些异步 I/O 接口使用 AIO 控制块来描述 I/O 操作
在进行异步 I/O 之前需要先初始化 AIO 控制块,调用 aio_read 函数来进行异步读操作,或 调用 aio_write 函数来进行异步写操作
要想强制所有等待中的异步操作不等待而写入持久化的存储中,可以设立一个 AIO 控制块并 调用 aio_fsync 函数
如果 op 参数设定为 O_DSYNC,那么操作执行起来就会像调用了 fdatasync 一样。否则,如果 op 参数设定为 O_SYNC, 那么操作执行起来就会像调用了 fsync 一样
像 aio_read 和 aio_write 函数一样,在安排了同步时,aio_fsync 操作返回。在异步 同步操作完成之前,数据不会被持久化。AIO 控制块控制我们如何被通知,就像 aio_read 和 aio_write 函数一样。为了获知一个异步读、写或者同步操作的完成状态,需要调用 aio_error 函数
执行 I/O 操作时,如果还有其他事务要处理而不想被 I/O 操作阻塞,就可以使用异步 I/O。然 而,如果在完成了所有事务时,还有异步操作未完成时,可以调用 aio_suspend 函数来阻塞进 程,直到操作完成。
list 参数是一个指向 AIO 控制块数组的指针,nent 参数表明了数组中的条目数。数组中的空 指针会被跳过,其他条目都必须指向已用于初始化异步 I/O 操作的 AIO 控制块
当还有我们不想再完成的等待中的异步 I/O 操作时,可以尝试使用 aio_cancel 函数来取消它们
还有一个函数也被包含在异步 I/O 接口当中,尽管它既能以同步的方式来使用,又能以异步的方 式来使用,这个函数就是 lio_listio。该函数提交一系列由一个 AIO 控制块列表描述的 I/O 请求
readv 和 writev 函数用于在一次函数调用中读、写多个非连续缓冲区。
散布读(scatter read)和聚集写(gather write)。
readn和writen
一次 read 操作所返回的数据可能少于所要求的数据,即使还没达到文件尾端也可能是 这样。这不是一个错误,应当继续读该设备
一次 write 操作的返回值也可能少于指定输出的字节数。这可能是由某个因素造成的, 例如,内核输出缓冲区变满
这两 个函数只是按需多次调用 read 和 write 直至读、写了 N 字节数据
存储映射 I/O(memory-mapped I/O)能将一个磁盘文件映射到存储空间中的一个缓冲区上, 于是,当从缓冲区中取数据时,就相当于读文件中的相应字节。与此类似,将数据存入缓冲区时, 相应字节就自动写入文件
存储映射 I/O 伴随虚拟存储系统已经用了很多年
为了使用这种功能,应首先告诉内核将一个给定的文件映射到一个存储区域中
新程序则不能通过 exec 继承存储映射区,调用 mprotect 可以更改一个现有映射的权限
如果共享映射中的页已修改,那么可以调用 msync 将该页冲洗到被映射的文件中。msync 函数类似于 fsync(见 3.13 节),但作用于存储映射区
当进程终止时,会自动解除存储映射区的映射,或者直接调用 munmap 函数也可以解除映射区。
关闭映射存储区时使用的文件描述符 并不解除映射区
对于 MAP_SHARED 区磁盘文件的更新,会在我们将数据写到存储映射区后的某个时刻, 按内核虚拟存储算法自动进行。在存储区解除映射后,对 MAP_PRIVATE 存储区的修改会被丢弃