热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

RPC状态机执行过程

RPC状态机执行过程可能了解这个状态过程对以后工作没用,但还是想了解一下具体执行过程,也算是对自己以前一个工作的交代。我们用的是SUNRPC,在内核中实现的RPC,代码在netsu

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
*/

int
rpc_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


推荐阅读
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Windows7 64位系统安装PLSQL Developer的步骤和注意事项
    本文介绍了在Windows7 64位系统上安装PLSQL Developer的步骤和注意事项。首先下载并安装PLSQL Developer,注意不要安装在默认目录下。然后下载Windows 32位的oracle instant client,并解压到指定路径。最后,按照自己的喜好对解压后的文件进行命名和压缩。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • 本文介绍了Codeforces Round #321 (Div. 2)比赛中的问题Kefa and Dishes,通过状压和spfa算法解决了这个问题。给定一个有向图,求在不超过m步的情况下,能获得的最大权值和。点不能重复走。文章详细介绍了问题的题意、解题思路和代码实现。 ... [详细]
  • Gitlab接入公司内部单点登录的安装和配置教程
    本文介绍了如何将公司内部的Gitlab系统接入单点登录服务,并提供了安装和配置的详细教程。通过使用oauth2协议,将原有的各子系统的独立登录统一迁移至单点登录。文章包括Gitlab的安装环境、版本号、编辑配置文件的步骤,并解决了在迁移过程中可能遇到的问题。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
author-avatar
雷诺gg
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有