Apollo:CyberRT源码概览

最顶层:Node

第二层

Reader

在这里插入图片描述

通过注释我们可以知道:

  • Reader通过订阅Channel获取消息。
    • Reader会和一种Topology网络发生关系,可以加入也可以退出(通过成员函数JoinTheTopology()和LeaveTheTopology())
  • 收到消息时会触发传递到Reader的回调callback
  • 也可以监听Blocker中的缓存信息

在这里插入图片描述

Reader继承自ReaderBase

在这里插入图片描述
它包含一个Blocker和一个Receiver。

在这里插入图片描述
Reader和Channel相关联的,由Reader的成员变量ChannelManager进行管理

Reader只在实际环境中会被NodeChannelImpl创建,并主要做两个事:

  • 每当该Channel来了一个消息,就会调用其Enqueue函数,该函数会调用Blocker::Publish函数把该消息放入Reader中的Blocker的published_msg_queue队列并调用Blocker保存相应的回调函数来处理(可是实际环境中Blocker的回调函数为空,所以不会有用)。
  • 如果一开始设置有一个回调函数,那么当message到达时就会在调用Enqueue函数后额外调用该回调函数来处理(可是实际环境中Reader的回调函数都为空,所以这一步是不可能发生的)

Writer

有Reader必然有Writer

  • Writer的作用是向对应的Channel写数据,最后会调用到transport::Transmitter->Transmit函数
  • 每个Writer只能写一个channel但某个channel可以有多个Writer。

IntraReader

代码在cyber/bolcker/intra_reader.h,它继承自Reader,只在虚拟环境中会被NodeChannelImpl创建。它会重载Reader::Init函数,不再创建协程,而是直接把IntraReader::OnMessage(记录时间并调用IntraReader创建时传入的回调函数)注册为该Blocker的回调函数。

IntraWriter

同样是虚拟环境中用的,它的Write函数不会调用到Transmitter的方法,而是直接调用到Blocker::Publish函数来放入数据。

第三层

在这里插入图片描述

Blocker & BlockerManager

Blocker是Reader的一个成员

  • Blocker里面保存了一张callback_id和回调函数的map
  • BlockerManager保存了全局的一张channel_name对应Blocker的map

可以将Blocker理解为一个消息的缓存类,同时也记录了该Reader的一些回调函数。这里之所以用一些是因为Blocker这个结构的主要功能应该是提供一个管理者获取数据的入口,方便debug、记录日志、运行虚拟环境和监控整个系统,所以在Blocker里注册的回调函数应该都是管理员注册的监控函数,和系统主逻辑没关系。

DataVisitor

代码位于cyber/data/data_visitor.h

该继承自DataVisitorBase:

在这里插入图片描述
DataVisitorBase有一个指向DataNotifier的指针,还有一个回调函数Notifier:
在这里插入图片描述
该回调函数在Component::Initialize函数最后调用Scheduler::CreateTask(cyber/scheduler/scheduler.cc中作为匿名函数被visitor->RegisterNotifyCallback注册)时被定义并赋值给DataVisitorBase中的notifier_(它的作用就是唤醒创建的协程,该协程做的事情就是调用dv->TryFetch来拿数据,如果成功就调用组件的Proc函数):
在这里插入图片描述
该回调函数会在DataVisitor初始化的时候注册到全局的DataNotifier中的map中。

  • DataVisitor在Component::Initialize和Reader::Init中都会创建,是实际环境中获取数据的方式。它最终会调用到AllLatest::Fusion函数。每个DataVisitor的每个消息种类都会有一个缓存ChannelBuffer,在初始化的时候这些ChannelBuffer都会被加入到DataDispatcher中的一张map中。而所有种类的消息还会有一个消息整合体的ChannelBuffer,这条buffer不会放入全局map(因为不会有Dispatcher会直接发这种数据),但之后取数据都是从这里取。取的时候按照计数顺序也就是index来取,所以我们可以看到这里没有实时性保证(除非buffer队列长度为1,但系统默认长度其实也就是1,位于cyber/node/reader.h:DEFAULT_PENDING_QUEUE_SIZE)。
  • 对于有多个种类消息的DataVisitor还是只有一个Notifier对应M0,也就是说只有当M0消息来的时候协程会被唤醒并通过DataFusion来获取整合所有种类的信息。

DataNotifier

  • DataNotifier是一个全局的单例,保存有一个channel_id对应vector<std::shared_ptr>的map(即每个channel对应多个Notifier,因为有多个订阅者),在DataVisitor初始化的时候就会把自己的Notifier注册到这个map中。

DataDispatcher

  • DataDispatcher也是一个全局单例,记录了一个channel_id对应vector<std::weak_ptr<CacheBuffer<std::shared_ptr>>>的map(也就是说每个channel会有好几个订阅者,每个订阅者拥有一条CacheBuffer,每次收到该channel的消息会给每个buffer都放一份)
  • 它主要的功能就是DataDispatcher::Dispatch函数用来向某个channel分发收到的数据,它会先从map中取出所有对应的buffer,然后调用CacheBuffer::Fill函数来给buffer填数据(这里的Fill函数还会去把所有种类的消息整合成一条新的整合数据,后面会介绍),之后再调用DataNotifier::Notify函数来找出所有对应该channel_id的Notifier并调用它们唤醒一开始创建的协程来取数据并运行回调函数。

其他

TopologyManager

在这里插入图片描述

  • CyberRT中的元素之间的关系由Topology呈现
  • Topology相当于一个有向图dag
  • Node是DAG中的顶点
  • Channel是Write流向Reader的边
  • Service是Server流向Client的边
  • Topology是由TopologyManager生成的
  • TopologyManager内有三个子Mannager:NodeManager、ChannelManager、ServiceManager
    • NodeManager用来查找Node是否在Topology中
    • ChannelManager用来查找Channel是否在Topology中,以及对应的Writer 和 Reader
    • ServiceManager用来查找 Service 是否在 Topology 当中,以及对应的 Server 和 Client
  • TopologyManager依靠fast-rtps通信,可以监听元素加入和离开Topology网络
  • 可以自己注册ChangeFunc来监听Topology网络的变化

在这里插入图片描述

Component

Component 的基类是 ComponentBase
在这里插入图片描述
从相应的头文件可以看到一些信息提示:

  • Component 可以通过一些 .proto 配置
  • class loader 可能会加载它
  • 内置 Reader
  • 有相应的 Node
  • 通过 Scheduler 进行调度

我们接下来看 Component 相关的头文件。
在这里插入图片描述
通过注释我们可以看到:

  • 一个Component最多可以支撑4个Channel进行消息处理
  • Component继承自ComponentBase,开发者可以自行定义Component,只要继承Component并复写它的 Init() 和 Proc()
  • Init 和 Proc 的调用是由CyberRT Frame 驱动的,不要主动去调用他们
    在这里插入图片描述
    Initialize 有protocol文件进行配置
    Proc是Component的逻辑处理单元,包含 4 个参数,分别代表 4 个 channel 的消息。

TimerComponent

看名字就知道它是一个定时器组件。
在这里插入图片描述
在这里插入图片描述
TimerComponent 同样继承自 ComponentBase,不过内部有一个定时器 Timer。
在这里插入图片描述

Timer 的内部又有两个关键成员变量类型 TimingWheel 和 TimerTask,顾名思义是定时器时间轮换和定时器任务相关的类。

问题是谁触发 TimerTask 呢?

我们很容易想到调度器,而 CyberRT 代码中也正好有一个 scheduler。

问题是 scheduler 又是谁触发的呢?

scheduler

在一个线团中去找线头是件很难的事情,所以,需要借助于猜测。

我猜测整个 cyberRT 的起源是 init.c 这个文件。
在这里插入图片描述
出现了 scheduler 的身影,但这里是设置 log 相关的线程。
所以,我将目光移到 SysMo 上。
在这里插入图片描述
SysMo创建的时候会建立一个线程,里面执行一个while循环,然后每次调用scheduler的CheckSchedStatus方法

所以,我们终于可以去阅读 scheduler 相应的代码了。

在这里插入图片描述

这是一个工程类,根据策略不同有SchedulerClassic和SchedulerChoreography两种实现。这里选择 SchedulerClassic 看看它内部长什么样子。

看它的头文件可以发现一些关键要素:

  • 采用协程调度
  • 内部会创建 Processor,并会定期触发通知信息
  • 有一些 ClassicTask,可能代表常规的任务

但聊聊约约我察觉到代码路径离我的目标越来越远,我其实关注的是如何找到 Node 和 Component 的源头,它们是如何被周期触发 proc 方法的。

于是我跳出代码森林,重视审视整个代码目录,然后发现了 mainboard。\

mainboard

mainboard 是 cyberRT 的入口,init 方法都在这里触发。Module 也在这里启动。

参考

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值