1 概述
图1 分层的API调用
通常,如图1,一个复杂的系统都是分层的:
- 每个层通过API调用下一层提供的服务;
- l同时通过API被调用为上一层提供服务;
- 同一层模块间也相互通过被调用向对方提供服务;
- 模块的功能封装在模块内部,对外只有调用或被调用的API接口。
调用,是软硬件系统设计的基础。通过不同层次的模块间的调用,把各自不同的简单的模块,连接成一个庞大而复杂的整体系统。
2 软件调用
2.1 程序进程内的调用:函数调用
在高级语言编程里,如果我们把一个函数当做一个“模块”的话,那么从一个“模块”调用另一个“模块”就是我们通常非常熟悉的函数调用。
在x86的计算机系统中,一切的函数调用都要将不同的数据、地址压入或者弹出栈。我们直接通过实例来看函数是如何调用的。我们先来看看此时的栈是什么样的:
图2 被调用者栈帧的生成
此时调用者做了两件事情:
- 将被调用函数的参数按照从右到左的顺序压入栈中;
- 将返回地址压入栈中。
被调用者也做了两件事情:
- 将调用者的%ebp 压入栈,此时 %esp 指向它。
- 第二,将 %esp 的值赋给 %ebp, %ebp 就有了新的值,它也指向存放老 %ebp 的栈空间。
这时,它成了是函数 MyFunction() 栈帧的栈底。这样,我们就保存了“调用者”函数的 %ebp,并且建立了一个新的栈帧。
下面的操作就好理解了。在 %ebp 更新后,我们先分配一块0x12字节的空间用于存放本地变量,这步一般都是用 sub 或者 mov 指令实现。在这里使用的是 movl。通过使用 mov 配合 -4(%ebp), -8(%ebp) 和 -12(%ebp) 我们便可以给 a, b 和 c 赋值了。
图3 本地变量赋值后的栈帧
接下来我们来看看函数是如何返回的。从下面这个例子我们可以看出,和调用函数时正好相反。当函数完成自己的任务后,它会将 %esp 移到 %ebp 处,然后再弹出旧的 %ebp 的值到 %ebp。这样,%ebp 就恢复到了函数调用前的状态了。
我们注意到最后有一个 ret 指令,它首先将数据(返回地址)弹出栈并保存到 %eip 中,然后处理器根据这个地址无条件地跳到相应位置获取新的指令。
图4 被调用者返回后的栈帧
函数的调用其实不难,只要搞懂了如何保存以及还原 %ebp 和 %esp,就能明白函数是如何通过栈帧进行调用和返回的了。
参考文献:
https://www.cnblogs.com/sddai/p/9762968.html
2.2 程序进程间的调用:IPC
一般来说,linux下的进程包含以下几个关键要素:
- 有一段可执行程序;
- 有专用的系统堆栈空间;
- 内核中有它的控制块(进程控制块),描述进程所占用的资源,这样,进程才能接受内核的调度;
- 具有独立的存储空间。
在软件层面,进程和线程有时候并不完全区分,而往往根据上下文理解其含义。而在硬件层面,进程和线程最大的区别在于存储空间,每个进程都有一个独立的存储空间,而同一个进程中线程,大家共享的是同一个存储空间。
进程间通信的目的:
- 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间。
- 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 资源共享:多个进程之间共享同样的资源,为了作到这一点,需要内核提供锁和同步机制。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
Linux操作系统下进程间通信的主要方式:
- 管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
- 命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
- 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction。
- 消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
- 内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
- 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
- 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
参考文献:
https://oxnz.github.io/2014/03/31/linux-IPC/
https://www.jianshu.com/p/758c7e0df40d
2.3 远程程序调用:RPC
RPC(Remote Procedure Call):远程过程调用,它是一种通过网络从远程计算机程序上请求服务。目前流行的开源 RPC 框架还是比较多的,有阿里巴巴的 Dubbo、Facebook 的 Thrift、Google 的 gRPC、Twitter 的 Finagle 等。
图5 RPC 核心功能图
一个 RPC 的核心功能主要有 5 个部分组成,分别是:
- 客户端(Client):服务调用方;
- 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端;
- 网络服务:底层传输,可以是 TCP 或 HTTP;
- 服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理;
- 服务端(Server):服务的真正提供者。
图6 gRPC原理图
gRPC是一个高性能、通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。gRPC提供了一种简单的方法来精确地定义服务和为iOS、Android和后台支持服务自动生成可靠性很强的客户端功能库。客户端充分利用高级流和链接功能,从而有助于节省带宽、降低的TCP链接次数、节省CPU使用、和电池寿命。
gRPC具有以下重要特征:
- 强大的IDL特性 RPC使用ProtoBuf来定义服务,ProtoBuf是由Google开发的一种数据序列化协议,性能出众,得到了广泛的应用。
- 支持多种语言,支持C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP等编程语言。
参考文献:
https://developer.51cto.com/art/201906/597963.htm
https://colobu.com/2017/04/06/dive-into-gRPC-streaming/
2.4 远程调用REST
REST, resource REpresentational State Transfer, 资源表现状态转移。REST描述的是在网络中Client和Server的一种交互形式;RESTful API是REST的具体实现。Server提供的RESTful API中,URL中只使用名词来指定资源,原则上不使用动词。“资源”是REST架构或者说整个网络处理的核心。
图7 REST API的操作
用HTTP协议里的动词来实现资源的添加,修改,删除等操作。即通过HTTP动词来实现资源的状态扭转:
- GET 用来获取资源;
- POST 用来新建资源(也可以用于更新资源);
- PUT 用来更新资源;
- DELETE 用来删除资源。
Server和Client之间传递某资源的一个表现形式,比如用JSON,XML传输文本,或者用JPG,WebP传输图片等。
为什么要用RESTful结构呢?最开始的时候网页是前端后端融在一起的,比如PHP,JSP等。在桌面时代问题不大,但是随着移动互联网的发展,各种类型的Client层出不穷,RESTful可以通过一套统一的接口为 Web,iOS和Android提供服务。另外对于广大平台来说,比如Facebook platform,微博开放平台,微信公共平台等,它们不需要有显式的前端,只需要一套提供服务的接口,于是RESTful成为它们最好的选择。
Restful和RPC有哪些区别?
- 所属类别不同。REST,指某个瞬间状态的资源数据的快照,包括资源数据的内容、表述格式(XML、JSON)等信息。REST 是一种软件架构风格。这种风格的典型应用,就是HTTP。其因为简单、扩展性强的特点而广受开发者的青睐。RPC可以实现客户端像调用本地服务(方法)一样调用服务器的服务(方法)。RPC 可以基于 TCP/UDP,也可以基于 HTTP 协议进行传输的。
- 使用方式不同。HTTP接口只关注服务提供方,对于客户端怎么调用并不关心。接口只要保证有客户端调用时,返回对应的数据就行了。而RPC则要求客户端接口保持和服务端的一致。REST是服务端把方法写好,客户端并不知道具体方法。RPC是服务端提供好方法给客户端调用,客户端需要知道服务端的具体类,具体方法,然后像调用本地方法一样直接调用它。
- 面向对象不同。从设计上来看,RPC,是面向方法的;而REST是面向资源的。
- 序列化协议不同。接口调用通常包含两个部分,序列化和通信协议。通信协议,上面已经提及了,REST是基于HTTP协议,而RPC可以基于TCP/UDP,也可以基于HTTP。常见的序列化协议,有:json、xml、hession、protobuf、thrift、text、bytes等,REST通常使用的是JSON或者XML,而RPC使用的是 JSON-RPC,或者 XML-RPC。
- 应用场景。REST和RPC都常用于微服务架构中。HTTP相对更规范,更标准,更通用,无论哪种语言都支持http协议。如果你是对外开放API,基本最先支持的几个协议都包含RESTful。RPC框架作为架构微服务化的基础组件,它能大大降低架构微服务化的成本,提高调用方与服务提供方的研发效率,屏蔽跨进程调用函数(服务)的各类复杂细节。让调用方感觉就像调用本地函数一样调用远端函数、让服务提供方感觉就像实现一个本地函数一样来实现服务。REST调用及测试都很方便,RPC就显得有点繁琐,但是RPC的效率是毋庸置疑的。所以在多系统之间的内部调用采用RPC,对外提供的服务,Rest更加合适。
参考文献:
https://www.zhihu.com/question/28557115/answer/48094438
https://blog.csdn.net/John\_ToStr/article/details/103473077
作者:Chaobo
来源:https://mp.weixin.qq.com/s/lTjPY4ynOdkAqpnVdE6rXg
作者微信公众号
相关文章推荐
更多软硬件技术干货请关注软硬件融合专栏。