这篇文章讲讲服务器端RPC报文的处理流程。服务器端RPC报文的处理函数是svc_process,这个函数位于net/sunrpc/svc.c中。这个函数需要一个svc_rqst结构的指针作为参数,svc_rqst是与RPC请求相关的一个数据结构,这里包含了接收到的RPC消息,RPC消息的解析结果也放在这个数据结构中,RPC消息的处理结果也放在这个消息中了。这个数据结构的定义在include/linux/sunrpc/svc.h。由于我主要想讲解NFS,所以很多RPC的知识就略过不讲了。
/*
* Process the RPC request.
*/
int
svc_process(struct svc_rqst *rqstp)
{
// 这是一块缓存,服务器从网卡中接收到RPC消息后存放在这里(已经去掉了IP头,TCP或UDP报文头)
// argv指向了缓存的起始地址,从这里开始就是RPC报文头了。
struct kvec *argv = &rqstp->rq_arg.head[0];
// 这是一块缓存,这块缓存用来存放RPC应答消息,现在还没有分配内存。
struct kvec *resv = &rqstp->rq_res.head[0];
struct svc_serv *serv = rqstp->rq_server;
u32 dir;
/*
* Setup response xdr_buf.
* Initially it has just one page
*/
rqstp->rq_resused = 1;
// 为RPC应答消息分配内存
resv->iov_base = page_address(rqstp->rq_respages[0]);
resv->iov_len = 0;
rqstp->rq_res.pages = rqstp->rq_respages + 1;
rqstp->rq_res.len = 0;
rqstp->rq_res.page_base = 0;
rqstp->rq_res.page_len = 0;
rqstp->rq_res.buflen = PAGE_SIZE;
rqstp->rq_res.tail[0].iov_base = NULL;
rqstp->rq_res.tail[0].iov_len = 0;
rqstp->rq_xid = svc_getu32(argv); // 这里开始解析RPC报文了,按照RPC报文格式,这是RPC消息的XID。
dir = svc_getnl(argv); // 这个函数解析出了RPC保本中的第二个字段(Message Type)
if (dir != 0) { // RPC协议规定,RPC应答消息中Message Type字段必须为0.如果不是0就不处理了。
/* direction != CALL */
svc_printk(rqstp, "bad direction %d, dropping request\n", dir);
serv->sv_stats->rpcbadfmt++;
svc_drop(rqstp); // 丢弃这个RPC报文。
return 0;
}
// 现在可以确定这是一个RPC请求报文了,调用svc_process_common()进行处理,这是RPC请求的主处理函数,并且会填充RPC应答报文。
// 返回值1表示处理过程正常,已经正常填充了RPC应答消息,可以发送给客户端了。
// 返回值0表示RPC请求报文格式异常,直接丢弃这个报文。
/* Returns 1 for send, 0 for drop */
if (svc_process_common(rqstp, argv, resv))
return svc_send(rqstp); // 这是发送RPC应答消息的报文,不会深入分析这个函数了。
else {
svc_drop(rqstp); // 丢弃RPC报文。
return 0;
}
}
svc_process_common是主要的处理函数,这个函数的定义如下:
static int svc_process_common(struct svc_rqst *rqstp, struct kvec *argv, struct kvec *resv)参数rqstp 表示一个RPC请求。
参数argv是一块缓存,这块缓存中保存了接收到的RPC请求报文。
参数resv是一块缓存,这块缓存用来保存组装后的RPC应答报文。
这个函数的处理流程就比较复杂了,基本上包含下面五个处理步骤:
(1)组装RPC报文头基本信息
(2)解析RPC服务信息
(3)对用户身份进行验证