文件锁存放在一个数组当中,用来记录系统对于文件锁的使用情况。
文件锁的定义如下:
/* This is the file locking table. Like the filp table, it points to the
* inode table, however, in this case to achieve advisory locking.
*/
EXTERN struct file_lock {
short lock_type; /* F_RDLOCK or F_WRLOCK; 0 means unused slot */
pid_t lock_pid; /* pid of the process holding the lock */
struct inode *lock_inode; /* pointer to the inode locked */
off_t lock_first; /* offset of first byte locked */
off_t lock_last; /* offset of last byte locked */
} file_lock[NR_LOCKS];
调用函数对文件进行加锁的时候要查询这个表,然后再进行进一步的操作。
文件锁部分只有两个函数。lock_op函数用来为fcntl的文件锁调用服务。如果lock_op函数完成了unlock操作,这时候需要唤醒因为加锁失败而在等待的进程,这就使lock_rivive函数所要完成的工作。
/* This file handles advisory file locking as required by POSIX.
*
* The entry points into this file are
* lock_op: perform locking operations for FCNTL system call
* lock_revive: revive processes when a lock is released
*/
#include "fs.h"
#include <fcntl.h>
#include <unistd.h>
#include "file.h"
#include "fproc.h"
#include "inode.h"
#include "lock.h"
#include "param.h"
/*===========================================================================*
* lock_op *
*===========================================================================*/
PUBLIC int lock_op(f, req)
struct filp *f;
int req; /* either F_SETLK or F_SETLKW */
{
/* Perform the advisory locking required by POSIX. */
int r, ltype, i, conflict = 0, unlocking = 0;
mode_t mo;
off_t first, last;
struct flock flock;
vir_bytes user_flock;
struct file_lock *flp, *flp2, *empty;
/* Fetch the flock structure from user space. */
user_flock = (vir_bytes) name1;
r = sys_copy(who, D, (phys_bytes) user_flock,
FS_PROC_NR, D, (phys_bytes) &flock, (phys_bytes) sizeof(flock));
if (r != OK) return(EINVAL);
/* Make some error checks. */
ltype = flock.l_type;
mo = f->filp_mode;
if (ltype != F_UNLCK && ltype != F_RDLCK && ltype != F_WRLCK) return(EINVAL);
if (req == F_GETLK && ltype == F_UNLCK) return(EINVAL);
if ( (f->filp_ino->i_mode & I_TYPE) != I_REGULAR) return(EINVAL);
if (req != F_GETLK && ltype == F_RDLCK && (mo & R_BIT) == 0) return(EBADF);
if (req != F_GETLK && ltype == F_WRLCK && (mo & W_BIT) == 0) return(EBADF);
/* Compute the first and last bytes in the lock region. */
switch (flock.l_whence) {
case SEEK_SET: first = 0; break;
case SEEK_CUR: first = f->filp_pos; break;
case SEEK_END: first = f->filp_ino->i_size; break;
default: return(EINVAL);
}
/* Check for overflow. */
if (((long)flock.l_start > 0) && ((first + flock.l_start) < first))
return(EINVAL);
if (((long)flock.l_start < 0) && ((first + flock.l_start) > first))
return(EINVAL);
first = first + flock.l_start;
last = first + flock.l_len - 1;
if (flock.l_len == 0) last = MAX_FILE_POS;
if (last < first) return(EINVAL);
/* Check if this region conflicts with any existing lock. */
empty = (struct file_lock *) 0;
for (flp = &file_lock[0]; flp < & file_lock[NR_LOCKS]; flp++) {
if (flp->lock_type == 0) {
if (empty == (struct file_lock *) 0) empty = flp;
continue; /* 0 means unused slot */
}
if (flp->lock_inode != f->filp_ino) continue; /* different file */
if (last < flp->lock_first) continue; /* new one is in front */
if (first > flp->lock_last) continue; /* new one is afterwards */
if (ltype == F_RDLCK && flp->lock_type == F_RDLCK) continue;
if (ltype != F_UNLCK && flp->lock_pid == fp->fp_pid) continue;
/* There might be a conflict. Process it. */
conflict = 1;
if (req == F_GETLK) break;
/* If we are trying to set a lock, it just failed. */
if (ltype == F_RDLCK || ltype == F_WRLCK) {
if (req == F_SETLK) {
/* For F_SETLK, just report back failure. */
return(EAGAIN);
} else {
/* For F_SETLKW, suspend the process. */
suspend(XLOCK);
return(0);
}
}
/* We are clearing a lock and we found something that overlaps. */
unlocking = 1;
if (first <= flp->lock_first && last >= flp->lock_last) {
flp->lock_type = 0; /* mark slot as unused */
nr_locks--; /* number of locks is now 1 less */
continue;
}
/* Part of a locked region has been unlocked. */
if (first <= flp->lock_first) {
flp->lock_first = last + 1;
continue;
}
if (last >= flp->lock_last) {
flp->lock_last = first - 1;
continue;
}
/* Bad luck. A lock has been split in two by unlocking the middle. */
if (nr_locks == NR_LOCKS) return(ENOLCK);
for (i = 0; i < NR_LOCKS; i++)
if (file_lock[i].lock_type == 0) break;
flp2 = &file_lock[i];
flp2->lock_type = flp->lock_type;
flp2->lock_pid = flp->lock_pid;
flp2->lock_inode = flp->lock_inode;
flp2->lock_first = last + 1;
flp2->lock_last = flp->lock_last;
flp->lock_last = first - 1;
nr_locks++;
}
if (unlocking) lock_revive();
if (req == F_GETLK) {
if (conflict) {
/* GETLK and conflict. Report on the conflicting lock. */
flock.l_type = flp->lock_type;
flock.l_whence = SEEK_SET;
flock.l_start = flp->lock_first;
flock.l_len = flp->lock_last - flp->lock_first + 1;
flock.l_pid = flp->lock_pid;
} else {
/* It is GETLK and there is no conflict. */
flock.l_type = F_UNLCK;
}
/* Copy the flock structure back to the caller. */
r = sys_copy(FS_PROC_NR, D, (phys_bytes) &flock,
who, D, (phys_bytes) user_flock, (phys_bytes) sizeof(flock));
return(r);
}
if (ltype == F_UNLCK) return(OK); /* unlocked a region with no locks */
/* There is no conflict. If space exists, store new lock in the table. */
if (empty == (struct file_lock *) 0) return(ENOLCK); /* table full */
empty->lock_type = ltype;
empty->lock_pid = fp->fp_pid;
empty->lock_inode = f->filp_ino;
empty->lock_first = first;
empty->lock_last = last;
nr_locks++;
return(OK);
}
获取锁之后,首先进行error check。
1 锁类型必须是rdlck、wrlck或unlck之一。
2 getlck不能和unlck组合使用,getlck仅用于测试加读锁或写锁会不会被阻塞。
3 文件必须是常规文件 (regular file)
4 设置共享锁的时候,文件必须要是 可读 打开的
5 设置独占锁的时候,文件必须是 可写 打开的
进行以上错误检测之后,计算我们要设置的锁的起始位置。然后还要检测是否和现有的锁有冲突。比如,我们要对一个区域加共享锁,但是不知道这个区域有没有被别的进程加独占锁。这需要遍历整个file_lock table 。
查看已被占用的锁,即lock_type != 0的文件锁项。将文件锁表中锁的有关信息与我们要的锁的信息进行比较,查看冲突情况。
有几种情形不会造成冲突,可以直接跳过:
1. 锁的对象是不同文件
2 锁的区域无交集
3 两个都是共享锁
4 文件锁表中的锁属于当前进程,因此当前进程要设置锁(SET_LCK || SET_LCKW)的时候可以直接设置,有冲突的时候也会将之前的锁覆盖掉。这时如果是UNLCK操作,也不会引起冲突,这个在下面会处理。下面对不会因此冲突的unlock操作单独处理。
下面设置conflict为1,注意,这只意味着可能有冲突,还有不会冲突的如上红字描述的情形。
接着考虑不满足上面四个条件的情形:
1 如果是GETLCK操作,跳出for循环,根据是否有冲突,做不同的处理
2 如果是SETLCK操作,想要获得共享锁或独占锁的时候,因为不满足上面的情形直接失败。
3 如果是SETLCKW操作,调用suspend使进程挂起等待。
4 剩下一种情况就是unlock了。解锁操作根据解锁的范围做不同的操作。
注意可能将原先上锁区域分成两部分的情况,这时候需要重新找一个空的slot放置新的锁。
unlock之后调用lock_revive唤醒正在等待的进程。
没有冲突的时候使用之前记录的empty记录新的锁。
/*===========================================================================*
* lock_revive *
*===========================================================================*/
PUBLIC void lock_revive()
{
/* Go find all the processes that are waiting for any kind of lock and
* revive them all. The ones that are still blocked will block again when
* they run. The others will complete. This strategy is a space-time
* tradeoff. Figuring out exactly which ones to unblock now would take
* extra code, and the only thing it would win would be some performance in
* extremely rare circumstances (namely, that somebody actually used
* locking).
*/
int task;
struct fproc *fptr;
for (fptr = &fproc[INIT_PROC_NR + 1]; fptr < &fproc[NR_PROCS]; fptr++){
task = -fptr->fp_task;
if (fptr->fp_suspended == SUSPENDED && task == XLOCK) {
revive( (int) (fptr - fproc), 0);
}
}
}