Adaptive Autosar通讯层:Skeleton Class

Skeleton Class

代码生成和构造

skeleton class也是直接从ARXML生成的。

开发人员要继承这个虚类,然后在子类里实现 架构设计的 本服务要提供的方法。

skeleton实例的构造函数重载:可以从

ara::com::InstanceIdentifierara::com::InstanceIdentifierContainer(multi-binding)、

ara::core::InstanceSpecifier 构造。

void OfferService(); void StopOfferService();都是幂等(idempotent)函数。随便调用多少次都和调用一次效果相同。

从同一个skeleton class可以部署很多的服务实例。当然了每次构造都要基于不同的identifier。在构造前会有一个静态类成员方法来检测该identifier是不是独一无二的。如果基于同一个identifier构建新实例,之前已经构造好的需要先销毁。这样也可以理解为什么不让使用复制构造函数和复制分配(copy construction nor copy assignment)。同样的identifier会让method调用之类的变得不确定。

下面来看三种不同的构造输入:

  • ara::com::InstanceIdentifier

​ 依据具体的binding描述符创建服务

  • ara::com::InstanceIdentifierContainer

​ multi-binding…依据容器包含的多个描述符创建实例。多绑定

  • ara::core::InstanceSpecifier

    依据 在service manifest中查找ara::core::InstanceSpecifier获得的 instance identifier(s)来创建服务实例,如果在manifest中这个InstanceSpecifier是map到了多个binding的描述符,那么就是multi-bingding.

在构造实例后,该实例会对用户不可见,以防止潜在的用户调用它的method。直到在服务的程序中调用

OfferService API

Offering Service instance

在初始化服务提供实例,并确保state之类的都到位之后,调用OfferService()来提供服务。
调用OfferService(),哪怕还没返回,service就已经开始接收服务请求了。
如果不想提供服务了,调用StopOfferService()。该API返回之后不再接收服务请求。
AP要求供应商在服务实例的析构函数中嵌入StopOfferService()。

轮询和事件驱动模式

服务调用何时发生是不可确定的。

对纯粹的事件驱动来讲,CM要生成events calls。然后把这些events转换成具体的method调用,再调用具体的service实现的service methods。

这样的结果是:

  • 直接调用服务Method是最快的,延时只受到整体的负载和IPC机制的限制。
  • 包含服务实例的OS进程上下文切换可能会很频繁且不确定。降低总的产出。

我们还支持纯纯的轮询方式。服务提供者可以显示调用ara::com的API来执行单个 call event。

构造函数的第二个参数是描述生成实例的处理方式(轮询还是事件之类的)

enum class MethodCallProcessingMode { kPoll, kEvent, kEventSingleThread };

在服务实例的整个生命周期内,该服务实例都是同一种mode。

Polling Mode

设为kPoll,CM不会异步调用提供的服务method。
在服务实例的代码中显式调用:

ara::core::Future<bool> ProcessNextMethodCall();

可以从CM的待处理Method call队列中取一条来执行。本API仅限Poll模式调用。

用Future的好处是在执行完之后可以得到提醒。可以使用future里的then(), 来判断在条件成熟的情况下,再调用ProcessNextMethodCall(),实现链式调用。

在有外部调用的时候,ara::core::Future的返回值会被CM设置为true。方便开发人员在队列里有调用请求排队的时候调用ProcessNextMethodCall。如果上一个调用已经返回false了,再调用可能不会做任何事情。(也有可能在返回的这一点点时间里来了一个调用,可能性很小)。

Event-Driven Mode

设为kEvent或者kEventSingleThread。CM就会在收到服务消费者请求的时候异步的调用服务实例的Method。
与轮询模式不同,服务用户会在调用method的时候触发服务提供者的活性(可以唤醒?是这个意思吧)。
KEvent让CM可以并发调用服务Method。比如有来自不同服务用户的一个MethodA和两个MethodB同时到达,CM会从线程池里拉3个线程来并发处理这些Method调用请求。
kEventSingleThread就是CM一次调用一个service method。CM需要维护一个队列来缓冲请求,然后一个个调用method处理。
设计两种方式是为了效率和节省资源考虑。如果Method调用间有大量的数据同步需求(比如先后依赖),建议使用kEventSingleThread避免大量空闲线程等待关键线程数据结果。

Methods

skeleton 生成类中。Method定义为纯虚函数。必须在子类中复写。
输入参数就直接映射到了Method的输入参数。返回值使用了ara::core::Future。
为什么要用Future呢,是因为我们会使用线程池来处理做Method的工作,调用的Method入口函数需要与线程池真正干活的线程同步(就是要等他干完活才能返回)。

面对多线程编程,和传统嵌入式的编程方式真的是有些不一样啊

下面来看两种实现方式 “同步”和“异步”:

using namespace ara::com;
/**
* Our implementation of RadarService
*/  
class RadarServiceImpl : public RadarServiceSkeleton {
public:
 Future<AdjustOutput> Adjust(const Position& position)
 {
 ara::core::Promise<AdjustOutput> promise;
 // calling synchronous internal adjust function, which delivers
results
 struct AdjustOutput out = doAdjustInternal(
 position,
 &out.effective_position);
 promise.set_value(out);
 // we return a future from an already set promise...
 return promise.get_future();
 }
 private:
 AdjustOutput doAdjustInternal(const Position& position) {
 // ... implementation
 }
 }

Method是一个入口,又调用了一个私有成员函数来执行操作(解耦?)。在调用doAdjustInternal得到返回值的时候,Method的输出就已经可以确定了。然后使用promise的set_value来获取out,再通过return来返回promise.get_future(),基于此让返回值构建函数返回的future。
调用这个Method的caller,可以马上通过Future::grt()获得结果。这个结果是直接通过调用方法函数得到的,不会存在阻塞。

下面是另外一种异步worker thread的方式。

 using namespace ara::com;

 /** 
 * Our implementation of the RadarService
 */ 
 class RadarServiceImpl : public RadarServiceSkeleton {

 public: 
 Future<AdjustOutput> Adjust(const Position& position)
 {
 ara::core::Promise<AdjustOutput> promise;
 auto future = promise.get_future();

 // asynchronous call to internal adjust function in a new Thread
 std::thread t(
 [this] (const Position& pos, ara::core::Promise prom) {
 prom.set_value(doAdjustInternal(pos));
 },
 std::cref(position), std::move(promise)).detach();

 // we return a future, which might be set or not at this point...
 return future;
 }

 private:
 AdjustOutput doAdjustInternal(const Position& position) {
 // ... implementation
 }
 }

上述代码中,实现了异步调用处理进程的方式。我们把实际上干活的函数封装在了一个lambda表达式中。

lambda表达式是在C++ 11 中引入的。可以理解为未命名的内联函数、闭包、函数内部无名的封装的代码块。
[]表示捕获,是说明表达式内可以使用的当前作用域内的信息,
[this]表示可以使用当前对象的成员。

Events

skeleton端的服务实现就是要通知别人事件的发生。

在skeleton端,每个事件都被封装在一个类里,在namespace skeleton下面。看起来和proxy那一端有很大的不同。(我觉得和Method比起来也很不同)。

 class BrakeEvent {
 public: 
 /** 
 * Shortcut for the events data type. 
 */ 
 using SampleType = RadarObjects;

 void Send(const SampleType &data);

 ara::com::SampleAllocateePtr<SampleType> Allocate();

 /**
 * After sending data you loose ownership and can’t access
 * the data through the SampleAllocateePtr anymore.
 * Implementation of SampleAllocateePtr will be with the
 * semantics of std::unique_ptr (see types.h)
 */
 void Send(ara::com::SampleAllocateePtr<SampleType> data);
 };

值得注意的是有两种Send重载变体。Send是向外发送event数据的接口。
第一种就是直接把整个data引用发送出去(给binding,从当前进程的堆区?类似的,反正是存在本地的某个地方)。因为在调用send()后, data可能在服务端会变化或者被释放(重新赋值啊之类的)binding会在调用中对data进行复制(这样在客户端拿到数据的时候才不会乱掉)。

在第二种Send()中。data 变成了ara::com::SampleAllocateePtr。按照AP常见的作法,他会在代码中提供抽象的接口,然后在抽象接口背后提供明确的到现有的C++类型的映射。这里这个指针的背后是std::unique_ptr。
这里的意思是只有某一方可以持有这个指针。如果所有者想要把这个指针交出去选哟显示地通过std::move。

来看内存分配成员函数:

ara::com::SampleAllocateePtr<SampleType> Allocate();

这个方法, 返回一个指针,分配了用于存放一个样本单位event数据的内存。
这个指针可以用于存放数据,也可以放在第二个Send()里发出去。
这么设计的原因是:为了优化数据复制!!!

如果data特别大,比如是个vector队列(我能传图像吗 hh)。用第一种Send()。会在当前进程的堆区开一块内存来存放数据。
然后在Send()的时候,binding需要复制一份数据到用户可以访问的内存空间。
如果数据又大,频率又高,那就崩了。

使用Allocate()和第二种Send()就是为了避免复制。

Allocate()应该把数据存在能让收发两边都能访问的内存区域。比如共享内存区域。这块区域需要做读写端同步机制,保证数据一致性。
event提供方,调用Allocate(),然后向分配的内存区域写数据。然后在Send()里提交所有权。
下面是使用示例:

 using namespace ara::com;

 // our implementation of RadarService - subclass of RadarServiceSkeleton
 RadarServiceImpl myRadarService;

 /** 
 * Handler called at occurrence of a BrakeEvent
 */ 
 void BrakeEventHandler() {

 // let the binding allocate memory for event data...
 SampleAllocateePtr<BrakeEvent::SampleType> curSamplePtr =
 myRadarService.BrakeEvent.Allocate();

 // fill the event data ...
 curSamplePtr->active = true;
 fillVector(curSamplePtr->objects);

 // Now notify event to consumers ...
 myRadarService.BrakeEvent.Send(std::move(curSamplePtr));

 // Now any access to data via curSamplePtr would fail -
 // we’ve given up ownership!
 }

Fields

  • 更新和通知 field 的变更
  • 响应 Get()和 Set()的调用。

skeleton::fields::下的类

 class UpdateRate {
 public: 

 using FieldType = uint32_t;

 /** 
 * Update equals the send method of the event. This triggers the
 * transmission of the notify (if configured) to
 * the subscribed clients.
 *
 * In case of a configured Getter, this has to be called at least
 * once to set the initial value.
 */
 void Update(const FieldType& data);

 /**
 * Registering a GetHandler is optional. If registered the function
 * is called whenever a get request is received.
 *
 * If no Getter is registered ara::com is responsible for responding
 * to the request using the last value set by update.
 *
 * This implicitly requires at least one call to update after
 * initialization of the Service, before the service
 * is offered. This is up to the implementer of the service.
 *
 * The get handler shall return a future.
 */
 void RegisterGetHandler(std::function<ara::core::Future<FieldType>()>
 getHandler);

 /**
 * Registering a SetHandler is mandatory, if the field supports it.
 * The handler gets the data the sender requested to be set.
 * It has to validate the settings and perform an update of its
 * internal data. The new value of the field should than be set
 * in the future.
 *
  * The returned value is sent to the requester and is sent via
 notification to all subscribed entities.
 */
 void RegisterSetHandler(std::function<ara::core::Future<FieldType>(
 const FieldType& data)> setHandler);
 };

Update方法来实现更改field的值。值更改后。binding会通知订阅者。binding会把传给他的值复制保存一份。

注册Setter 和 Getter

RegisterGetHandler注册Get()的处理方法。这个处理方法是服务开发人员实现的。
客户端调用Get()的时候,binding就会来调用这里注册的方法。只有在接口描述里面定义了存在field getter才会生成这种注册方法。
实际上,是否要实现个性化的Get()是可选的。一般来讲都不需要。binding会默认去取update()更新的最新值。
理论上使用这种GetHandler的理由是:如果计算一次field的结果很耗成本和时间,使用Gethandler可以在只有需要的时候把计算过程放在Gethandelr里来计算field。如果设置了on -change-notification,注册Get handler也没有什么意义。
总结:我虽然提供了这个API,但是最好别用。用了后果自负。

RegisterSetHandler与上面的Gethanler不同,希望开发人员都注册自己的SetHandler。这样可以在更新Field的值之前检测值的有效性。输入参数是clent希望设置的,返回值是经过Sethandler验证或者矫正的有效值。CM会根据SetHandler的返回值自动调用更新Fields值的程序,开发人员无需再在SetHandler里面显示调用Update()。

确保SetHandler存在

ara::com会确保SetHandler存在。如果不存在会报unchecked error.在OfferService()之前,开发人员需要为每个fields注册“SetHandler”,如果setter在配置中开了的话,不然的话 CM会报“未检查set值”的错误 。

保证任何时候Field的值都有效

服务实现者需要在服务对客户端可见前就提供一个Field值。这样在客户注册订阅后,通过初始值通知(如果有的话)或者Get,可以先拿到一个有效值。

CM需要报一个“unchecked error”当发生以下情况:

服务端调用OfferService()让服务可见,但是有的field没有被Update()。
这些field开启了“通知”(直接就发给订阅者了),或者使能了getter但是没有注册"GetHandler"(难道在gethandler 里面处理没初始值的情况也行?? hh).

注意:AP 模型支持定义field 初始值,存在一些生成变量之类的东西里。这样可以在Application代码中调用Update()把这些初始值传入。

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小羊苏C

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值