百度Apollo Cyber源码解读-整体数据传输框架解读

451 阅读5分钟

前言

前面的几篇文章主要讲的是Cyber中的某一个点,比如协程、component等,今天这篇文章主要是从总体的框架上来盘一下Cyber中主要的模块以及对应的职责。这里是数据流转的图。参考: pic3.zhimg.com/v2-df0088f6…

image.png

数据处理流程

先从Cyber的数据处理流程入手,将其拆解为六个关键步骤:

  1. 数据写入:Node节点中的Writer将数据写入通道。
  2. 消息发布与订阅:通道中的Transmitter负责发布消息,而Receiver则订阅这些消息。
  3. 消息分发触发:Receiver接收到消息后,会触发回调,进而激活DataDispatcher进行消息分发。
  4. 缓存与通知:DataDispatcher将消息放入CacheBuffer,并通过Notifier通知对应的DataVisitor处理消息。
  5. 数据融合与协程唤醒:DataVisitor从CacheBuffer中读取数据,进行融合处理,然后通过notifier_唤醒对应的协程。
  6. 数据处理与休眠:协程执行注册的回调函数,完成数据处理后再次进入睡眠状态。

技术实现与理解

在对数据流程有了整体把握后,我深入分析了各个模块之间的关系,并结合Apollo的技术实现,形成了以下理解:

  1. Component与Node:Component是封装好的数据处理流程,如自动驾驶中的Planning Component等。它在加载后会执行隐藏的“Initialize()”函数,创建一个Node节点,每个Component模块只能有一个Node节点,用于消息的订阅和发布。
  2. Node与Reader/Writer:Node节点可创建多个Reader和Writer,分别用于订阅和发布消息。
  3. Reader/Writer与Receiver/Transmitter/Channel:Channel对应一个唯一的Topic,包含Transmitter和Receiver。Reader通过Receiver订阅消息,Writer通过Transmitter发布消息。
  4. Receiver、DataDispatcher与DataVisitor:Receiver接收到消息后,触发DataDispatcher进行分发。DataDispatcher将数据放入缓存并通知DataVisitor,后者唤醒对应的协程。
  5. DataVisitor与Coroutine:DataVisitor通过Notify唤醒协程,协程执行绑定的数据处理函数,完成后进入休眠。
  6. Scheduler、Task与Coroutine:数据处理依赖协程完成,每个协程是一个Task,由Scheduler进行调度。Apollo在Linux中将进程优先级设为实时轮转,以确保进程优先级最高,再通过协程实现调度策略。

对协程调度策略的理解

详解见:百度Apollo-Cyber RT 协程实现原理简介 CyberRT 在user space 实现了自己的调度器,本文主 - 掘金

在Apollo中,协程调度策略是其高性能运行时框架Cyber RT的核心组成部分,旨在解决自动驾驶系统的高并发、低延迟和高吞吐量问题。以下是Apollo中协程调度策略的详细解释:

1. 多优先级队列

Cyber RT将每个任务视为一个协程,这些协程在线程上运行,并且可以设置优先级。协程通过一个多优先级队列来管理,每次从优先级最高的协程开始执行。这种策略确保了高优先级任务能够更快地得到处理,从而提高系统的响应速度和效率。

2. 任务窃取

在多核处理器环境中,不同Processor上的任务分配可能不均匀,导致一些Processor闲置,而另一些则处于饱和状态。为了提高CPU利用率,空闲的Processor会从饱和的Processor上“窃取”一半的任务过来执行。这种任务窃取机制有效地平衡了负载,提高了整体系统的效率。

3. 任务类型

Apollo中的任务分为两种类型:消息触发型和定时触发型。消息触发型任务在接收到特定消息时触发,而定时触发型任务则按照预设的时间间隔执行。这种分类使得任务管理更加灵活,能够适应不同的业务需求。

4. 优先级配置

任务的优先级可以通过配置文件进行设置。例如,在/apollo/cyber/conf/example_sched_choreography.conf中,可以设置不同任务的优先级。优先级值越大,任务的优先级越高。例如:

scheduler_conf {
  policy: "choreography"
  process_level_cpuset: "0"
  choreography_conf {
    choreography_processor_num: 4
    choreography_affinity: "range"
    choreography_cpuset: "0"
    choreography_processor_policy: "SCHED_FIFO" # policy: SCHED_OTHER, SCHED_RR, SCHED_FIFO
    choreography_processor_prio: 10
    pool_processor_num: 4
    pool_affinity: "range"
    pool_cpuset: "0"
    pool_processor_policy: "SCHED_OTHER"
    pool_processor_prio: 0
    tasks: [
      {
        name: "common0"
        processor: 0
        prio: 1
      },
      {
        name: "common1"
        processor: 0
        prio: 2
      },
      {
        name: "common2"
        processor: 0
        prio: 3
      },
      {
        name: "common3"
        processor: 0
        prio: 4
      }
    ]
  }
}

在这个配置中,common3的优先级最高,common0的优先级最低。

5. 调度策略

Cyber RT支持多种调度策略,包括SCHED_FIFOSCHED_RRSCHED_FIFO是一种先来先服务的调度策略,高优先级任务会一直运行直到完成或主动让出CPU。SCHED_RR则是一种时间片轮转调度策略,任务在时间片用完后会进入就绪队列等待下一次调度。

6. 协程实现

Cyber RT在用户空间实现了自己的调度器,协程(CRoutine)是非共享栈、非对称、通过自写汇编实现的。这种实现方式避免了进入内核空间,提高了上下文切换的效率。协程的状态管理通过RoutineState枚举类实现,包括READYFINISHEDSLEEPIO_WAITDATA_WAIT等状态。

7. 配置文件

调度策略和任务优先级可以通过配置文件进行灵活配置。例如,/apollo/cyber/conf/cyber.pb.conf文件中可以设置通信模式、共享内存配置等。这些配置文件使得系统管理员能够根据具体需求调整调度策略,优化系统性能。

总结

通过对Cyber框架的深入分析,我认识到,要理解复杂的系统,关键在于抽象化和模块化。Apollo的Cyber框架通过精心设计的模块和流程,实现了高效的数据处理和实时调度。希望我的分析能帮助你更好地理解Apollo的技术实现,也建议你绘制相应的数据流程图和关系图,以便更直观地把握这些概念。