APUE第14章 高级I/O

非阻塞I/O

指定非阻塞的方法

  1. 调用open的时候指定O_NONBLOCK。
  2. 对于已经打开的文件描述符可以用fcntl,设置O_NONBLOCK文件状态标识。

记录锁

背景问题

如果两个人同时编辑一个文件,其结果如何呢?在大多数UNIX系统中,文件的最后状态取决于写该文件的最后一个进程。但是有些应用程序如数据库,进程需要确保他正在单独写一个文件。所以提供了记录锁,记录锁锁定一个区间,这个区间将暂时不能被其他进程修改。

fvntl记录锁

1
2
3
4
5
6
7
8
9
10
11
#include <fcntl.h>
int fcntl(int fd, int cmd, .../* struct flock *flockptr */);
//返回值:成功,则依赖于cmd,否则返回-1

struct flock{
short l_type; //F_RDLCK,F_WRLCK,F_UNLCK
short l_whence; //SEEK_SET,SEEK_CUR,SEEK_END
off_t l_start; // offset in bytes
off_t l_len; //length in bytes, 0 means lock to EOF
pid_t l_pid; // return with F_GETLK
};

cmd与返回值

  1. F_GETLK:判断flockptr的锁会不会被另外的锁排斥(阻塞)。如果存在,则会将该锁信息重写进flockptr;如果不存在,则将l_type设置为F_UNLCK,且不改变flockptr
  2. F_SETLK:设置锁,如果被排斥则立即出错返回,errno为EACCESS或EAGAIN。
  3. F_SETLKW:这个是F_SETLK的阻塞版本,不会出错而是休眠等待锁可用。

    Notes: F_GETLK不会报告调用进程自己拥有的锁

锁的继承和释放

  1. 进程退出自动释放
  2. 不论如何复制,只要某个描述符关闭后,所有的锁都会关闭
  3. fork不会复制锁
  4. 执行exec后,新程序可以继承原执行程序的锁,如果设置CLOSE_ON_EXEC的标识就不会有这个效果,因为锁在文件被关闭的时候就已经释放了。

I/O多路转接(复用)

背景问题

如果场景需要从两个描述符读的时候呢?其中一种解决方案是fork一个进程,分别进行处理,通过IPC来进行通讯。第二种是使用非阻塞的I/O,然后通过轮询的方式,如果有数据就处理,没有就等待,比较浪费CPU时间。另一种方法是使用异步I/O,当描述符准备好就发信号通知

Select和Poll

都是I/O的方式,但是都不算很优雅,select是传3个很大的fd_set进去,而且select的新能不好,poll虽然做了改进,但是仍然是需要遍历查询。

由于上述的这些原因,引入了epoll,这几个之间的对比可访问这个传送门

存储映射I/O

作用是将文件映射到存储空间的一个缓冲区上

1
2
3
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flag, int fd, off_t off);
//addr为0的话,表示有系统选择该隐社区的起始地址

映射文件的起始偏移量受系统虚拟存储页长度的限制,如果映射区不是页长的的整数倍一般会多一页来存储,而多出来的内容可以修改但是不会体现在文件中,要修改文件大小,首先要加长文件。方式一般是map然后unmap,在继续map对应偏移量一直往下做,详情见《APUE》P426