进程间通信-fcntl记录上锁
这是读写锁的一种扩展类型,可用于有亲缘关系或无亲缘关系的进程之间共享某个文件的读写。这些锁用于不同进程间的上锁,而不是用于同一进程中不同线程间的上锁。
fcntl记录上锁,可以指定文件中待上锁和解锁部分的字节范围。
Posix记录上锁定义了一个特殊的字节范围来指定整个文件,它的起始偏移为0,长度也为0.
术语-粒度
用于标记能被锁住的对象的大小。对于Posix记录上锁来说,粒度就是单个字节。通常情况下粒度越小,允许同时使用的用户数就越多。
记录上锁的Posix接口是fcntl函数
1 |
|
- F_SETLK 获取(l_type为F_RDLCK或F_WRLCK)或释放(l_type为F_UNLCK)由arg指向的flock结构所描述的锁,如果无法将该锁授予调用进程,该函数就立即返回一个EACCESS或EAGAIN错误,并不阻塞
- F_SETLKW 该命令与上一个命令类似,不过在无法获取锁请求的锁时,调用线程将阻塞
- F_GETLK 检查由arg指向的锁以确定是否有某个已存在的锁会妨碍将新锁授予调用线程。如果当前没有这样的锁存在,由arg指向的struct flock的l_type成员就被置为F_UNLCK。否则,关于这个已存在锁的信息将在由arg指向的struct flock中返回,中包含持有该锁的进程ID
注意:
- F_GETLK完了接着F_SETLK不是一个原子操作,并不能保证F_SETLK会成功,因为在两个函数执行期间,很可能有另一个进程已经获取了我们想要的锁。
提供F_GETLK的原因在于:当执行F_SETLK的fcntl返回一个错误时,导致该错误的某个锁的信息可由F_GETLK来返回,从而允许我们确定是哪个进程锁住了所请求的文件区,以及上锁方式。
但同样有上面的原因,这两个操作不是原子操作,在F_GETLK调用之前,很可能原来持有锁的进程已经释放了锁。 - 对于一个文件的任意字节,同一时刻,最多只能存在一种类型的锁(读出锁或写入锁),而且,一个给定字节可以有多个读出锁,但只能有一个写入锁。
- 当一个描述符不是打开用来读时,如果对它请求一个读出锁,错误就会发生;同样,当一个描述符不是打开用来写时,如果对它请求一个写入锁,错误也会发生
- 对于一个打开着某个文件的给定进程来说,当它关闭该文件的所有描述符或它本身终止时,与该文件关联的所有锁都被删除。锁不能通过fork由子进程继承
- 记录上锁不应该同标准I/O函数库一起使用,因为该函数库会执行内部缓冲。当某个文件需要上锁时,为避免问题,应对它使用read和write。
在进程终止时由内核完成已有锁清理工作的特性,只有fcntl记录上锁完全提供了,System V信号量把它作为一个选项提供。互斥锁、条件变量、读写锁、Posix信号量,并不在进程终止时执行清理工作。 - 锁住整个文件的两种方式
- l_whence=SEEK_SET, l_start=0, l_len=0(常用,只需一个函数调用)
- 使用lseek把读写指针定位到文件头,然后指定l_whence=SEEK_CUR, l_start=0, l_len=0
劝告性上锁
Posix记录上锁称为劝告性上锁。其含义是内核维护着已由各个进程上锁的所有文件的正确信息,但是它不能防止一个进程写已由另一个进程读锁定的某个文件;类似的,它也不能防止一个进程读已由另一个进程写锁定的某个文件。当然,前提是该进程有读或写该文件的足够权限。
强制性上锁
可以用chmod +l filename来对某个文件启用强制性上锁
强制性上锁虽然能够阻止非协作进程读一个已被锁住的文件,但是也不能保证万无一失。
建议:如果有多个进程在更新同一个文件,那么所有进程必须使用某种上锁形式协作。
读出者和写入者的优先级
不同的系统可能有不同的实现,有的是以FIFO顺序处理上锁请求的,而不管上锁请求的类型;有的则会优先考虑读出请求
启动一个守护进程的唯一副本
记录上锁的一个常见用途是确保某个程序(如守护程序)在任何时刻只有一个副本在运行。
守护进程维护一个只有1行文本的文件,其中包含它的进程ID。它打开这个文件,必要的时候创建它,然后请求整个文件的一个写入锁。如果没有取得该锁,我们就知道该程序有另一个副本在运行,于是输出一个出错信息并终止。
如果某个守护进程提前崩溃了,内核会自动释放它的记录锁。
文中代码托管在: https://github.com/carl-wang-cn/demo/tree/master/ipc