APUE - 高级 I/O
非阻塞 I/O
对于一个给定的描述符有两种办法对其指定非阻塞 I/O:
- 如果调用 open 获得描述符,则可指定 O_NONBLOCK 标志。
- 对于已经打开的一个描述符,则可调用 fcntl,由该函数打开 O_NONBLOCK 文件状态标志。
记录锁
记录锁的功能是:当一个进程正在读或修改文件的某个部分时,它可以阻止其他进程修改同一文件区。
fcntl记录锁
#include <fcntl.h>
int fcntl(int filedes, int cmd, ... /* struct flock *flockptr */ );
struct flock {
short l_type; /* F_RDLCK, F_WRLCK, or F_UNLCK */
off_t l_start; /* offset in bytes, relative to l_whence */
short l_whence; /* SEEK_SET, SEEK_CUR, or SEEK_END */
off_t l_len; /* length, in bytes; 0 means lock to EOF */
pid_t l_pid; /* returned with F_GETLK */
};
对于记录锁,cmd 是 F_GETLK、F_SETLK 或 F_SETLKW。
多个进程在一个给定的字节上可以有一把共享的读锁,但是在一个给定字节上只能有一个进程独用的一把写锁。进一步而言,如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一把独占性的写锁,则不能再对它加任何读锁。
如果一个进程对一个文件区间已经有了一把锁,后来该进程又企图在同一文件区间再加一把锁,那么新锁将替换老锁。
加读锁时,该描述符必须是读打开;加写锁时,该描述符必须是写打开。
锁的隐含继承和释放
关于记录锁的自动继承和释放有三条规则:
- 锁与进程和文件两方面有关。这有两重含义:当一个进程终止时,它所建立的锁全部释放;任何时候关闭一个描述符时,则该进程通过这一描述符可以引用的文件上的任何一把锁都被释放(这些锁都是该进程设置的)。
- 由 fork 产生的子进程不继承父进程所设置的锁。
- 在执行 exec 后,新程序可以继承原执行程序的锁。但是注意,如果对一个文件描述符设置了 close-on-exec 标志,那么当作为 exec 的一部分关闭该文件描述符时,对相应文件的所有锁都被释放了。
I/O多路转接
select 和 pselect 函数
#include <sys/select.h>
int select(int maxfdp1, fd_set *restrict readfds,
fd_set *restrict writefds, fd_set *restrict exceptfds,
struct timeval *restrict tvptr);
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
#include <sys/select.h>
int pselect(int maxfdp1, fd_set *restrict readfds,
fd_set *restrict writefds, fd_set *restrict exceptfds,
const struct timespec *restrict tsptr,
const sigset_t *restrict sigmask);
poll 函数
#include <poll.h>
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor to check, or <0 to ignore */
short events; /* events of interest on fd */
short revents; /* events that occurred on fd */
};
异步 I/O
SysV 异步 I/O
在 SysV 中,异步 I/O 是 STREAMS 系统的一部分。它只对 STREAMS 设备和 STREAMS 管道起作用。SysV 的异步 I/O 信号是 SIGPOLL。
BSD 异步 I/O
在 BSD 派生的系统中,异步 I/O 是 SIGIO 和 SIGURG 两个信号的组合。前者是通用异步 I/O 信号,后者则只用来通知进程在网络连接上到达了带外的数据。
为了接收 SIGIO 信号,需执行下列三步:
- 调用 signal 或 sigaction 为 SIGIO 信号建立信号处理程序。
- 以命令 F_SETOWN 调用 fcntl 设置进程 ID 和进程组 ID,它们将接收对于该描述符的信号。
- 以命令 F_SETFL 调用 fcntl 设置 O_ASYNC 文件状态标志,使在该描述符上可以进行异步 I/O。
第 3 步仅能对指向终端或网络的描述符执行,这是 BSD 异步 I/O 设施的一个基本限制。
对于 SIGURG 信号,只需执行第 1 步和第 2 步。该信号仅对引用支持带外数据的网络连接描述符而产生。
readv 和 writev 函数
readv 和 writev 函数用于在一次函数调用中读、写多个非连续缓冲区。
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
struct iovec {
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
};
writev 以顺序 iov[0],iov[1] 至 iov[iovcnt-1] 从缓冲区中聚集输出数据。writev 返回输出的字节总数,通常,它应等于所有缓冲区长度之和。
readv 则将读入的数据按上述同样顺序散布到缓冲区中。readv 总是先填满一个缓冲区,然后再填写下一个。readv 返回读到的总字节数。如果遇到文件结尾,已无数据可读,则返回 0。
存储映射 I/O
存储映射 I/O 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。与此类似,将数据存入缓冲区,则相应字节就自动地写入文件。
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);
与映射存储区相关的有 SIGSEGV 和 SIGBUS 两个信号。信号 SIGSEGV 通常用于指示进程试图访问对它不可用的存储区。如果进程企图存数据到 mmap 指定为只读的映射存储区,那么也产生此信号。如果访问映射区的某个部分,而在访问时这一部分实际上已不存在,则产生 SIGBUS 信号。
在调用 fork 之后,子进程继承存储映射区,调用 exec 后的新程序则不继承此存储映射区。
调用 mprotect 可以更改一个现存映射存储区的权限。
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
如果在共享存储映射区中的页已被修改,那么我们可以调用 msync 函数将该页冲洗到被映射的文件中。
#include <sys/mman.h>
int msync(void *addr, size_t len, int flags);
进程终止时,或调用了 munmap 之后,存储映射区就被自动解除映射。关闭文件描述符 filedes 并不解除映射区。
#include <sys/mman.h>
int munmap(caddr_t addr, size_t len);
调用 munmap 不会使映射区的内容写到磁盘文件上。