14.2 非阻塞IO
对于一个给定的描述符有两种方法对其指定非阻塞IO:
1.如果调用open获得描述符,则可指定O_NONBLOCK标志
2.对于已经打开的一个描述符,则可调用fcntl,由该函数打开O_NONBLOCK文件状态标志。
----------------------------------------------------------------------------
14.3 记录锁
记录锁的功能:当一个进程正在读或者修改文件的某个部分时,它可以阻止其他进程修改同一文件区。更合适的术语可能是字节范围锁,因为它锁定的是文件中的一个区域。
#include
int fcntl(int filedes , int cmd , ... /* struct flock *flockptr */);
对于记录锁,cmd是F_GETLK ,F_SETLK 或者 F_SETLKW 。 第三个参数是一个指向flcok结构的指针。
F_GETLK : 判断由flockptr所描述的锁是否会被另一把锁排斥(阻塞)。如果存在一把锁,它阻止创建由flockptr所描述的锁,则把该现存锁的信息写到flockptr指向的
结构中。如果不存在这种情况,则除了将l_type设置为F_UNLCK之外,flockptr所指向结构中的其他信息保持不变。
F_SETLK : 设置由flockptr所描述的锁,如果试图建立一把读锁(l_type设为F_RDLCK)或写锁(l_type设为F_WRLCK),而按兼容性规则不能允许建立该锁,则fcntl立即
出错返回,此时errno设置为EACCES或EAGAIN。如果l_type设为F_UNLCK,则此命令可用来清除由flockptr说明的锁。
F_SETLKW : 这是F_SETLK的阻塞版本(w代表等待)。如果当前所请求区间的某个部分已经被另一个进程设置了一把锁,而按照规则由flockptr所请求的锁不能建立,则该进程
阻塞(休眠),如果请求创建的锁已经可用,或者休眠由信号中断,则该进程被唤醒。
用F_GETLK测试能否建立一把锁,然后用F_SETLK 和 F_SETLKW企图建立一把锁,这两者不是一个原子操作。因此有可能在两次fcntl调用之间会有另一个进程插入并建立一把锁,
从而使第一次fcntl的锁测试结果失效。
struct flock{
short l_type ;
off_t l_start;
short l_whence;
off_t l_len;
pid_t l_pid;
}
l_type : 锁类型, F_RDLCK(共享读锁) , F_WRLCK(独占性写锁) , F_UNLCK(解锁一个区域)
l_start : 锁开始位置的相对偏移量(字节),开始位置有l_start 和 l_whence 共同决定。
l_whence : 决定相对偏移量的起点,可选用的值为 SEEK_SET , SEEK_CUR , SEEK_END。
l_len : 锁覆盖的长度。如果为0,则表示锁的区域从其起始位置开始直至最大可能偏移量为止。为了锁住整个文件,我们设置l_start和l_whence使锁的起点在文件起始处,
并且设置 l_len 为0。
注意记录锁只作用于不同进程提出的锁请求,对于单进程,后一个锁请求会替换前一个锁请求获取的锁。
加读锁时,文件描述服必须是读打开。加写锁时,文件描述符必须是写打开。
关于记录锁的自动继承和释放的三条规则:
1.当一个进程终止时,他所建立的所有锁全部释放 ; 任何时候关闭一个描述符时,则该进程通过这一描述符可以引用的文件上的任何一把锁都被释放。
2.由fork产生的子进程不继承父进程所设置的锁。 这意味着,如果一个进程得到一把锁,然后调用fork,那么对于父进程获得的锁而言,子进程被视为另一个进程,对于从父进程继
承过来的任意描述符,子进程需要用fcntl才能获得它自己的锁。
3.在执行exec之后,新程序可以继承原执行程序的锁。 但是,如果对一个文件描述符设置了close-on-exec标志,那么当作为exec的一部分关闭该文件描述符时,对应文件的所有
锁都被释放了。
---------------------------------------------------------------------------------
14.5 IO多路转接
1.select函数
#include
int select(int maxfdp ,
fd_set *restrict readfs ,
fd_set *restrict writefds ,
fd_set *restrict exceptfds ,
struct timeval *restrict tvptr);
maxfdp:“最大描述符加1”。在三个描述符集中找出最大的描述符编号,然后加1,就是maxfdp的值,实际上就是要检查的描述符数,内核只在此范围内寻找打开的位。可将该参数设为
FD_SETSIZE,它是中的一个常量,说明了最大的描述符数,但它对于一般程序而言通常太大了。
tvptr : (a)NULL : 永远等待
(b)tvptr->tv_sec==0 && tvptr->tv_usec==0 : 完全不等待,测试所有指定的描述符并立即返回
(c)tvptr->tv_sec!=0 && tvptr->tv_usec!=0 : 等待指定的时间
readfds : 指向关心的可读的描述符集
writefds : 指向关心的可写的描述符集
exceptfds : 指向关心的处于异常条件的描述符集
readfds,writefds,exceptfds这三个任意一个或者全部都可以为空指针,表示对相应状态并不关心。如果三个都为空指针,则select提供了比sleep更精确的计时器。
每个可能的描述符在fd_set中占一位
fd_set支持的操作:
1、分配一个这种类型的变量
2、将这种类型的变量值赋予同类型的另一个变量
3、对fd_set变量使用下面四个函数中的一个
#include
int FD_ISSET(int fd , fd_set *fdset); //测试描述符是否在描述符集中
void FD_CLR(int fd , fd_set *fdset); //清除某个描述符
void FD_SET(int fd , fd_set *fdset); //设置某个描述符
void FD_ZERO(fd_set *fdset); //将描述符集中的每一位设为0,即清除所有描述符
select的返回值:
1. -1 : 表示出错。比如在所指定的描述符都没准备好时捕捉到一个信号,返回-1,此时并不修改其中任何描述符集。
2. 0 : 表示没有描述符准备好。若指定的描述符都没有准备好,而指定的时间已经超过,则所有描述符集被清0。
3. 正整数 : 表示已经准备好的描述符数,该值是三个描述符集中已准备好的描述符数之和。
注意:如果在一个描述符上碰到了文件结尾处,则select认为该描述符是可读的。然后调用read时,它返回0,这是unix系统指示到达文件结尾处的方法。
POSIX提供了select的变体,称为pselect
#include
int pselect(int maxfdp1 ,
fd_set *restrict readfs ,
fd_set *restrict writefds ,
fd_set *restrict exceptfds ,
const struct timespec *restrict tsptr,
const sigset_t *restrict sigmask);
它与select的区别:
(1)pselect使用timespec结构作为超时参数的类型,更精准。
(2)pselect的超时值被声明为const
(3)pselect可使用一可选择的信号屏蔽字。若sigmask不为空,调用pselect时,以原子操作的方式安装该信号屏蔽字,在返回时恢复以前的信号屏蔽字。若sigmask
为空,则行为与select相同。
2. poll函数
#include
int poll(struct pollfd fdarray[] , nfds_t nfds , int timeout);
poll不是为每个状态构造一个描述符集,而是构造一个pollfd结构数组,每个数组元素指定一个描述符编号以及对其所关心的状态。
struct pollfd{
int fd;
short events;
short revents;
}
fdarray数组中的元素数由nfds说明。
通过设置events成员,告诉内核我们对该描述符关心什么事件。返回时,内核设置revents成员,以说明对于该描述符已经发生了什么事件。
events和revents的值见 P385 表14-6
timeout参数的功能与select的超时参数类似,不同值的含义也类似,不再赘述。
----------------------------------------------------------------------------------------
14.7 readv 和 writev 函数
readv 和 writev 函数用于在一次函数调用中读写多个非连续缓冲区,有时也将这两个函数成为散布读和聚集写。
#include
ssize_t readv(int filedes , const struct iovec *iov , int iovcnt);
ssize_t wirtev(int filedes , const struct iovec *iov , int iovcnt);
这两个函数的第二个参数是指向iovec结构数组的一个指针:
struct iovec{
void *iov_base;
size_t iov_len;
}
-------------------------------------------------------------------------------------------
14.9 存储映射
#inlcude
void *mmap(void *addr, size_t len ,int prot , int flag , int filedes, off_t off);
该方法将一个给定的文件映射到一个存储区域中。
addr : 用于指定映射存储区的起始地址,通常设为0,表示由系统选择该存储区的起始地址。应该是系统虚存页长度的倍数
filedes : 指定要被映射文件的描述符,该描述符必须为打开状态。
off : 映射字节在文件中的起始偏移量。通常应该是系统虚存页长度的倍数
len : 映射的字节数
prot : 对映射存储区的保护要求。PROT_READ 映射区可读,PROT_WRITE 映射区可写, PROT_EXEC 映射区可执行 , PROT_NONE 映射区不可访问。prot可以为前三个值的
任意组合按位或,或者PROT_NONE。对指定存储映射区的保护要求不能超过文件open模式访问权限。例如,若该文件是只读打开的,那么对映射区就不能指定为PROT_WRITE。
flag参数影响映射存储区的多种属性:
MAP_FIXED : 返回值必须为addr。不利于可移植性,不鼓励使用此标志。
MAP_SHARED :对存储区的存储操作会同步到文件中。
MAP_PRIVATE : 对存储区的存储操作导致创建该映射文件的一个私有副本。所有后来对该映射区的引用都是引用该副本,而不是原始文件。此标志常用于调试程序。
与存储映射区相关的有SIGSEGV 和 SIGBUS 两个信号:
SIGSEGV 通常用于指示进程试图访问对它不可用的存储区。如果进程试图存数据到mmap指定为只读的映射存储区,那么也产生此信号。
如果访问映射区的某个部分,而在访问时这一部分实际上已不存在,则产生SIGBUS信号。
在调用fork之后,子进程继承存储映射区(因为子进程复制父进程地址空间,而存储映射区是该地址空间中的一部分),但是由于同样的理由,调用exec之后的新程序则不继承此存储映射区。
#include
int mprotect(void *addr , size_t len , int prot);
调用mprotect 可以更改映射存储区的权限:
#include
mprotect(void *addr , size_t len ,int prot);
如果在共享存储映射区中的页已被修改,那么我们可以调用msync将该页冲洗到被映射的文件中。msync函数类似与fsync,但作用与存储映射区。
#include
int msync(void *addr , size_t len , int flags);
flags : 控制如何冲洗存储区。MS_ASYNC 或者 MS_SYNC。
进程终止时,或者调用了munmap之后,存储映射区就被自动解除映射。关闭文件描述符filedes并不解除映射区。
#include
int munmap(caddr_t addr , size_t len);
munmap不会影响被映射的对象,也就是说,调用munmap不会使映射区的内容写到磁盘文件上。对于MAP_SHARED区磁盘文件的更新,在写到存储映射区时按内核虚存算法自动进行。解除了映射后,
对于MAP_PRIVATE存储区的修改被丢弃。
阅读(423) | 评论(0) | 转发(0) |