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的内核代码。
rpc状态转换图
看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

0 0
原创粉丝点击