Linux nlmclnt_lock NFS锁管理与rpc_call_sync
2026/6/14 12:25:12 网站建设 项目流程

Linux nlmclnt_lock NFS锁管理与rpc_call_sync

nlmclnt_lock是NFS客户端网络锁管理器(NLM)协议实现的锁请求入口函数,位于fs/lockd/clntproc.c。它负责将用户态的flock/posix lock操作转化为NLM协议的RPC调用。当VFS层调用flock或fcntl系统调用,经过do_lock_fcntl/do_flock等路径后,最终进入nlmclnt_lock。

函数核心定义如下:

```c
int nlmclnt_lock(struct nlm_host *host, struct file_lock *fl,
struct nlm_lockowner *owner, struct nlm_wait *block)
{
struct nlm_rqst reqst, *call = &reqst;
struct nlm_res *resp;
struct nlm_wait nlm_block;
int status = -ENOLCK;

if (!host->h_server)
return -ENOLCK;

resp = nlm_alloc_resp(call, GFP_KERNEL);
if (!resp)
return -ENOMEM;

nlmclnt_setlockargs(call, fl);
call->a_host = host;
call->a_owner = owner;

if (fl->fl_flags & FL_SLEEP) {
/* 初始化阻塞等待结构 */
nlm_block.b_status = 0;
nlm_block.b_wait = __WAIT_BIT_KEY_INITIALIZER(nlm_block.b_status);
block = &nlm_block;
}

status = nlmclnt_lock_async(call, fl, block, &nlm_lock_ops);
...
}
```

nlmclnt_setlockargs负责填充NLM请求参数,将内核file_lock转换为NLM协议参数:

```c
static void nlmclnt_setlockargs(struct nlm_rqst *req, struct file_lock *fl)
{
struct nlm_args *argp = &req->a_args;
struct nlm_lock *lock = &argp->lock;
struct inode *inode = file_inode(fl->fl_file);

lock->fh = NFS_FH(inode);
lock->oh.data = req->a_owner->pid;
lock->oh.len = sizeof(req->a_owner->pid);
lock->svid = fl->fl_pid;
lock->fl.fl_start = fl->fl_start;
lock->fl.fl_end = fl->fl_end;
lock->fl.fl_type = fl->fl_type;
argp->block = fl->fl_type != F_UNLCK;
argp->exclusive = (fl->fl_flags & FL_EXCLUSIVE) != 0;
argp->reclaim = 0;
argp->state = nsm_local_state;
}
```

nlmclnt_lock_async构建RPC消息并通过rpc_call_sync发起同步调用:

```c
static int nlmclnt_lock_async(struct nlm_rqst *req, struct file_lock *fl,
struct nlm_wait *block,
const struct rpc_call_ops *ops)
{
struct rpc_message msg = {
.rpc_proc = &nlm_procedures[4], /* NLMPROC_LOCK */
.rpc_argp = &req->a_args,
.rpc_resp = &req->a_res,
};
struct rpc_task_setup task_setup = {
.rpc_client = req->a_host->h_rpcclnt,
.rpc_message = &msg,
.callback_ops = ops,
.flags = RPC_TASK_ASYNC,
};
struct rpc_task *task;
int status;

task = rpc_run_task(&task_setup);
if (IS_ERR(task))
return PTR_ERR(task);

if (fl->fl_flags & FL_SLEEP) {
/* 在锁被阻塞时,通过nlm_block等待队列处理grants */
status = nlm_wait_task(block, task);
} else {
status = rpc_wait_for_completion_task(task);
}

if (status == 0)
status = req->a_res.status;

switch (status) {
case NLM_LCK_DENIED_GRACE_PERIOD:
/* 在宽限期被拒绝,自动重试 */
schedule_timeout_killable(NLM_CLNT_DELAY);
status = -EAGAIN;
break;
case NLM_LCK_DENIED_NOLOCKS:
status = -ENOLCK;
break;
}

rpc_put_task(task);
return nlm_stat_to_errno(status);
}
```

rpc_call_sync是RPC层的同步调用封装,它创建一个RPC任务并直接等待完成:

```c
int rpc_call_sync(struct rpc_clnt *clnt, struct rpc_message *msg, int flags)
{
struct rpc_task *task;
struct rpc_task_setup task_setup = {
.rpc_client = clnt,
.rpc_message = msg,
.callback_ops = &rpc_default_ops,
.flags = flags,
};
int ret;

task = rpc_run_task(&task_setup);
if (IS_ERR(task))
return PTR_ERR(task);

ret = rpc_wait_for_completion_task(task);
if (ret == 0)
ret = task->tk_status;

rpc_put_task(task);
return ret;
}
```

在NLM锁场景中,rpc_call_sync的关键在于处理锁的重试。当NLM服务器返回NLM_LCK_DENIED_GRACE_PERIOD时,客户端必须在宽限期结束前不断重试。nlmclnt_lock通过循环调用rpc_call_sync完成这个重试逻辑:

```c
int nlmclnt_lock(struct nlm_host *host, struct file_lock *fl,
struct nlm_lockowner *owner, struct nlm_wait *block)
{
int status;

for (;;) {
status = nlmclnt_lock_async(call, fl, block, &nlm_lock_ops);
if (status != -EAGAIN)
break;
/* EAGAIN表示在宽限期,等待后重试 */
if (signalled())
break;
if (!(fl->fl_flags & FL_SLEEP))
break;
}

if (status == 0)
nfs_file_set_open(fl, NFS_LOCK_OPEN);
return status;
}
```

RPC任务在rpc_execute中启动,经历了bind、connect、send、call_allocate、call_reserve、call_bind、call_connect、call_transmit、call_status等状态机阶段。每个阶段由task->tk_action函数指针驱动。对于NLM协议,RPC传输使用UDP或TCP协议,在rpc_create_client时确定传输类型。

nlmproc.c中的nlm_svc_lock_handler是服务端对应的锁处理函数,接收NLM_LOCK请求后在服务端进行POSIX文件锁操作。客户端通过Cookie和Status字段匹配请求和响应,Cookie由nlmclnt_setlockargs中的随机数生成,保证RPC幂等性检查。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询