Fuchsia OS中的RPC机制-FIDL

原文地址:http://blog.csdn.net/jinzhuojun/article/details/78007568

背景

Fuchsia是Google继Android和Chrome OS后推出的下一代操作系统。和其它OS类似,为了使应用能跨进程和服务端通信,它需要一套进程间的过程调用(RPC)机制,类似于Android中的binder。在Fuchsia中,各种service之间的通信接口都是用FIDL(Fuchsia Interface Definition Language,曾用名mojom)定义的(即那些以fidl为后缀名的文件,类似Android中的aidl文件)。因为Fuchsia的runtime支持多种编程语言,因此它有多种语言的binding(如C/C++,Dart,Go,Rust)。相应的实现目录为lib/fidl。这个目录下compiler目录下包含了fidl的编译器实现。前端负责解析处理fdil文件,多个后端分别输出对应C, Go, Rust语言的相应文件。这个目录编译后会产生fidl这个可执行文件(在host_x64目录下)和几个对应语言的generator。c, cpp, go, rust目录对应FIDL底层通信的实现和多个语言的binding。examples, fuzz目录主要是用例和测试。

因为Fuchsia采用了和Linux完全不同的kernel - Magenta,这里不得不引入几个和FIDL相关的概念。以下是官网的概念注释:

Service
A service is an implementation of a FIDL interface. Components can offer their creator a set of services, which the creator can either use directly or offer to other components.
Services can also be obtained by interface name from a Namespace, which lets the component that created the namespace pick the implementation of the interface. Long-running services, such as Mozart, are typically obtained through a Namespace, which lets many clients connect to a common implementation.

FIDL
The Fuchsia Interface Definition Language (FIDL) is a language for defining protocols for use over Channels. FIDL is programming language agnostic and has bindings for many popular languages, including C, C++, Dart, Go, and Rust. This approach lets system components written in a variety of languages interact seamlessly.

Channel
A Channel is the fundamental IPC primitive provided by Magenta. It is a bidirectional, datagram-like transport that can transfer small messages including Handles.

Handle
The “file descriptor” of the Magenta kernel. A Handle is how a userspace process refers to a kernel object. They can be passed to other processes overChannels.

可以看到,service的实现通过FIDL提供接口。FIDL通过channel作为底层数据传输机制。Channel和Fuchsia中提供的另外两种IPC机制(fifo, socket)相比,是唯一可以用来传handle的。说到handle,这也是Magenta中比较重要的概念。我们知道,kernel中管理这很多kernel object。userspace要通过system call和这些kernel object交互就需要这些object在userspace相应的handle。这个handle用mx_handle_t,即32位整形表示。一个kernel object可以有多个handle,这些handle可以在不同进程。当对于同一个object的handle都关闭时,相应的object即被销毁或者标记。是不是感觉和Linux中的fd很像,确实有很多相似之处。区别之一是handle有right的概念来管理权限,且同一个object的不同handle可以拥有不同的right。

值得注意的是,和Android的binder相比,FIDL采用了不同的线程模型。主要区别是binder是基于线程池的多线程结构,调用是同步的。当binder线程有什么需要主线程完成的工作时,会post消息到主线程的消息循环中。而FIDL采用的是单线程异步通信。对上层代码来说意味着更少的线程间数据同步。


这里写图片描述

Sample走读

我们可以通过自带的简单sample大体看下它是如何工作的。在examples目录下有个echo的sample,实现分布在services,echo_client_cpp和echo_server_cpp目录下。功能简单地不能再简单,client发送字符串,server端回复字符串。接口描述文件echo.fidl在编译时会生成以下文件:

./debug-x86-64/gen/lib/fidl/examples/services/echo.fidl-common.cc 
./debug-x86-64/gen/lib/fidl/examples/services/echo.fidl.h 
./debug-x86-64/gen/lib/fidl/examples/services/echo.fidl.cc 
./debug-x86-64/gen/lib/fidl/examples/services/echo.fidl-common.h 
./debug-x86-64/gen/lib/fidl/examples/services/echo.fidl-sync.h 
./debug-x86-64/gen/lib/fidl/examples/services/echo.fidl-sync.cc 
./debug-x86-64/gen/lib/fidl/examples/services/echo.fidl-internal.h 
./debug-x86-64/gen/lib/fidl/examples/services/echo.fidl.dart

其中echo.fidl-common.h中的Echo为接口类,它定义了用于RPC的基本接口。

64 class Echo : public internal::Echo_Base {
65 public:
66   virtual ~Echo() override {}
67 
68   using Proxy_ = EchoProxy;
69   using Stub_ = EchoStub;
70   using EchoStringCallback = std::function<void(::fidl::String)>;
71   virtual void EchoString(const ::fidl::String& value, const EchoStringCallback& callback) = 0;
72 };

另外还定义了Proxy_和Stub_类型分别为EchoProxy和EchoStub。它们分别类似于Android中的BpXXX和BnXXX。是用户逻辑和通信层间的接口相关层,主要用于各种参数及回调数据的encode/decode(即序列化和反序列化)等。过程中会大量使用fidl/cpp中的实现。用户逻辑层指的是用户自己的代码,在这个例子中就是echo_client.cpp和echo_server.cpp中的代码。通信层主要用于处理连接和各种函数的派发。数据的传输基于最底层的kernel中的channel机制。这几层的关系大体如下:


这里写图片描述

1. 初始化

下面看下基本流程。在server端,sample中开始会创建EchoDelegate对象,这个类的构造函数中会创建Echo服务:

31 class EchoDelegate {
32 public:
33  EchoDelegate()
34    : context_(app::ApplicationContext::CreateFromStartupInfo()) {
35    context_->outgoing_services()->AddService<Echo>(
36      [this](fidl::InterfaceRequest<Echo> request) {
37        echo_provider_.AddBinding(std::move(request));
38      });
39  }

首先通过CreateFromStartupInfo()初始化ApplicationContext,然后通过outgoing_services()拿出它的ServiceNamespace类型成员,再通过它的AddService()接口添加service。这里的Echo即为service的interface。它的定义Echo在生成的echo.fidl-common.h中。可以看到,这个新添加的service名字叫”echo::Echo”(echo.fidl-common.cc中)。这里AddService()函数的参数类型为InterfaceRequestHandler,它封装了传入的lambda函数。该lambda函数中即调用了EchoImpl的AddBinding()。比如client发起interface为Echo的服务连接请求,这个InterfaceRequestHandler就会被调用,它将本地的echo_provider_(类型为EchoImpl)和client端请求进行绑定。

15 class EchoImpl : public Echo {
16 public:
17  void AddBinding(fidl::InterfaceRequest<Echo> request) {
18    bindings_.AddBinding(this, std::move(request));
19 }

在EchoImpl::AddBinding函数中会调用BindingSet::AddBinding(),这个函数里会创建Binding对象,它代表interface实现与channel之间的绑定关系(当Binding对象被销毁,channel和interface之间的绑定关系也被销毁,channel随之关闭,interface实现对象随即处于未绑定状态),之后把它加入到BindingSet所维护的bindings_这个表中。这样就将client发来的request与对应的实现类建立了关系。可以看到,实现类可以和多个client的request进行绑定。

92  // Constructs a completed binding of |impl| to the channel endpoint in
93  // |request|, taking ownership of the endpoint. Does not take ownership of
94  // |impl|, which must outlive the binding. See class comment for definition of
95  // |waiter|.
96  Binding(ImplPtr impl,
97          InterfaceRequest<Interface> request,
98          const FidlAsyncWaiter* waiter = GetDefaultAsyncWaiter())
99      : Binding(std::forward<ImplPtr>(impl)) {
100    Bind(request.PassChannel(), waiter);
101  }

在Binding的构造函数中会调用Bind()函数,它其中会创建Router并保存指针到internal_router_变量中。Router变量中将thunk_(类型为HandleIncommingMessageThunk)设置到本地变量connector_中。Router的成员connector_类型为Connector。如它的名字,它主要负责通过channel进行数据传输。当Connector中需要调用到Router中时,就需要这个thunk作为跳板。然后将stub_(这里实际类型为EchoStub)的指针设到internal_router_的incoming_receiver_成员中。这个stub又是router到上层逻辑的跳板。

116  // Completes a binding that was constructed with only an interface
117  // implementation. Takes ownership of |handle| and binds it to the previously
118  // specified implementation. See class comment for definition of |waiter|.
119  void Bind(mx::channel handle,
120            const FidlAsyncWaiter* waiter = GetDefaultAsyncWaiter()) {
121    FTL_DCHECK(!internal_router_);
...
129    internal_router_.reset(
130        new internal::Router(std::move(handle), std::move(validators), waiter));
131    internal_router_->set_incoming_receiver(&stub_);
132    internal_router_->set_connection_error_handler([this]() { 133 if (connection_error_handler_) 134 connection_error_handler_(); 135 }); 136 }

再来看看client端。首先创建消息循环和EchoClientApp对象,接着调用EchoClientApp的Start()函数。最后进入消息循环。重点看下EchoClientApp::Start()函数。它的参数为service的url,就是service的path。

31  bool Start(std::string server_url) {
32    auto launch_info = app::ApplicationLaunchInfo::New();
33    launch_info->url = server_url;
34    launch_info->services = echo_provider_.NewRequest();
35
36    context_->launcher()->CreateApplication(std::move(launch_info),
37                                            controller_.NewRequest());
38
39    app::ConnectToService(echo_provider_.get(), echo_.NewRequest());
40    FTL_DCHECK(echo_);
41
42    echo_->EchoString("hello world",
43                      [this](fidl::String value) {
44                        ResponsePrinter printer;
45                        printer.Run(std::move(value));
46                      });
47    return true;
48  }

这里通过ApplicationContext通过launcher()接口拿到ApplicatonLauncher的指针。然后调用其CreateApplication()将server端进程起来,然后才有了上面的server端初始化。这里的CreateApplication()是application launcher服务提供的接口。这里比较重要的是获得ServiceProvider的本地proxy,因为ServiceProvider本身就是一个service接口。这里的NewRequest()函数会创建channel,一端放于InterfacePtr<>(即ServiceProviderPtr),另一端于InterfaceRequest<>中,用于和远端interface实现绑定。

99  InterfaceRequest<Interface> NewRequest() {
100    FTL_DCHECK(!is_bound()) << "An existing handle is already bound.";
101
102    mx::channel endpoint0;
103    mx::channel endpoint1;
104    mx::channel::create(0, &endpoint0, &endpoint1);
105    Bind(InterfaceHandle<Interface>(std::move(endpoint0), Interface::Version_));
106    return InterfaceRequest<Interface>(std::move(endpoint1));
107  }

然后通过system_provider这个本地proxy调用ConnectToService()连接Echo这个service。

15// Helper for using a |ServiceProvider|'s |ConnectToService()| that creates
16// a new channel and returns a fully-typed interface pointer (and can use
17// the default interface name).
18template <typename Interface>
19inline fidl::InterfacePtr<Interface> ConnectToService(
20    ServiceProvider* service_provider,
21    const std::string& interface_name = Interface::Name_) {
22  fidl::InterfacePtr<Interface> interface_ptr;
23  service_provider->ConnectToService(interface_name,
24                                     interface_ptr.NewRequest().PassChannel());
25  return interface_ptr;
26}

到这里,成员变量echo_就成为了远端对象的本地proxy。这个过程类似于Android中server端的addService()和client端的getService()。这个本地proxy的类型为EchoPtr。这样client端的EchoPtr和server端的EchoImpl就有了跨进程的绑定关系,为后面的RPC打下了基础。总体流程如下:


这里写图片描述

2. Client发请求

之前提到,client发起请求会通过EchoPtr这个本地对象,而EchoPtr真实类型为InterfacePtr(定义在echo.fidl.h)。InterfacePtr中有个重要的成员变量internal_state_,类型为InterfacePtrState。

127  // Returns a raw pointer to the local proxy. Caller does not take ownership.
128  // Note that the local proxy is thread hostile, as stated above.
129  Interface* get() const { return internal_state_.instance(); }
130
131  // Functions like a pointer to Interface. Must already be bound.
132  Interface* operator->() const { return get(); }
133  Interface& operator*() const { return *get(); }

可以看到,对于EchoPtr的所有调用,会转成InterfacePtrState的instance()来获得proxy。这里根据需要用ConfigureProxyIfNecessary()函数初始化Router和Proxy对象。

123    router_ = new Router(std::move(handle_), std::move(validators), waiter_);
124    waiter_ = nullptr;
125
126    proxy_ = new Proxy(router_);

Router类继承自MessageReceiverWithResponder。把它作为参数创建proxy。这个proxy的类型为EchoProxy。接下来通过该proxy向server端发起调用请求EchoString(),其实最后会调用该EchoProxy对应接口。

60 void EchoProxy::EchoString(                                                                                   
  61     const ::fidl::String& in_value, const EchoStringCallback& callback) {                                     
  62   size_t size = sizeof(internal::Echo_EchoString_Params_Data);                                                
  63   size += GetSerializedSize_(in_value);                                                                       
  64   ::fidl::RequestMessageBuilder builder(                                                                      
  65       static_cast<uint32_t>(internal::Echo_Base::MessageOrdinals::EchoString), size);                         
  66                                                                                                               
  67   internal::Echo_EchoString_Params_Data* params =                                                             
  68       internal::Echo_EchoString_Params_Data::New(builder.buffer());                                           
  69   SerializeString_(in_value, builder.buffer(), &params->value.ptr);                                           
  70   params->EncodePointersAndHandles(builder.message()->mutable_handles());                                     
  71   ::fidl::MessageReceiver* responder =                                                                        
  72       new Echo_EchoString_ForwardToCallback(callback);                                                        
  73   if (!receiver_->AcceptWithResponder(builder.message(), responder))                                          
  74     delete responder;                                                                                         
  75 }

首先要把参数序列化。这里的参数包括一个字符串和一个回调函数。这些参数的序列化方法之前也已经自动化生成了,类名为Echo_EchoString_Params_Data;相应的负责response的参数序列化的类为Echo_EchoString_ResponseParams_Data。它们的申明和定义位于eco.fidl-internal.h和echo.fidl-common.cc中。大体上要处理的数据有三类:一类是普通数据,拷贝即可;第二类是指针,因为跨进程传递后它失去意义,因此要将它转成相对地址;第三类是handle,即mx_handle_t。在这里先创建RequestMessageBuilder,然后通过SerializeString()和EncodePointerAndHandles()等方法构建Message对象。另外,因为这个接口还有个callback,因此下面用Echo_EchoString_ForwardToCallback()对这个callback进行封装。最后调用Router的AcceptWithResponder()将刚才构造的Message发出去。

103bool Router::AcceptWithResponder(Message* message, MessageReceiver* responder) {
104  FTL_DCHECK(message->has_flag(kMessageExpectsResponse));
105
106  // Reserve 0 in case we want it to convey special meaning in the future.
107  uint64_t request_id = next_request_id_++;
108  if (request_id == 0)
109    request_id = next_request_id_++;
110
111  message->set_request_id(request_id);
112  if (!connector_.Accept(message))
113    return false;
114
115  // We assume ownership of |responder|.
116  responders_[request_id] = responder;
117  return true;
118}

其中首先生成该request的id,因为当server端返回结果后,需要根据这个id来找相应的reponse callback。这个信息记录在reponders_成员中。其中最主要的是Connection::Accept()函数。它完成真正的消息发送工作。

76bool Connector::Accept(Message* message) {
...
84  mx_status_t rv =
85      channel_.write(0, message->data(), message->data_num_bytes(),
86                     message->mutable_handles()->empty()
87                         ? nullptr
88                         : reinterpret_cast<const mx_handle_t*>(
89                               &message->mutable_handles()->front()),
90                     static_cast<uint32_t>(message->mutable_handles()->size()));

到此为止,client端发送request的流程结束。


这里写图片描述

3. Server端回复

可以看到Router和Connector是client和server端都有的,它们分别负责跨进程过程调用和底层数据传输。在Connector的构造函数中,会调用WaitToReadMore()来监听handle。这个函数其实调用了kDefaultAsyncWaiter中的AsyncWait()函数(位于/lib/mtl/waiter/default.cc)。它朝当前MessageLoop中添加对channel的监听,当有消息来(或对方关闭连接时)调用回调Connector::CallOnHandleReady(),继而调用Connector::OnHandleReady()函数。

121void Connector::OnHandleReady(mx_status_t result, mx_signals_t pending) {
...
129
130  if (pending & MX_CHANNEL_READABLE) {
131    // If the channel is readable, we drain one message out of the channel and
132    // then return to the event loop to avoid starvation.
133
134    // Return immediately if |this| was destroyed. Do not touch any members!
135    mx_status_t rv;
136    if (!ReadSingleMessage(&rv))
137      return;
138
139    // If we get MX_ERR_PEER_CLOSED (or another error), we'll already have 140 // notified the error and likely been destroyed. 141 FTL_DCHECK(rv == MX_OK || rv == MX_ERR_SHOULD_WAIT); 142 WaitToReadMore(); 143 144 } else if (pending & MX_CHANNEL_PEER_CLOSED) { 145 // Notice that we don't notify an error until we've drained all the messages 146 // out of the channel. 147 NotifyError(); 148 // We're likely to be destroyed at this point.
149  }
150}

正常情况下调用ReadSingleMessage()读取一个message并派发它(调用相应的回调)。在ReadAndDispatchMessage()函数中,先调用ReadMessage()执行读取message动作,然后通过HandleIncomingMessageThunk::Accept()调用到Rounter::HandleIncomingMessage()函数。这个函数中又分几种情况:1) Message期望有response; 2) Message本身为response; 3) 其它。这里属于第一种情况。

136  if (message->has_flag(kMessageExpectsResponse)) {
137    if (incoming_receiver_) {
138      MessageReceiverWithStatus* responder = new ResponderThunk(weak_self_);
139      bool ok = incoming_receiver_->AcceptWithResponder(message, responder);
140      if (!ok)
141        delete responder;
142      return ok;
143    }
144
145    // If we receive a request expecting a response when the client is not
146    // listening, then we have no choice but to tear down the channel.
147    connector_.CloseChannel();

这里先创建ResponderThunk()对象,作为参数传到EchoStub的AcceptWithResponder()中。这个函数实现在生成的echo.fidl.cc中。其中会decode相应的参数,因为EchoString()这个request是带response的,之后需要创建Responder的proxy,类型为EchoStringCallback()。最后调用到用户代码定义的回调,即echo_server.cc中的EchoString()。

21  void EchoString(const fidl::String& value,
22                  const EchoStringCallback& callback) override {
23    FTL_LOG(INFO) << "EchoString: " << value;
24    callback(value);
25  }

函数最后调用了callback,这里其实调用了Echo_EchoString_ProxyToResponder::operator()(在echo.fidl.cc中)。因为它本质也是一个跨进程过程调用,因此其中和client发request时类似,需要将回调的参数序列化,最后调用ResponderThunk::Accept()函数。这个函数进而调用Router::Accept()和Connector::Accept()将消息通过channel发出去。


这里写图片描述

4. Client端处理回复

到这里数据流又回到了client这端。它需要处理server端发回的response。Client和server端类似,当有消息来最后也会调用到Router::HandleIncomingMessage()处理,这时会走到上面说的第二种情况,即message本身为response。之前在消息从client发出时对于有callback的情况已经将相应的MessageReceiver对象(Echo_EchoString_ForwardToCallback,定义在echo.fidl.cc中)记录在responders_这个映射表中,这时就可以根据id取出相应的MessageReceiver对象,然后调用其Accept()接口。

148  } else if (message->has_flag(kMessageIsResponse)) {
149    uint64_t request_id = message->request_id();
150    ResponderMap::iterator it = responders_.find(request_id);
151    if (it == responders_.end()) {
152      FTL_DCHECK(testing_mode_);
153      return false;
154    }
155    MessageReceiver* responder = it->second;
156    responders_.erase(it);
157    bool ok = responder->Accept(message);
158    delete responder;
159    return ok;

在Echo_EchoString_ForwardToCallback::Accept()中,按惯例需要decode相应参数,最后调用用户代码中定义的callback,即echo_client.cc中EchoString()中定义的lambda函数。

42    echo_->EchoString("hello world",
43                      [this](fidl::String value) {
44                        ResponsePrinter printer;
45                        printer.Run(std::move(value));
46                      });

这个阶段流程大体如下:


这里写图片描述

这样基本上整个流程就结束了。最后总结个主要的相关类图。


这里写图片描述
相关文章

相关标签/搜索