下表中列出的文件系统同时支持建议性锁定和强制性锁定。
表 5–4 支持的文件系统
文件系统 |
说明 |
---|---|
ufs |
缺省的基于磁盘的文件系统 |
fifofs |
为进程提供对数据的公用访问的命名管道文件的伪文件系统 |
namefs |
主要由 STREAMS 用来在文件顶部动态挂载文件描述符的伪文件系统 |
specfs |
提供对特殊字符设备和块设备的访问的伪文件系统 |
NFS 只支持建议性文件锁定。proc 和 fd 文件系统不支持文件锁定。
只能使用有效的打开描述符来请求对文件进行锁定。对于读取锁定,文件必须至少是使用读取权限打开的。对于写入锁定,文件也必须是使用写入权限打开的。在以下示例中,将打开文件以进行读取和写入访问。
... filename = argv[1]; fd = open (filename, O_RDWR); if (fd < 0) { perror(filename); exit(2); } ...
要锁定整个文件,请将偏移量设置为零并将大小设置为零。
可以使用多种方法在文件上设置锁定。方法的选择取决于锁定如何与程序的其余部分进行交互、性能以及可移植性。本示例使用 POSIX 标准兼容的 fcntl(2) 接口。此接口尝试锁定文件,直到发生以下情况之一:
成功设置文件锁定。
出现错误。
超过 MAX_TRY,程序停止尝试锁定文件。
#include <fcntl.h> ... struct flock lck; ... lck.l_type = F_WRLCK; /* setting a write lock */ lck.l_whence = 0; /* offset l_start from beginning of file */ lck.l_start = (off_t)0; lck.l_len = (off_t)0; /* until the end of the file */ if (fcntl(fd, F_SETLK, &lck) <0) { if (errno == EAGAIN || errno == EACCES) { (void) fprintf(stderr, "File busy try again later!\n"); return; } perror("fcntl"); exit (2); } ...
使用 fcntl(2),可以通过设置结构变量来设置锁定请求的类型和起始位置。
不能使用 flock(3UCB) 锁定映射文件。但是,可以针对映射文件使用面向多线程的同步机制。可以在 POSIX 风格和 Solaris 风格下使用这些同步机制。
锁定记录时,请不要将锁定段的起始点和长度设置为零。否则,此锁定过程与文件锁定相同。
使用记录锁定的原因是存在数据争用。因此,当无法获取所有所需锁定时,应收到失败响应:
等待一定时间后,再次尝试
终止该过程,向用户发出警告
使进程进入休眠状态,直到向该进程发送已释放锁定的信号
执行上述操作的某些组合
本示例说明使用 fcntl(2) 锁定的记录。
{ struct flock lck; ... lck.l_type = F_WRLCK; /* setting a write lock */ lck.l_whence = 0; /* offset l_start from beginning of file */ lck.l_start = here; lck.l_len = sizeof(struct record); /* lock "this" with write lock */ lck.l_start = this; if (fcntl(fd, F_SETLKW, &lck) < 0) { /* "this" lock failed. */ return (-1); ... }
下一示例说明 lockf(3C) 接口。
#include <unistd.h> { ... /* lock "this" */ (void) lseek(fd, this, SEEK_SET); if (lockf(fd, F_LOCK, sizeof(struct record)) < 0) { /* Lock on "this" failed. Clear lock on "here". */ (void) lseek(fd, here, 0); (void) lockf(fd, F_ULOCK, sizeof(struct record)); return (-1); }
应使用与设置锁定相同的方法来删除锁定。只是锁定类型有所不同 (F_ULOCK)。解除锁定不受其他进程的阻止,并且只影响调用进程所设置的锁定。解除锁定只影响在先前锁定调用中指定的文件段。
可以确定哪个进程在持有锁定。可以按照前面示例所示设置锁定,并在 fcntl(2) 中使用 F_GETLK。
struct flock lck; lck.l_whence = 0; lck.l_start = 0L; lck.l_len = 0L; do { lck.l_type = F_WRLCK; (void) fcntl(fd, F_GETLK, &lck); if (lck.l_type != F_UNLCK) { (void) printf("%d %d %c %8ld %8ld\n", lck.l_sysid, lck.l_pid, (lck.l_type == F_WRLCK) ? 'W' : 'R', lck.l_start, lck.l_len); /* If this lock goes to the end of the address space, no * need to look further, so break out. */ if (lck.l_len == 0) { /* else, look for new lock after the one just found. */ lck.l_start += lck.l_len; } } } while (lck.l_type != F_UNLCK);
fcntl(2) 与 F_GETLK 命令可以在等待服务器响应时处于休眠状态。如果客户机或服务器出现资源不足的情况,则此命令可能会失败,同时返回 ENOLCK。
将 lockf(3C) 与 F_TEST 命令一起使用来测试进程是否在持有锁定。此接口并不返回有关锁定的位置或拥有权的信息。
(void) lseek(fd, 0, 0L); /* set the size of the test region to zero (0). to test until the end of the file address space. */ if (lockf(fd, (off_t)0, SEEK_SET) < 0) { switch (errno) { case EACCES: case EAGAIN: (void) printf("file is locked by another process\n"); break; case EBADF: /* bad argument passed to lockf */ perror("lockf"); break; default: (void) printf("lockf: unexpected error <%d>\n", errno); break; }
进程派生时,子进程会得到父进程打开的文件描述符的副本。子进程不继承锁定,因为锁定由特定进程拥有。父进程和子进程共享每个文件的公用文件指针。并且都可以尝试在同一文件中的相同位置设置锁定。使用 lockf(3C) 和 fcntl(2) 都会出现此问题。如果持有记录锁定的程序进行派生,则子进程应该关闭此文件。关闭此文件之后,子进程应该重新打开此文件以设置新的独立文件指针。
UNIX 锁定功能可以检测和避免死锁。仅当系统准备将记录锁定接口置于休眠状态时才会发生死锁。执行搜索来确定两个进程是否处于死锁状态。如果检测到潜在的死锁,则锁定接口会失败,并设置 errno 来指示死锁。使用 F_SETLK 设置锁定的进程不会导致死锁,因为当不能被授予锁定时,这些进程并不会等待。