文件锁是保持文件同步的一种手段,当多个用户同时操作同一个文件时,文件锁可以保证数据不发生冲突。NFSv2和NFSv3依靠NLM协议实现文件锁,NFSv4本身实现了文件锁,不需要NLM协同工作了。NFS中的文件锁既可以加在客户端,也可以加在服务器端。如果客户端挂载NFS文件系统时使用了选项nolock,表示在客户端加锁。这种情况下可以保证同一个客户端的多个进程访问同一个文件的过程不发生冲突,但是不同客户端访问同一个文件时还可能发生冲突,因为文件锁加在了客户端,其他客户端不知道这个文件锁的存在。如果客户端挂载NFS文件系统时使用了选项lock,表示在服务器端加锁,这样所有的客户端都可以检查服务器端是否存在文件锁,因此所有客户端访问同一个文件时都不会发生冲突。客户端加锁时和其他文件系统的加锁过程没有什么区别,因此我们只讲解服务器端加锁的情况。
1.NFS中文件锁操作
NFSV4中,服务器端锁处理函数的入口点是nfs4_proc_lock(),这个函数处理了NFSv4中所有关于文件锁的操作,函数代码如下:
参数filp:这是一个文件对象指针,表示对哪个文件进行锁处理
参数cmd:这是文件锁操作的方法,取值为:F_GETLK、F_SETLK、F_SETLKW
参数request:这里包含了文件锁信息
static int
nfs4_proc_lock(struct file *filp, int cmd, struct file_lock *request)
{
struct nfs_open_context *ctx;
struct nfs4_state *state;
unsigned long timeout = NFS4_LOCK_MINTIMEOUT;
int status;
/* verify open state */
// ctx是打开文件时设置的数据结构,其中包含了nfs4_state指针.
ctx = nfs_file_open_context(filp); // return filp->private_data
state = ctx->state; // 取出nfs4_state结构
// 检查文件锁的起始位置和结束位置是否非法
if (request->fl_start < 0 || request->fl_end < 0)
return -EINVAL;
// 文件锁包含三种操作:F_GETLK、F_SETLK、F_SETLKW
if (IS_GETLK(cmd)) { // 查询文件锁的信息
if (state != NULL)
return nfs4_proc_getlk(state, F_GETLK, request);
return 0;
}
// 检查操作类型是否合法
if (!(IS_SETLK(cmd) || IS_SETLKW(cmd)))
return -EINVAL;
if (request->fl_type == F_UNLCK) { // 这是解锁操作
if (state != NULL)
return nfs4_proc_unlck(state, cmd, request); // 删除文件锁
return 0;
}
if (state == NULL)
return -ENOLCK;
/*
* Don't rely on the VFS having checked the file open mode,
* since it won't do this for flock() locks.
*/
// 检查是否有权限创建文件锁
switch (request->fl_type) {
case F_RDLCK: // 创建一个读锁
if (!(filp->f_mode & FMODE_READ)) // 只有读打开的情况下才能创建读锁
return -EBADF;
break;
case F_WRLCK: // 创建一个写锁
if (!(filp->f_mode & FMODE_WRITE)) // 只有写打开的情况下才能创建写锁
return -EBADF;
}
do {
status = nfs4_proc_setlk(state, cmd, request); // 创建一个文件锁
if ((status != -EAGAIN) || IS_SETLK(cmd))
break;
timeout = nfs4_set_lock_task_retry(timeout);
status = -ERESTARTSYS;
if (signalled())
break;
} while(status < 0);
return status;
}
Linux定义了三种文件锁操作方法:
F_GETLK:查询指定文件锁的信息,参数request中包含了待查找文件锁的信息
F_SETLK:设置文件锁,包括加锁和解锁两种操作,参数request中包含了锁信息。加锁过程中如果和其他的文件锁发生了冲突,则直接出错退出。
F_SETLKW:设置文件锁,包括加锁和解锁两种操作,参数request中包含了锁信息。加锁过程中如果和其他的文件锁发生了冲突,则一直等待直到加锁成功为止。
2.文件锁相关的请求
NFSv4中有四个与文件锁相关的请求,分别是:
LOCK:给文件加锁,nfs4_proc_setlk()会发起这个请求
LOCKT:查询文件锁的信息,nfs4_proc_getlk()会发起这个请求。
LOCKU:解锁,nfs4_proc_unlck()会发起这个请求。
RELEASE_LOCKOWNER:释放文件锁所有者,nfs4_proc_unlck()会发起这个请求。
2.1LOCK请求报文和应答报文结构
LOCK操作也分为几种情况,下面先说说最常见的情况:创建一个新的文件锁。这种情况下LOCK请求报文应该包含下列数据:
struct LOCK4args {
/* CURRENT_FH: file */
nfs_lock_type4 locktype;
bool reclaim;
offset4 offset;
length4 length;
locker4 locker;
};
locktype表示文件锁类型,取值如下:
READ_LT:创建一个读锁,如果与其他的文件锁发生了冲突,则马上退出。
WRITE_LT:创建一个写锁,如果与其他的文件锁发生了冲突,则马上退出。
READW_LT:创建一个读锁,如果与其他的文件锁发生了冲突,则等待,直到成功加锁。
WRITEW_LK:创建一个写锁,如果与其他的文件锁发生了冲突,则等待,直到成功加锁。
reclaim:这是锁状态回复标志位。如果服务器宕机重启了,这种情况下客户端需要恢复以前申请的文件锁,这时LOCK请求报文中的reclaim设置为1,其他情况下这个值为0。
offset:这是文件锁锁定的数据段在文件中的起始偏移量。
length:这是文件锁锁定的数据端的长度。
locker:这是标识本次LOCK操作的数据结构,如果是第一次加锁,这个数据结构如下:
struct open_to_lock_owner4 {
seqid4 open_seqid;
stateid4 open_stateid;
seqid4 lock_seqid;
lock_owner4 lock_owner;
};
open_seqid:加锁前需要先打开文件,因此需要先执行OPEN操作,这是OPEN操作中的seqid顺序号。
open_stateid:这是OPEN操作中返回的stateid。
lock_seqid:这是为LOCK操作准备的顺序号
lock_owner:相当于是LOCK操作的名称,包含下列信息:
struct lock_owner4 {
clientid4 clientid;
opaque owner<NFS4_OPAQUE_LIMIT>;
};
clientid:这是客户端的clientid。
owner:这是一个字符串,客户端随便定义,但是需要保证能够唯一确定文件锁的所有者。
如果加锁成功,LOCK应答报文中包含下列数据:
struct LOCK4resok {
stateid4 lock_stateid;
};
应答报文很简单,只包含了为这个文件锁生成的stateid。
如果与文件中其他的文件锁发生了冲突,加锁失败了,LOCK应答报文包含下列数据:
struct LOCK4denied {
offset4 offset;
length4 length;
nfs_lock_type4 locktype;
lock_owner4 owner;
};
这个数据结构中包含了与我们冲突的文件锁的信息。
offset:冲突的文件锁在文件中的偏移量。
length:冲突的文件锁的长度。
locktye:冲突的文件锁的长度。
owner:冲突的文件锁的名称。
2.2LOCKT请求报文和应答报文结构
LOCKT请求报文包含下列数据:
struct LOCKT4args {
/* CURRENT_FH: file */
nfs_lock_type4 locktype;
offset4 offset;
length4 length;
lock_owner4 owner;
};
locktype:文件锁类型,取值如下:READ_LT、WRITE_LK、READW_LT、WRITEW_LK。
offset:这是文件锁锁定的数据段在文件中的起始偏移量。
length:这是文件锁锁定的数据端的长度。
owner:相当于是LOCKT操作的名称。
如锁没有文件锁跟我们测试的文件锁发生冲突,LOCKT应答报文只返回错误码:NFS_OK。
如果文件中有文件锁跟我们测试的文件锁发生了冲突,则返回发生冲突的文件锁的信息,应答报文如下:
struct LOCK4denied {
offset4 offset;
length4 length;
nfs_lock_type4 locktype;
lock_owner4 owner;
};
2.3LOCKU请求报文和应答报文结构
LOCKU请求报文包含了用户请求删除的文件锁的信息,数据如下:
struct LOCKU4args {
/* CURRENT_FH: file */
nfs_lock_type4 locktype;
seqid4 seqid;
stateid4 stateid;
offset4 offset;
length4 length;
};
如果服务器正确删除了这个文件锁,则返回stateid。否则,什么都不返回。2.4RELEASE_LOCKOWNER请求报文和应答报文结构
RELEASE_LOCKOWNER请求报文包含下列数据:
struct RELEASE_LOCKOWNER4args {
lock_owner4 lock_owner;
};
RELEASE_LOCKOWNER应答报文只包含服务器返回的错误码。