Apollo Cyber RT操作系统学习记录(一)

本文介绍 Apollo Cyber RT操作系统学习记录(一)

Apollo Cyber RT操作系统学习记录(一)

This article was original written by Jin Tian, welcome re-post, first come with https://jinfagang.github.io . but please keep this copyright info, thanks, any question could be asked via wechat: jintianiloveu

Apollo在2019年初,发布了3.5版本,随之发布的是CyberRT,这是一个取代ros的实时操作系统,它似乎解决了ros遇到的许多问题,从底层上给予了自动驾驶系统更加稳定的依赖。但是事实如果真的如此,那未来岂不是要成为行业内的标杆?不学习一波,不分析一波,恐怕对不住大家。

CyberRT架构

这里头一张图,来展示一下这个系统的架构:

最底层的是一些apollo内部使用的库,为了减少依赖,提高整个系统的效率,许多轮子得自己造。比如他们自己实现的高效率的FreeList对象回收池。

再往上是通信相关的,包括服务发现,还有 Publish-Subscribe 通信机制。 Cyber RT 也支持跨进程、跨机通信,上层业务逻辑无需关心,通信层会根据算法模块的部署,自动选择相应通信机制。

通信层之上是 数据缓存/融合层,多路传感器之间数据需要融合,而且算法可能需要缓存一定的数据。比如典型的仿真应用,不同算法模块之间需要有一个数据桥梁,数据层起到了这个模块间通信的桥梁的作用。

再往上是计算模型,计算模型包括刚才前面提到的调度和任务,后面我们会详细讨论。

计算模型之上是为开发者提供的接口。Cyber RT为开发者提供了Component 类,开发者的算法业务模块只需要继承该类,实现其中的 Proc 接口即可。该接口类似于 ROS 中的 Callback,消息通过参数的方式传递,用户只要在Proc中实现算法、消息处理相关的逻辑。Cyber RT 也基于协程,为开发者提供了并行计算相关的接口。

这点似乎比ROS要好,自己的类只要继承Component,然后实现Proc方法,就可以实现消息的回调,似乎比ROS要精简一些?

CyberRT中另外一个比较重要的创新就是,改变了ROS里面的调度顺序,或者说ROS里面其实并没有调度,完全依赖于系统,此时就会导致系统负载过多过乱,CyberRT在这个基础之上引入了自建调度体系,这是一个十分重要的创新,想对于ROS来说。

另外一个比较重要的点就是,apollo里面将算法搭载在协程之上,关于协程和线程的区别,可以简单的概述为:协程是轻量化的线程,线程是进程下面的多个并行化的任务,线程与线程之间的通信必须通过信道进行,而协程可以直接通过访问全局变量来进行协程 之间的通信。具体如何搭载,还得看代码实现和分析了。

CyberRT代码分析

CyberRT的实现还是比较模块化的,比较重要的就是component和croutine两个模块,后者是自己实现的一个高性能的协程库,为整个系统提供协程的调用。

Docker编译Apollo3.5

Here is the step:

# install git-lfs first
sudo apt-get install -y git-lfs
# pull large files (some js, model, .so file)
git-lfs fetch --all
echo 'export APOLLO_HOME=$(pwd)' >> ~/.zshrc
source ~/.zshrc
# pull apollo docker first
./docker/scripts/dev_start.sh
# into apollo docker
./docker/scripts/dev_into.sh
# build apollo inside docker
# optional: you can also build CPU only mode apollo
bash apollo.sh build
# start dreamview
./scripts/bootstrap.sh

但是有时候打开没有任何界面咋回事??git-lfs没有把大文件拉取下来,进去你会发现js文件根本没有拿到,全部是meta。用这个命令fetch一下,讲道理是全部下载下来的:

git lfs fetch --all

最后运行完bootstrap之后,要用firefox打开,为什么不用chrome呢?chrome有缓存很但疼。

Apollo内部核心模块扣出

其实apollo中最核心要紧的就是它内部的一些算法,比如perception里面的cnnseg,跟踪,yolo3d检测等,如果能拿到并使用它来做我们的自己的感知,那就有点意思了。

CyberRT的上手入门

既然这个操作系统解决了一些ROS的弊端,那为什么不拿来用呢?怎么用,先从最基本的话题发送,接收开始吧。首先我们要知道ROS存在哪些问题,简单的列举一些吧:

  • 烦人的master,由于master的存在,导致你每次的ros工程都要启动master节点,否则GG,并且由于所有的节点都依赖于master,一旦master挂机了,其他的节点都GG了;
  • ROS内部没有调度,完全依赖于操作系统,这个就很蛋疼了,所有进程之间没有优先性,这回导致一些重要的节点有时候会由于其他非重要节点的抢占而卡吨,而有时候他们的卡顿是致命的,比如控制和规划节点,你控制如果落后了,规划再好都没有用。

OK,那么我们来看看Cyber的优点,其中很显然的一点,开源!代码简单!将真,cyber的代码真的写的很清晰。但是看懂他的逻辑就比较难了,不过也没有必要,你能熟练的使用他,甚至用他完全取代ros,你就把他变成了你自己的东西了。

首先我们来看一下Apollo里面全新的代码组织结构是如何组织,我们只看cyber和modules:

├── cyber
│   ├── base
│   ├── binary.h
│   ├── blocker
│   ├── BUILD
│   ├── class_loader
│   ├── common
│   ├── component
│   ├── conf
│   ├── croutine
│   ├── cyber.cc
│   ├── cyber.h
│   ├── data
│   ├── event
│   ├── examples
│   ├── init.cc
│   ├── init.h
│   ├── io
│   ├── logger
│   ├── mainboard
│   ├── message
│   ├── node
│   ├── parameter
│   ├── proto
│   ├── python
│   ├── py_wrapper
│   ├── README.md
│   ├── record
│   ├── scheduler
│   ├── service
│   ├── service_discovery
│   ├── setup.bash
│   ├── state.cc
│   ├── state.h
│   ├── task
│   ├── time
│   ├── timer
│   ├── tools
│   └── transport
├── cyber_tests
│   └── detection
├── data
│   ├── bag
│   ├── core
│   ├── kv_db.sqlite
│   └── log
├── modules
│   ├── calibration
│   ├── canbus
│   ├── common
│   ├── contrib
│   ├── control
│   ├── data
│   ├── dreamview
│   ├── drivers
│   ├── guardian
│   ├── localization
│   ├── map
│   ├── monitor
│   ├── perception
│   ├── planning
│   ├── prediction
│   ├── routing
│   ├── third_party_perception
│   ├── tools
│   ├── transform
│   └── v2x

可以说非常清晰,Cyber是框架,也就是调度的东西,而所有的节点现在变成了modules,也就是模块,这真正的实现了模块化啊!!上面那个cyber_tests是我自己加的测试包,你也可以把他当成modules,后面我会告诉大家如何在既有的apollo3.5上,添加自己的节点。

首先我们看看,一个modules,也就是一个模块,他包含了哪些东西呢,比如我们要写一个detection模块,它至少应该这么一些文件夹:

components/
proto/
dag/

这里components其实就是你的节点,在cyber里面全部可以看作是components。proto是你的消息,其实很简单。我们来定义一个消息看看:

syntax = "proto2";
package apollo.cyber_tests.detection.proto;
message BBox{
optional uint64 x=1;
optional uint64 y=2;
optional uint64 w=3;
optional uint64 h=4;
}
message BBoxObject{
optional BBox bbox=1;
optional uint64 cls_id=5;
optional string cls_str=6;
optional float prob=7;
}
message Chatter {
optional uint64 timestamp = 1;
optional uint64 lidar_timestamp = 2;
optional uint64 seq = 3;
optional bytes content = 4;
}

这是一个简单的proto,其中比较重要的是关于包名的命名, 比较推荐的写法是,从根目录开始写,使用绝对路径。然后你的为你的proto写一个BUILD文件,当然如果你有多个proto,只写一个BUILD也是可以的:

package(default_visibility = ["//visibility:public"])
cc_proto_library(
name = "detection_cc_proto",
deps = [
":detection_proto",
],
)
proto_library(
name = "detection_proto",
srcs = [
"bbox_array.proto",
],
)

这里的名字没啥好说的,你实现什么功能,就用什么名字,然后添加你依赖的proto文件。

最后重点来了,我要写一个很简单的publisher应该怎么写呢?

/**
*
* simple component based on Cyber RT framework
* */
#include "cyber/cyber.h"
#include "cyber/time/rate.h"
#include "cyber/time/time.h"
#include "cyber_tests/detection/proto/bbox_array.pb.h"
using apollo::cyber::Rate;
using apollo::cyber::Time;
using apollo::cyber_tests::detection::proto::Chatter;
int main(int argc, char *argv[]) {
apollo::cyber::Init(argv[0]);
auto publisher_node = apollo::cyber::CreateNode("publisher");
auto publisher = publisher_node->CreateWriter<Chatter>("channel/chatter");
Rate rate(1.0);
while(apollo::cyber::OK()){
/* code */
static uint64_t seq=0;
auto msg = std::make_shared<Chatter>();
seq++;
msg->set_timestamp(Time::Now().ToNanosecond());
msg->set_content("hello! from cyber publisher!.");
msg->set_seq(seq);
publisher->Write(msg);
AINFO << "published a message.";
rate.Sleep();
}
return 0;
}

这个其实很简单了,请注意这么几点:

  • 这里的node就是一个进程,这个进程可能需要很多个发布者,或者说是写入者,那都在CreateWriter里面,相应的,可以设置话题;
  • 其他的rate loop和ros一样。发布消息在cyber里面是写入。

OK,然后你还需要为你的component写BUILD文件:

load("//tools:cpplint.bzl", "cpplint")
package(default_visibility = ["//visibility:public"])
cc_binary(
name = "publisher",
srcs = ["publisher.cc"],
deps = [
"//cyber",
"//cyber_tests/detection/proto:detection_cc_proto",
],
)

然后编译!

bazel build cyber_tests/detection:publisher

看到在dokcer里面输出:

jintain@in_dev_docker:/apollo$ bazel build cyber_tests/detection:publisher
INFO: Reading 'startup' options from /apollo/tools/bazel.rc: --batch_cpu_scheduling --host_jvm_args=-XX:-UseParallelGC
INFO: (02-13 09:58:20.190) Found 1 target...
Target //cyber_tests/detection:publisher up-to-date:
bazel-bin/cyber_tests/detection/publisher
INFO: (02-13 09:58:25.341) Elapsed time: 6.179s, Critical Path: 4.94s
jintain@in_dev_docker:/apollo$ ./bazel-bin/cyber_tests/detection/publisher
WARNING: Logging before InitGoogleLogging() is written to STDERR
I0213 17:58:57.269748 22073 global_data.cc:153] [publisher] host ip: 192.168.113.248

大工搞成!!此时你已经使用Cyber框架实现了一个自己从component,并且实现了话题的写入。关于Cyber我们还有很多可以探索的东西,让我们深入这个自动驾驶系统来打造属于自己的自动驾驶软件设施!!