NFS Client in Linux Kernel - Loc
1. System Call
- flock
- fcntl
2. 内核中的Lock
分为两种lock,但在内核中统一成file_lock
数据结构
- FL_POSIX,来自fnctl系统调用,具体参见
flock64_to_posix_lock()
- FL_FLOCK,来自flock系统调用,具体参见
flock_make_lock()
2.1 NFS的特殊处理
以下两种函数,最终都统一调用nfs4_proc_lock
- nfs_lock //for posix
- nfs_flock //for flock
对于posix lock入口函数是nfs4_proc_lock()
,分别调用:
- nfs4_proc_getlk():用于查询 ,对应LOCKT NFS命令
- nfs4_proc_unlck():用于解锁,对应LOCKU NFS命令
- nfs4_proc_setlk():用于加锁,对应LOCK NFS命令
3. NFS协议中的Lock Owner
对于普通的文件系统来说,flock的owner是内核下的open fd,posix lock的owner是current->files。如果Owner一样,说明lock请求来自同一个实体,判断lock是否冲突时候直接跳过。对于NFS来说,协议规定由这个数据结构表示owner(其实open的owner也同样由这个数据结构表示):
摘自nfs4.1协议
struct state_owner4 {
clientid4 clientid;
opaque owner<NFS4_OPAQUE_LIMIT>;
};
- clientid,Server分配的,唯一标识一个和server建立session的client
- 字符串+字符串的长度,唯一标识client中的一个进程
- client标识+进程标识,唯一确定一个lock的owner。
3.1 Linux Kernel中的对Lock Owner的设置
Lock owner大小20字节:
- 8字节的固定字符,"lock id:"
- 4字节的s_dev,表示是本地哪个文件系统。
- 8字节的id。由ida_simple_get函数创建,在本地文件系统中唯一。对于flock,且open fd相同,会复用同一个。对于posix lock,且进程相同,也会复用同一个。
- 以上三点,保证了在一个client内Lock owner是唯一的。
- 再加上clientid,可以保证所有和Server建立session连接的client来自的lock owner是唯一确定。
static void encode_lockowner(struct xdr_stream *xdr, const struct nfs_lowner *lowner)
{
p = reserve_space(xdr, 32);
p = xdr_encode_hyper(p, lowner->clientid);
*p++ = cpu_to_be32(20); //lock owner大小,20
p = xdr_encode_opaque_fixed(p, "lock id:", 8); //8字节
*p++ = cpu_to_be32(lowner->s_dev);//4字节
xdr_encode_hyper(p, lowner->id);//8字节,不同进程会会分配到不同id
}
4. Linux Kernel对NFS Lock实体的描述
Linux Kernel对lock实体的表示,用的是struct nfs4_lock_state
。
struct nfs4_lock_state {
struct list_head ls_locks; //将同一个open file的lock插入到state->lock_states
struct nfs4_state * ls_state; //指向open state
unsigned long ls_flags;
struct nfs_seqid_counter ls_seqid;//里面重要的owner_id,由ida_simple_get函数创建
nfs4_stateid ls_stateid; //server端返回的信息,Server端对这个实体的key
atomic_t ls_count;
fl_owner_t ls_owner; //对于同一个文件的lock,如果此项相同会复用以前
};
4.1 nfs4_lock_state的构造
- 输入 state : 代表一个打开的文件
- 输入 owner : 对于flock是open fd,对于posix是current->files
- 输出 : 返回一个
nfs4_lock_state
结构
struct nfs4_lock_state *nfs4_get_lock_state(struct nfs4_state *state, fl_owner_t owner);
- 这个打开的文件里,可以获得一个list,包含所有对这个文件lock过的owner。如果这个owner已经在列表里,则直接返回。
- 如果没找到,从内存里获取一个。最重要是为这个结构,分配一个
lsp->ls_seqid.owner_id
,它是通过ida_simple_get分配的。 - nfs4_proc_getlk(),nfs4_proc_unlck(),nfs4_proc_setlk()都会调用nfs4_set_lock_state(),也就是为它们设置了owner。
nfs4_get_lock_state()就是根据open file state + owner唯一产生一个nfs4_lock_state
,它其实存在open state的一个list里,保存所有的lock state。
nfs4_set_lock_state()将nfs4_lock_state
存在了fl->fl_u.nfs4_fl.owner
,其中fl是file_lock
nfs4_proc_getlk(),nfs4_proc_unlck(),nfs4_proc_setlk()都会调用nfs4_set_lock_state(),其中nfs4_proc_setlk()会将server返回的lock stateid存在nfs4_lock_state
。而nfs4_proc_getlk()和nfs4_proc_unlck()会取出nfs4_lock_state
,从而得到lock stateid,发送给server。
4.2 对 nfs4_lock_state的修改
NFS lock请求结束后,进入nfs4_lock_done
,会把返回的stateid
更新到nfs4_lock_state里。
5. NFS协议中Lock的使用
对于一个实体第一次使用Lock,client需要提供: (代码参看encode_lock
)
- open_seqid
- open_stateid
- lock_seqid
- lock_owner
Server端会返回lock stateid,以后再操作时候,就直接提供lock stateid就可以了 - lock_stateid
- lock_seqid
6. Blocking lock vs Non-blocking lock
fcntl
系统调用会调到nfs4_proc_lock
这里
- 调用nfs4_proc_setlk()发送LOCK RPC给server
- 如果返回值是
EAGAIN
或者是non-blocking的lock(用IS_SETLK判断),则直接返回 - 调用nfs4_set_lock_task_retry等待一个timeout时间。第一次timeout是1s,第二次是2s,最大值是30s
- 跳到步骤1
6. 为什么两次flock不会引起第二次NFS请求
例如下面的命令,执行两次。第一次会导致发生NFS请求,但第二次不会导致发生NFS请求。为什么?
flock -nx /mnt/vfs/1.txt /root/test.sh
- 第一次调用时,发送完NFS请求,会调用nfs4_lock_done,经过一些列调用会走到flock_lock_inode。注意此时equest->fl_flags是带着FL_ACCESS标记的,因此会将file_lock复制一份,并插入队列。
- 第二次调用时,在发送NFS请求前,会再次调用flock_lock_inode。此时equest->fl_flags是不带FL_ACCESS,如果发现冲突,则直接返回-EAGAIN,而不会调用NFS请求。
7. call stack
fcntl64
fcntl_setlk64
flock64_to_posix_lock //fl->fl_owner = current->files
do_lock_file_wait
vfs_lock_file
nfs_lock // f_op->lock
do_setlk
nfs4_proc_lock // NFS_PROTO(inode)->lock
flock
flock_make_lock //fl->fl_owner = filp
nfs_flock // f_op->flock
do_setlk
nfs4_proc_lock // NFS_PROTO(inode)->lock
fcntl64和flock最终都调用nfs4_proc_lock
nfs4_proc_lock
nfs4_proc_setlk
_nfs4_proc_setlk
nfs4_set_lock_state
nfs4_get_lock_state
__nfs4_find_lock_state
nfs4_alloc_lock_state //set lsp->ls_seqid.owner_id
do_vfs_lock
locks_lock_inode_wait
flock_lock_inode_wait
flock_lock_inode
_nfs4_do_setlk
=> nfs4_lock_done
do_vfs_lock
nfs4_alloc_lockdata //p->arg.lock_owner.id = lsp->ls_seqid.owner_id;
8. 容易误解的地方
- Client打开一个文件,就会产生一个Open Owner,这个Open Owner里会有一个Open State。
- Client Lock一个文件,就会产生有一个Lock Owner(它一定是被打开的,所以一定有一个Open Owner和一个Open state), 对这个文件不停的修改range lock的范围,总对应一个lock state,而不是多个!! 所以一般情况下一个Lock Owner下,只有一个Lock State。对于delegation和layout,也被认为是lock state。这些所有的lock state存在lock owner的一个list里。对于一般情况,这个list只有一个元素(就是那个lock state)。