2016-03-01
非阻塞io
系统调用分为低速调用系统和其他,低速系统调用时可能会使进程永远阻塞的一类系统调用
- 如果数据并不讯在,则读文件可能会使调用者永远住的(例如读管道,终端设备和网络设备)
- 如果数据不能立即被接收,则写这些同样的文件也会使调用者永远阻塞
- 在某些条件发生之前,打开文件会被阻塞(例如打开一个终端设备可能需要等到与之连接的调制解调器应答,又如若以只写方式打开FIFO,那么在没有其他进程以用读方式打开该FIFO时也要等待)
- 对已经加上强制性记录锁的文件进行读写
- 某些ioctl操作
- 某些进程间通信函数
虽然读写磁盘文件会使调用在短暂时间内阻塞,但并不能把它们视为低速
非阻塞io使我们可以调用不会永远阻塞的io操作,例如open read write。如果这种操作不能完成,则立即出错返回,表示该操作如继续执行将继续阻塞下去。
对于一个给定的描述符有两种方法对其指定非阻塞io
- 如果是石澳泳open以获得该描述符,则可指定O_NONBLOCK标志
- 对于已经打开的一个描述符,则可以调用fcntl打开O_NONBLOCK文件状态标志。
记录锁
当两个人同时编辑一个文件时,其后结果将如何呢?很多unix系统中,该文件的最后状态取决于写该文件的最后一个进程。但是对于有些应用程序,例如数据库,有时进程需要确保它在单独写一个文件。为了向进程提供这种功能,较新的unix系统提供了记录锁机制。
记录锁的功能时:一个进程正在读或者修改文件的某个部分,可以阻止其他进程修改同一文件区、对于unix记录这个定语也是无用,因为unix内核根本没有使用文件记录这种概念。一个更适合的属于可能是区域锁,因为它锁定的指示文件的一个区域(也肯能是整个文件)。
历史
早期的伯克利版本只支持BSD flock函数。此函数只锁住整个文件,而不是锁住某个区域。但是POSIX的fcntl函数可以锁十多年间中的任一区域,大至整个文件,小至单个字节。
fcntl记录锁
int fcntl(int filedes, int cmd, ..., struct flock *flockptr)
对于记录锁,cmd是F_GETLK、F_SETLK、F_SETLKW第三个参数是一个指向flock结构指针
struct flock {
short l_type;
off_t lstart;
short l_whence;
off_t l_len;
pid_t l_pid;
}
flock 结构说明
- 所希望的锁类型: F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)、F_UNLCK(解锁一个区域)
- 要加锁或解锁的区域的起始地址,由l_start 和 l_whence两者决定。l_stat是相对位移量,l_whence则决定了相对位移量的起点。
- 区域的长度,由l_len表示
关于加锁和解锁区域的说明还要注意下列各点
- 该区域可以再当前文件尾端处开始或者超越其尾端处开始,但是不能再文件起始之前开始或者越过该起始位置。
- 若l_len为0,则表示锁的区域从其起点开始直至最大可能位置为止。也就是不管添写到该文件中多少数据,它都处于锁的范围。
- 为了锁定整个文件,通常的方法是将l_start说明为0,l_whence说明为SEEK_SET,l_len说明为0。
上面提到了两种类型的锁:共享读锁和独占写锁、基本规则是:多进程在一个给定的字节上可以有一把共享的读锁,但是在一个给定的字节上的写锁只能由一个进程独用。更进一步而言,如果在一个给定的字节上已经有一把或多把读锁,则不能再该直接上再加写锁;如果在一个直接上已经有一把独占性写锁,则不能再对它加任何读锁。
加读锁时,描述符必须是读打开的,加写锁该描述符必须是写打开的。
- F_GETLK决定flockptr所描述的锁死否被另外一把锁所排斥。如果存在一把锁,它阻止创建由flockptr所描述的锁,则把这把现存的锁的信息写到flockptr指向的结构中。如果不存在这种情况,则除了将l_type设置为F_UNLCK之外,flockptr锁指向结构中的其他信息保持不变。
- F_SETLK 设置由flockptr所描述的锁。如果试图建立一把按上述兼容性规则并不允许的锁,则fctnl立即出错返回,此时errno设置为EACCES或EAGAIN
- F_SETLKW 这是F_SETLK的阻塞版本。如果由于存在其他锁,那么按兼容性规则由flockptr所要求的锁不能被创建,则调用进程睡眠。如果捕捉到信号则睡眠中断。
死锁:如果两个进程相互等待对方并且持有并不释放的资源时,则这两个进程就处于死锁状态。如果一个进程已经控制了文件中的一个加锁区域,然后它又试图对另一个进程控制的区域加锁,则它就会睡眠,这种情况下,就有发生死锁的可能性。检测到死锁时,内核必须选择一个进程收到出错的返回。
锁的隐含继承和释放
- 锁与进程、文件两方面相关。当一个进程终止时,它所建立的锁全部释放,任何时候关闭一个描述符时,则该进程通过这一描述符可以存放的文件上的任何一把锁都被释放。
- 由fork产生的子程序不继承父进程所设置的锁。这意味着,若一个进程得到一把锁,然后调用fork,那么对于父进程获得的锁而言,子进程被视为另一个进程。对于从父进程处继承过来的任一描述符,子进程要调用fcntl以获得他自己的锁。这与锁的作用是相一致的。锁的作用就是阻止多个进程同时写同一个文件。如果子进程继承父进程的锁,则父子进程可以同时写同一个文件。
- 在执行exec后,新程序可以继承原执行程序的锁。
建议性锁可由精灵进程使用以保证该精灵进程只有一个副本在运行。启动时,很多精灵进程都把它们的进程id写到一个各自专用的pid上。系统停机时,可以从这些文件中取用这些精灵进程的进程id。防止一个精灵进程有多份副本同时运行的方法是:在精灵进程开始运行时,在它的进程id文件上设置一把写锁。如果在它运行时一直保持这把锁,则不可能再启动其他副本。
建议性锁和强制性锁
考虑数据库存取例程,如果库中所有函数都以一致的方法处理记录锁,则称使用这些函数存的任何进程集为合作进程。如果这些函数是唯一的用来存取数据库的函数,那么它们使用建议性锁是可行的。但是建议性锁并不能阻止对数据库文件有写许可权限的任何其他进程写数据库文件。不使用协同一致的方法来存取数据库的进程是一个非合作进程。
强制性锁机制中,内核每一个open read write都要检查调用进程对正在存取的文件是否违背了某一把锁的作用。
对一个特定文件打开其设置组id位,关闭其组执行位,则对该文件启动了强制性锁机制。因为当组执行位关闭时,设置组id位不再有意义,所以svr3的设计者用两者的这种组合来指定对一个文件的锁是强制性的而非建议性的。
建议性锁主要用于协作进程间文件加锁,对于有写权限的非协作进程,可以看做是协作进程间的一种约定,建议性锁无能为力。
如果欲打开的文件具有强制性锁,而且open调用中flag为O_TRUNC或者O_CREATE,则不论是否制定O_NONBLOCK open都立即出错返回,errno设置为EAGAIN。
强制性锁机制对unlink函数没有影响
如果一个数据文件大家都是可读的,并且是强制性锁机制起作用的。如果一个别有用心的用户对该文件保有一把读锁,则其他进程不能再写该文件。
网友评论