原文地址: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大体看下它是如何工作的。在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机制。这几层的关系大体如下:
下面看下基本流程。在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 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 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 request,
98 const FidlAsyncWaiter* waiter = GetDefaultAsyncWaiter())
99 : Binding(std::forward(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 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(std::move(endpoint0), Interface::Version_) );
106 return InterfaceRequest(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打下了基础。总体流程如下:
之前提到,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(), ¶ms->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(message->mutable_handles()->size()));
到此为止,client端发送request的流程结束。
可以看到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发出去。
到这里数据流又回到了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 });
这个阶段流程大体如下:
这样基本上整个流程就结束了。最后总结个主要的相关类图。