Linux rpc_create_client RPC客户端与xprt传输绑定

Linux rpc_create_client RPC客户端与xprt传输绑定

Linux rpc_create_client RPC客户端与xprt传输绑定

rpc_create_client是SUNRPC层创建RPC客户端实例的核心函数,位于net/sunrpc/clnt.c。它完成struct rpc_clnt的分配与初始化,并将客户端绑定到一个已创建的传输通道(xprt)上。NFS客户端在挂载时通过rpc_create调用间接调用此函数。

函数原型:

```c
struct rpc_clnt *rpc_create(struct rpc_create_args *args)
{
struct rpc_xprt *xprt;
struct rpc_clnt *clnt = ERR_PTR(-EIO);

if (args->protocol == XPRT_TRANSPORT_TCP)
xprt = xprt_create_transport(XPRT_TRANSPORT_TCP, args->address,
args->addrsize, args->servername,
args->xprtsec);
else if (args->protocol == XPRT_TRANSPORT_UDP)
xprt = xprt_create_transport(XPRT_TRANSPORT_UDP, args->address,
args->addrsize, args->servername,
args->xprtsec);
else
return ERR_PTR(-EPROTONOSUPPORT);

if (IS_ERR(xprt))
return ERR_CAST(xprt);

clnt = rpc_create_client(xprt, args->program, args->version,
args->prognumber, args->authflavor);

...
return clnt;
}
```

rpc_create_client的核心实现:

```c
static struct rpc_clnt *rpc_create_client(struct rpc_xprt *xprt,
const struct rpc_program *program,
u32 version, u32 prognumber,
rpc_authflavor_t authflavor)
{
struct rpc_clnt *clnt = NULL;
const struct rpc_version *vers;
int err;

vers = rpc_verify_header(program, version);
if (IS_ERR(vers))
goto out_err;

clnt = kzalloc(sizeof(*clnt), GFP_KERNEL);
if (!clnt)
goto out_err;

clnt->cl_program = program;
clnt->cl_vers = vers;
clnt->cl_prog = prognumber;
clnt->cl_xprt = xprt;
clnt->cl_autobind = 1;
clnt->cl_timeout = xprt->timeout;
clnt->cl_metrics = alloc_percpu(struct rpc_clnt_metrics);
INIT_LIST_HEAD(&clnt->cl_tasks);
spin_lock_init(&clnt->cl_lock);
refcount_set(&clnt->cl_count, 1);
rpc_init_rtt(&clnt->cl_rtt_default, HZ);

err = rpc_register_client(clnt);
if (err)
goto out_err;

if (authflavor != RPC_AUTH_NULL) {
clnt->cl_auth = rpcauth_create(authflavor, clnt);
if (IS_ERR(clnt->cl_auth))
goto out_err;
}

return clnt;

out_err:
if (!IS_ERR_OR_NULL(clnt))
rpc_destroy_client(clnt);
return ERR_PTR(err);
}
```

xprt_create_transport是传输层创建函数,定义在net/sunrpc/xprt.c:

```c
struct rpc_xprt *xprt_create_transport(int xprt_type,
struct sockaddr *addr,
size_t addrlen,
const char *servername,
struct xprtsec_parms *xprtsec)
{
struct rpc_xprt *xprt;
const struct rpc_xprt_ops *ops;
int err;

ops = rpc_xprt_find_transport_owner(xprt_type);
if (!ops)
return ERR_PTR(-EPROTONOSUPPORT);

xprt = ops->alloc_xprt(xprt_sec);
if (!xprt)
return ERR_PTR(-ENOMEM);

xprt->ops = ops;
xprt->addr = *addr;
xprt->addrlen = addrlen;
xprt->servername = kstrdup(servername, GFP_KERNEL);
xprt->max_reqs = RPC_MAX_SLOT_TABLE;
xprt->min_reqs = 0;
xprt->num_reqs = 0;
xprt->cwnd = RPC_INITIAL_CWND;
xprt->timeout = NULL;
xprt->bind_timeout = RPC_BIND_TIMEOUT_DEF;
xprt->reestablish_timeout = RPC_REESTABLISH_TIMEOUT_DEF;
xprt->idle_timeout = RPC_MAX_IDLE_TIMEOUT_DEF;
xprt->cong = 0;

err = xprt->ops->xprt_setup(xprt, addr, addrlen);
if (err)
goto out_destroy;

return xprt;

out_destroy:
xprt->ops->destroy_xprt(xprt);
return ERR_PTR(err);
}
```

对于TCP传输,alloc_xprt指向xs_tcp_alloc_xprt:

```c
static struct rpc_xprt *xs_tcp_alloc_xprt(struct xprt_sec *xprtsec)
{
struct rpc_xprt *xprt = kzalloc(sizeof(*xprt), GFP_KERNEL);
if (!xprt)
return NULL;

xprt->ops = &xs_tcp_ops;
xprt->xprtsec = *xprtsec;
return xprt;
}
```

xprt_setup对应xs_tcp_setup_xprt,它创建内核socket并设置连接参数:

```c
static int xs_tcp_setup_xprt(struct rpc_xprt *xprt,
struct sockaddr *addr, size_t addrlen)
{
struct socket *sock;
int err, type = SOCK_STREAM;

err = __sock_create(xprt->xprt_net, addr->sa_family, type, IPPROTO_TCP,
&sock, 1);
if (err < 0)
return err;

sock->sk->sk_allocation = GFP_NOIO;
sock->sk->sk_use_task_frag = false;
xprt->inet = sock->sk;

err = kernel_connect(sock, addr, addrlen, O_NONBLOCK);
if (err < 0 && err != -EINPROGRESS)
goto out;

xprt->sock = sock;
xs_set_memalloc(xprt);
return 0;

out:
sock_release(sock);
return err;
}
```

客户端创建完成后,xprt与clnt之间的绑定通过cl_xprt字段维系。当RPC任务通过rpc_run_task启动时,任务通过task->tk_client->cl_xprt找到传输通道,调用xprt->ops->send_request发送RPC消息。接收路径则通过xprt->recv函数(对于TCP为xs_tcp_data_ready)完成,数据到达后调用xprt_complete_rqst完成请求-响应匹配。

绑定后的auth创建过程通过rpcauth_create初始化认证机制,常用的有RPC_AUTH_UNIX(AUTH_SYS)和RPC_AUTH_GSS(Kerberos)。认证句柄保存在cl_auth中,在每次RPC调用时通过rpcauth_refreshcred更新凭证。