记录锁可用于有亲缘关系或无亲缘关系的进程之间共享某个文件的读与写。被锁住的文件通过其描述符访问,执行上锁操作的函数是fcntl。这种类型的锁通常是在内核中维护的,所以这些锁可以用于不同进程间的上锁,而不仅用于同一进程内不同线程间的上锁。
现在假如我们要在不同进程之间进行互斥操作,如果选用互斥锁或者读写锁的话,我们必须先让这些进程共享某个内存区,然后再该共享内存区中使用某种类型的同步变量。而如果使用fcntl记录上锁的话,就不需要共享内存。
下面将着重讲解fcntl函数的使用。
POSIX fcntl记录上锁
#include <fcntl.h>
int fcntl(int fd, int cmd, .../*struct flock *arg*/);
用于记录上锁的cmd参数共有三种值。这三个命令要求第三个参数arg是指向某个flock结构的指针
strcut flock {
short l_type; //F_RDLCK, F_WRLCK, F_UNLCK
short l_whence; //SEEK_SET, SEEK_CUR, SEEK_END
off_t l_start;
off_t l_len;
pid_t l_pid; //PID returned by F_GETLK
}
参数cmd的三种命令如下:
- F_SETLK:获取(l_type成员为F_RDLCK或F_WRLCK)或释放(l_type为F_UNLCK)由arg指向的flock结构所描述的锁。如果无法将该锁授予调用fcntl这个函数的进程,该函数就立即返回一个EACCES或EAGAIN错误而不阻塞。
- F_SETLCW:该命令与上一个命令类似,不过如果无法将所请求的锁授予调用进程,调用进程将阻塞到该锁能够分配为止。(W的意思就是wait)
- F_GETLK:检查由arg指向的锁以确定是否有某个已存在的锁会妨碍将新锁授予调用进程。如果当前没有这样的锁存在,那么由arg指向的flock结构的l_type成员就被设置成为F_UNLCK。否则关于这个已存在的锁的信息将在由arg指向的flock结构中返回(也就是说,该结构的内容由fcntl函数覆写),其中包括持有该锁的进程的进程ID。 应该清楚发出F_GETLK命令后紧接着发出F_SETLK命令不是一个原子操作。也就是说,如果我们发出F_GETLK命令,并且执行该命令的fcntl函数返回时设置l_type的值为F_UNLCK,那么跟着立即发出F_SETLK命令不能保证其fcntl函数会成功返回。因为这两次命令调用期间可能有另外一个进程运行并获取了我们想要的锁。 其实提供F_GETLK命令的原因在于:当执行F_SETLK命令的fcntl函数返回一个错误时,导致该错误的某个锁的信息可由F_GETLK命令返回,从而允许我们确定是哪个进程锁住了我们所要请求的文件区。但是即使是这样的情形,F_GETLK命令也可能返回该文件区已解锁的信息,因为在F_SETLK和F_GETLK命令之间,该文件区可能被解锁。
其实flock结构主要是描述锁的类型(读入锁或者是写入锁)以及待锁住的字节范围。跟lseek一样,起始字节偏移是作为一个相对偏移(l_start)伴随其解释(l_whence)制定的。
l_whence成员有三种取值:
- SEEK_SET:l_start相对于文件的开头解释。
- SEEK_CUR:l_start相对于文件的当前字节偏移。即当前读写指针的位置。
- SEEK_END:l_start相对于文件的末尾解释。
PS:对于一个打开着某个文件的进程来说,当它关闭该文件或者它本身终止时,与该文件关联的所有锁都被删除。这里说的锁仅限于用fcntl记录上的锁。而如果是互斥锁,条件变量,读写锁,POSIX信号量的话,并不在进程结束的时候执行清理工作。