RPC状态机执行过程
来源:互联网 发布:淘宝卖家费用 编辑:程序博客网 时间:2024/05/18 03:49
RPC状态机执行过程
可能了解这个状态过程对以后工作没用,但还是想了解一下具体执行过程,也算是对自己以前一个工作的交代。
我们用的是SUNRPC,在内核中实现的RPC,代码在net/sunrpc/。
同步RPC和异步RPC
RPC调用有同步和异步两种。
/** * rpc_call_sync - Perform a synchronous RPC call * @clnt: pointer to RPC client * @msg: RPC call parameters * @flags: RPC call flags */int rpc_call_sync(struct rpc_clnt *clnt, const struct rpc_message *msg, int flags);
/** * rpc_call_async - Perform an asynchronous RPC call * @clnt: pointer to RPC client * @msg: RPC call parameters * @flags: RPC call flags * @tk_ops: RPC call ops * @data: user call data */intrpc_call_async(struct rpc_clnt *clnt, const struct rpc_message *msg, int flags, const struct rpc_call_ops *tk_ops, void *data);
执行时都会调用rpc_run_task(&task_setup_data);
rpc_run_task
调用rpc_new_task分配一个新的RPC task,再调用rpc_execute执行RPC 任务,执行结果rpc_task返回。
rpc_new_task
/* * Create a new task for the specified client. */struct rpc_task *rpc_new_task(const struct rpc_task_setup *setup_data){ struct rpc_task *task = setup_data->task; unsigned short flags = 0; if (task == NULL) { task = rpc_alloc_task(); if (task == NULL) goto out; flags = RPC_TASK_DYNAMIC; } rpc_init_task(task, setup_data); task->tk_flags |= flags; dprintk("RPC: allocated task %p\n", task);out: return task;}
如果task是空,则调用rpc_alloc_task分配一个task,调用rpc_init_task用struct rpc_task_setup初始化rpc_task,设置task指向的内存空间为全0,会判断task->tk_ops->rpc_call_prepare != NULL,其实是判断task_setup_data->callback_ops回调函数是否有rpc_call_prepare,
const struct rpc_call_ops nfs4_entrycommit_ops = { .rpc_call_prepare = nfs4_entrycommit_prepare, .rpc_call_done = nfs4_entrycommit_done, .rpc_release = nfs4_entrycommit_release,};
前面在初始化struct rpc_task_setup task_setup_data变量时,有过初始化,rpc_call_prepare非空,task->tk_action = rpc_prepare_task,
/* * Helper to call task->tk_ops->rpc_call_prepare */void rpc_prepare_task(struct rpc_task *task){ task->tk_ops->rpc_call_prepare(task, task->tk_calldata);}
rpc_prepare_task通过函数指针调用函数nfs4_entrycommit_prepare执行,它会调用rpc_call_start(task),rpc_call_start实际执行task->tk_action = call_start,task->tk_action是函数指针,指向函数call_start,函数名是函数地址,rpc执行过程是状态机,但实际上没有改变状态,是通过回调函数实现的,task->tk_action指向不同的函数,下次就执行不同的函数,另外在struct rpc_task中还有void (tk_callback)(struct rpc_task ),我理解的是异步RPC时回调时使用的,稍后会有验证。
struct rpc_task {/* * RPC call state */ struct rpc_message tk_msg; /* RPC call info */ /* * callback to be executed after waking up * action next procedure for async tasks * tk_ops caller callbacks */ void (*tk_callback)(struct rpc_task *); void (*tk_action)(struct rpc_task *);};
rpc_init_task结束后,rpc_new_task也就结束,至此初始化的工作已经完成。
_rpc_execute
rpc_new_task执行结束,开始执行rpc_execute。
rpc_execute调用__rpc_execute开始执行,函数__rpc_execute的注释是这样的,This is the RPC `scheduler’ (or rather, the finite state machine),这才是RPC中传说的有限状态机。
for不停循环,首先判断是否有pending callback,等待回调的函数,void (save_callback)(struct rpc_task ),这是在使用前需要先声明;再判断,这才是有限状态机的下一步。
if (!RPC_IS_QUEUED(task)) { if (task->tk_action == NULL) break; task->tk_action(task); }
#define RPC_IS_QUEUED(t) test_bit(RPC_TASK_QUEUED, &(t)->tk_runstate)
RPC_IS_QUEUED是判断task的状态,rpc_execute在调用_rpc_execute前会调用rpc_set_active(task),设置rpc_task的状态,设置tk_runstate为RPC_TASK_ACTIVE,一共有3个状态。
#define RPC_TASK_RUNNING 0#define RPC_TASK_QUEUED 1#define RPC_TASK_ACTIVE 2
task->tk_action(task),函数指针开始调用call_start,call_start执行会修改task->tk_action,task->tk_action = call_reserve,call_start执行完后,再进入for循环,继续循环,只要task->tk_action非空,就会继续执行,如果是空,break跳出循环。
此处发现一写的很好的博客,参考会给出。
执行过程是:
->call_start
–>call_reserver:调用xprt_reserve,分配一个RPC 请求槽,就是struct rpc_xprt;
—>call_reserveresult
—->call_allocate
—–>call_bind
——>call_connect: 向server端发送前,先连接到server端
还是看图吧,跟着图走一遍流程就清楚了,pnfs的内核代码。
看call_transmit,传输RPC请求,并等待返回。
调用rpc_xdr_encode(task),进行编码,RPC报文分为两部分:RPC报文头和净荷信息。RPC报文头通过rpc_encode_header()组装,净荷信息通过nfs3_xdr_enc_remove3args()组装。它再调用task->tk_msg.rpc_proc->p_encode,调用具体的编码函数进行编码,task->tk_msg是结构体struct rpc_message,task->tk_msg.rpc_proc是在初始化结构体rpc_message时初始化了。
struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_ENTRYCOMMIT],};
结构体数组的一个元素,看nfs4_procedures[]这个结构体数组又发现,proc是宏定义,p_encode被初始化为nfs4_xdr_enc_entrycommit,查看该函数的代码,将向server端提交的信息编码。
函数rpc_xdr_encode调用之前初始化的函数nfs4_xdr_enc_entrycommit,其实我是不太能理解这个调用,我理解的函数调用,即使是函数指针调用,也是需要传递参数的,以下的函数调用连传递参数都没有。
SX了,这仅仅是赋值,encode还是指针类型,真正调用时函数指针且有参数传递,在rpcauth_wrap_req中。
encode = task->tk_msg.rpc_proc->p_encode;task->tk_status = rpcauth_wrap_req(task, encode, req, p, task->tk_msg.rpc_argp);
encode是函数指针传递,在函数rpcauth_wrap_req,调用 encode(rqstp, data, obj),执行,这个应该涉及函数指针的多种调用方式了。
函数call_transmit,编码结束后,调用xprt_transmit(task),该函数的注释是说发送一个RPC请求给一个端口。
参考[2]博客有如下说:
在实际传输之前要用XDR规范将数据进行编码,这是rpc的约定。看一下xprt_transmit就会发现,底层的rpc使用socket将数据传给服务器的,当然也可以用别的机制,比如任何底层链路协议,只要能进行网络传输的就可以。
我没看出来是socket传输的啊。
另外,请看以下call_status,我的理解是这里状态转变的地方,根据不同的状态采用不同的操作。
client端将RPC请求发送给server端,server端收到请求并处理,将处理结果返回给client端,client端收到后,还会执行到call_status,但是中间是如何执行到call_status的,暂不清楚,call_status会判断task的status,修改task->tk_action=call_decode,再回到_rpc_executefor循环,执行call_decode,调用rpcauth_unwrap_resp,它再调用具体的解码函数nfs4_xdr_dec_entrycommit进行解码。
看到这里,struct rpc_call_ops nfs4_entrycommit_ops,有3个函数,只有1个函数执行,那后两个函数呢?
在call_transmit,执行成功后,task->tk_action = rpc_exit_task,修改task->tk_action,执行rpc_exit_task,通过函数指针task->tk_ops->rpc_call_done调用nfs4_entrycommit_done,entrycommti是完成了,现在要释放占用的资源。
这个时候task->tk_action为空,跳出__rpc_execute的for不停循环,执行rpc_release_task,[rpc_release_task–>rpc_put_task–>rpc_free_task–>rpc_release_calldata],通过函数指针执行前面注册的release函数,nfs4_entrycommit_release。
一般会有3个函数
rpc_call_prepare():发起RPC请求报文前执行的函数,修改task->tk_action的指向;
rpc_call_done():处理完RPC应答报文后执行的函数;
rpc_release()是释放资源的函数,比如释放RPC请求过程中申请的内存,当RPC执行完毕或者失败时都会调用这个函数。
参考:
[1] http://m.blog.csdn.net/blog/ta_nk/7172927
[2] http://blog.csdn.net/dog250/article/details/5303423
- RPC状态机执行过程
- 5.6.7 执行指令状态机
- Hadoop详解(二)——HDFS的命令,执行过程,Java接口,原理详解。RPC机制
- usb 枚举过程中的状态机
- 远程过程调用(RPC)
- Rpc远程过程调用
- RPC远程过程调用
- RPC请求处理过程
- 远程过程调用RPC
- RPC远程过程调用
- RPC 远程过程调用
- RPC远程过程调用
- 远程过程调用(RPC)
- 远程过程调用 (RPC)
- RPC远程过程调用
- 远程过程调用RPC
- RPC请求处理过程
- RPC远程过程调用
- MQX驱动
- Mongodb开启与关闭
- EL表达式 (详解)
- 剑指off-递归求1到n的和
- 颜色匹配
- RPC状态机执行过程
- oracle官方文档_查看初始化参数(举例)
- cocos2dx之物理引擎
- ASP.NET AJAX
- duilib进阶教程 -- 实现List排序
- Android Studio 快捷键大全
- android线程池
- 定位
- android studio导入gitbub的library的步骤