【Apollo学习笔记】—— Cyber RT之调度

在这里插入图片描述

前言

本文是对Cyber RT的学习记录,文章可能存在不严谨、不完善、有缺漏的部分,还请大家多多指出。
课程地址: https://apollo.baidu.com/community/course/outline/329?activeId=10200
更多还请参考:
[1] Apollo星火计划学习笔记——第三讲(Apollo Cyber RT 模块详解与实战)https://blog.csdn.net/sinat_52032317/article/details/126924375
[2] 【Apollo星火计划】—— Cyber基础概念|通信机制
https://blog.csdn.net/sinat_52032317/article/details/131878429?spm=1001.2014.3001.5501
[3] 第一章:Cyber RT基础入门与实践https://apollo.baidu.com/community/article/1093
[4] 第二章:Cyber RT通信机制解析与实践https://apollo.baidu.com/community/article/1094
[5] 第三章:Component组件认知与实践https://apollo.baidu.com/community/article/1103
[6] 第四章:Cyber RT之调度简介与实践https://apollo.baidu.com/community/article/1106

相关代码整理

链接: https://pan.baidu.com/s/1ENgXE4yQ1v4nJRjcfZtd8w?pwd=ht4c 提取码: ht4c

调度介绍

操作系统进行调度的目的是为了最大化利用系统资源(特别是CPU资源)。调度往往是对有限资源的妥协,本质是为了:效率最大化,兼顾公平。通过调度,可以减少任务的等待时间。

任务耗时长的放在前面执行会增加任务等待时间。因此最优的调度策略诞生了,它就是最短时间优先,每次最短时间的任务优先执行,这样可以保证任务总等待时间最短。

但此时,若一直有小任务被创建,被优先执行,耗时长的任务就会一直得不到执行,一直处于饥饿状态。如此便造成CPU 利用的不公平

同时还存在一个问题:任务的执行时间通常是不可知的

以上的问题引入新的方法:时间片轮转。交替执行各个任务,每个任务分配一小段CPU时间,时间用尽则退出,让给其它任务使用。
在这里插入图片描述

时间片轮转是一种CPU调度算法,其基本思想是将CPU时间分为若干个时间片,每个进程占用一个时间片,当时间片用完后,该进程被挂起,等待下一次到来。优点平均分配CPU时间,降低了进程等待时间缺点时间片设置过小会导致进程频繁切换降低CPU利用率和系统吞吐量时间片设置过大会导致进程响应时间过长

常见的时间片轮转算法包括固定时间片动态时间片。固定时间片是指每个进程占用的时间片长度相同,且在调度前预先设定好,如每个时间片长度为10ms;动态时间片则是根据进程是否需要更多时间来动态分配时间片长度。

Cyber RT的改进

实时操作系统

  • 实时操作系统,通过给linux打实时补丁,支持抢占。
  • 中断绑定,摄像头,激光雷达,串口等外设需要不停的处理中断,因此可以绑定中断处理程序在一个核上。

实时操作系统(RTOS)是一种专门设计用于实时应用的操作系统。它的优点和缺点如下所述:

优点

  1. 响应时间可控:实时操作系统能够保证任务的响应时间,使得实时应用能够在特定的时间要求下进行操作。这对于需要高度可靠性和确定性的应用非常重要,如航空航天、工业自动化等。
  2. 任务调度优化:RTOS具有灵活的任务调度算法,以确保关键任务得到及时处理,并避免资源的浪费。它可以根据任务的优先级和时间约束进行任务调度,提高系统的效率和性能。
  3. 可靠性和稳定性:实时操作系统经过精心设计和测试,以确保系统的可靠性和稳定性。它具有容错机制,能够处理错误和异常情况,保证系统的稳定运行。

缺点

  1. 复杂度高:实时操作系统的设计和开发比较复杂,需要考虑各种任务的调度、同步和通信等问题。这增加了系统开发的难度和成本。
  2. 资源占用:实时操作系统需要占用一定的系统资源,如内存、处理器时间等。对于资源有限的嵌入式系统来说,这可能会成为一个问题。
  3. 可扩展性差:一些实时操作系统在设计之初就考虑了特定的应用需求,因此它们的可扩展性可能较差。在面对不同应用需求时,可能需要进行定制或者选择其他RTOS。

总体来说,实时操作系统在处理实时应用方面具有很多优点,但也需要根据具体需求和系统环境来评估其是否适用。

中断绑定是一种将中断与对应处理程序绑定的机制,它的作用是使操作系统能够响应硬件设备和用户程序发出的中断请求,并执行相应的操作

中断绑定的优点包括:

  1. 提高系统的响应速度:操作系统可以快速响应硬件设备和用户程序发出的中断请求,减少了系统的响应时间。
  2. 提高系统的可靠性:通过中断绑定机制,可以确保中断请求被正确的分配到处理程序中,减少了系统发生故障的可能性。
  3. 提高系统的灵活性:不同的硬件设备和用户程序需要不同的中断处理程序,在执行中断绑定的过程中,可以灵活地为不同的中断请求分配不同的处理程序。

中断绑定的缺点包括:

  1. 中断响应的开销:中断绑定需要一定的时间开销,因为每次中断请求都需要查找相应的处理程序。
  2. 处理程序的占用资源:中断处理程序需要占用系统资源,当系统中同时存在多个中断请求时,可能会导致资源的竞争和冲突。
  3. 难以调试:如果中断绑定发生错误,很难进行调试,因为中断处理程序的执行过程很难跟踪和调试。

资源限制&优先级

Cgroup是 Linux 内核的一个特性,用于限制、记录和隔离一组进程的资源使用(CPU、内存、磁盘 I/O、网络等)。Cgroup 具有以下特性:

  • 资源限制 —— 您可以配置 cgroup,从而限制进程可以对特定资源(例如内存或 CPU)的使用量。
  • 优先级 —— 当资源发生冲突时,您可以控制一个进程相比另一个 cgroup 中的进程可以使用的资源量(CPU、磁盘或网络)。
  • 记录 —— 在 cgroup 级别监控和报告资源限制。
  • 控制 —— 您可以使用单个命令更改 cgroup 中所有进程的状态(冻结、停止或重新启动)。
pthread_setaffinity_np

    choreography_conf {
    
    
        choreography_processor_num: 8
        choreography_affinity: "range"
        choreography_cpuset: "0-7" # bind CPU cores 核
        choreography_processor_policy: "SCHED_FIFO" # policy: SCHED_OTHER,SCHED_RR,SCHED_FIFO 设置策略

协程

协程。用户态的线程,由用户控制切换。协程的定义可以参考go语言中的GMP模型。

协程(Coroutine)是一种轻量级的线程,它可以在同一个线程内实现多个任务的切换执行,从而达到高效利用资源的目的。协程常用于高并发、网络编程、异步处理等场景。

M,Machine,表示系统级线程,goroutine 是跑在 M 上的。P,processor,是 goroutine 执行所必须的上下文环境,可以理解为协程处理器,是用来执行 goroutine 的。

协程相对线程的优势

  • 系统消耗少。线程的切换用户态->中断->内核态,协程的切换只在用户态完成,减少了系统开销。
  • 轻量。协程占用资源比线程少,一个线程往往消耗几兆的内存。

自动驾驶系统需要不断地进行数据采集、处理和决策,同时还需要与车辆的各种硬件进行交互。这些操作通常会涉及到大量的 I/O 操作,包括网络通信、传感器数据采集和控制命令发送等,这些操作都可能会阻塞程序的执行。如果使用传统的线程或进程来处理这些操作,就会导致程序的性能不稳定,并可能出现死锁等问题。

协程是一种轻量级的多任务解决方案,能够避免多线程或多进程的开销和锁竞争等问题。在自动驾驶中,协程可以用于异步处理各种 I/O 操作,例如通过异步网络库处理网络通信、通过异步文件读写库处理文件读取等。由于协程能够在不同的任务之间进行切换,因此可以避免程序在等待 I/O 操作时被阻塞,从而提高了程序的性能和并发性能。同时,协程还能够提供更好的代码可读性和可维护性,因为它们能够使用更简洁和易于理解的代码来处理异步操作。综上所述,引入协程可以减少任务切换带来的开销,提高系统的响应速度和效率,同时也可以简化代码结构,提高代码的可读性和可维护性

Cyber RT调度策略

对应到GMP模型,Cyber的每个任务都被视为一个协程,协程在线程上运行,并且可以设置协程的优先级。协程通过一个多优先级队列来管理,每次从优先级最高的协程开始执行

任务窃取

为了提高效率,空闲的Processor会从饱和的Processor上偷取一半的任务过来执行,从而提高CPU利用率。

两种任务类型

componen组件

组件是Apollo中的最小执行单元,每个组件对应某一项特定的任务,例如定位、控制。多个组件联合起来可以实现更加复杂的功能,例如感知。Apollo一共有2种类型的组件:消息触发型和定时触发型。
PS:关于组件,可以参考这篇博客【Apollo学习笔记】—— Cyber RT之创建组件

自定义任务

如果在程序中想启动新的任务并发处理,可以使用cyber::Async接口,创建的任务会在协程池中由cyber统一调度管理。

class Foo {
    
    
 public:
  void RunOnce() {
    
    
    auto res = Async(&Foo::Task, this, 10);
    EXPECT_EQ(res.get(), 10);
  }

  uint32_t Task(const uint32_t& input) {
    
     return input; }
};

Cyber调度实践

实验步骤:

  1. 修改cyber/conf/example_sched_choreography.conf中的配置
  2. 启动./bazel-bin/cyber/examples/common_component_example/channel_prediction_writer发送消息
  3. 启动cyber_launch start cyber/examples/common_component_example/common.launch,其中包括4个组件,用于消费消息。

配置文件

cyber/conf目录下修改cyber/conf/example_sched_choreography.conf中的配置,通过更改配置可以设置任务的优先级,绑定的核心等。
文件内容为:

scheduler_conf {
    
    
    policy: "choreography"
    process_level_cpuset: "0"  # all threads in the process are on the cpuset
    threads: [
        {
    
    
            name: "lidar"
            cpuset: "1"
            policy: "SCHED_RR" # policy: SCHED_OTHER,SCHED_RR,SCHED_FIFO
            prio: 10
        }, {
    
    
            name: "shm"
            cpuset: "2"
            policy: "SCHED_FIFO"
            prio: 10
        }
    ]
    choreography_conf {
    
    
        choreography_processor_num: 4
        choreography_affinity: "range"
        choreography_cpuset: "0"
        choreography_processor_policy: "SCHED_RR" # 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
            }
        ]
    }
}

prio代表优先级,数字越大,优先级越大。

DAG文件

分别创建common0.dag、common1.dag、common2.dag、common3.dag四个文件。注意修改相应名称。

# Define all coms in DAG streaming.
    module_config {
    
    
    module_library : "/opt/apollo/neo/packages/test-dev/latest/lib/libcommon_component.so"
    components {
    
    
        class_name : "CommonComponentCroutine"
        config {
    
    
            name : "common0"
            readers {
    
    
                channel: "/apollo/driver"
            }
        }
      }
    }

cyber_launch文件

供参考

<cyber>
    <module>
        <name>common</name>
        <dag_conf>/opt/apollo/neo/packages/test-dev/latest/test_croutine/conf/common0.dag</dag_conf>
        <dag_conf>/opt/apollo/neo/packages/test-dev/latest/test_croutine/conf/common1.dag</dag_conf>
        <dag_conf>/opt/apollo/neo/packages/test-dev/latest/test_croutine/conf/common2.dag</dag_conf>
        <dag_conf>/opt/apollo/neo/packages/test-dev/latest/test_croutine/conf/common3.dag</dag_conf>
        <process_name>example_sched_choreography</process_name>
    </module>
</cyber>

<process_name>example_sched_choreography</process_name>这要写对

component组件

common_component.h

#include <memory>
#include "cyber/component/component.h"
#include "test/proto/examples.pb.h"

using apollo::cyber::Component;
using apollo::cyber::ComponentBase;
using apollo::cyber::test::proto::Driver;

class CommonComponentCroutine : public Component<Driver> {
    
    
 public:
  bool Init() override;
  bool Proc(const std::shared_ptr<Driver>& msg0) override;
};
CYBER_REGISTER_COMPONENT(CommonComponentCroutine)

common_component.cc

#include "test/test_croutine/common_component.h"
#include <bits/stdc++.h>
#include "cyber/croutine/croutine.h"
#include "cyber/time/rate.h"


using apollo::cyber::croutine::CRoutine;
using apollo::cyber::Rate;

bool CommonComponentCroutine::Init() {
    
    
  AINFO << "Commontest component init";
  return true;
}

bool CommonComponentCroutine::Proc(const std::shared_ptr<Driver>& msg0) {
    
    
  if (CRoutine::GetCurrentRoutine() != nullptr) {
    
    
    AINFO << "Name: " << CRoutine::GetCurrentRoutine()->name();
  }

  calculate task
  int i = 10000000;
  while (i) {
    
    
    log(i);
    --i;
  }
  // Rate rate(5.0);
  // rate.Sleep();
  AINFO << "Start common component Proc [" << msg0->msg_id() << "]";
  return true;
}

BUILD文件

供参考

load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
load("//tools/install:install.bzl", "install", "install_src_files")
load("//tools:cpplint.bzl", "cpplint")

package(default_visibility = ["//visibility:public"])

cc_binary( 
    name = "libcommon_component.so",
    linkshared = True,
    linkstatic = True,
    deps = [":common_component_lib"],
)

cc_library(
    name = "common_component_lib",
    srcs = ["common_component.cc"],
    hdrs = ["common_component.h"],
    visibility = ["//visibility:private"],
    deps = [
        "//cyber",
        "//test/proto:examples_cc_proto",
    ],
    alwayslink = True,
)

filegroup(
    name = "conf",
    srcs = [
        ":common0.dag",
        ":common1.dag",
        ":common2.dag",
        ":common3.dag",
        ":demo.launch",
    ],
)

install(
    name = "install",
    data = [
        ":conf",
    ],
    library_dest = "test/lib",
    data_dest = "test/test_croutine/conf",
    targets = [
        ":libcommon_component.so",
    ],
)

install_src_files(
    name = "install_src",
    src_dir = ["."],
    dest = "test/src/test_croutine",
    filter = "*",
)

问题

出现以下问题,导致无法加载.conf文件,采取了默认分配的方式。

E0802 22:03:10.314543 1670469 file.cc:106] [mainboard]Failed to parse file /opt/apollo/neo/packages/cyber/conf/example_sched_choreography.conf as binary proto

报错段

在这里插入图片描述
默认分配
在这里插入图片描述
结果

在这里插入图片描述
在这里插入图片描述

PS:该问题是在本地运行时出现的。在云实验平台上做正常。
已解决:配置文件有误,末尾多了一个,
结果:
在这里插入图片描述

参考

[1] 百度Apollo系统学习-Cyber RT 调度
https://blog.csdn.net/qq_25762163/article/details/103763525
[2] 技术文档丨Cyber RT车载系统CPU资源调度和数据与协程任务驱动模型https://developer.baidu.com/article/detail.html?id=290451

猜你喜欢

转载自blog.csdn.net/sinat_52032317/article/details/132031010