자동 모드 플랫폼 아폴로 5.5 독서 노트 : 사이버 RT 작업 스케줄링

머리말

사이버 RT 3.5에 도입 아폴로 자동 조종 시스템은 기반 ROS 전에 변종을 교체했다. 사이버 RT의 스케줄링 시스템의 주요 기능입니다. 개인 Anquan 지앙과 관련된 자동 조종하기 때문에, 그래서 실시간 강조한다. 기존의 로봇 시스템은이 목적을 위해 설계되지 않은, 실시간 요구 사항을 충족하기가 어렵습니다. 시스템의 비동기 작업이 많이 있습니다 실행하고 잡아 수있는 경우, 시스템은 불확실성이 될 것입니다. 한편으로는 개발자가 컨트롤로 장면을 결합 할 수 있습니다에 작업 실행 시스템의 확실성을 높이기 위해, 사이버 RT는, 스케줄링을 수행하는 사용자 모드에서, 코 루틴 (코 루틴)을 도입, 피할 커널 스케줄링 작업은 불확실성을 소개하고 다른 않도록 사용자 모드로 한 손 - 커널에 의한 오버 헤드 모드 전환. 봄 축제는 작년에 뭔가 쓴 자동 조종 플랫폼 아폴로 3.5 독서 노트 : 코 루틴의 사이버 RT (코 루틴) 코 루틴의 사이버 RT에서 간단한 채팅 메커니즘. 봄 축제의 작업 기반 스케줄링 메커니즘에 대한 다음, 대화에 올해.

우리는 자동 조종 시스템 프로세스가 인식, 의사 결정, 세 개의 큰 조각의 구현으로 나눌 수 있습니다 알고 있습니다. 브레이크, 액셀러레이터와 방향을 제어 차량의 외부 환경을 감지하는 센서로부터, 계산 모듈 일련 통과한다. 데이터가 모듈 사이의 상호 신뢰가 있고, 따라서도 1의 위상으로 표현 될 수있다. 사이버 RT 파이프 라인 프로세싱은, 하나의 일반적인 알고리즘 모듈에 대응 Component. 가공하는 알고리즘 후 모듈로 데이터의 흐름, 최종 출력. Component포함하는 Node도 1의 노드들에 대응하는 계산. Node간 통신 Channel에지에 대응도를 산출한다. Channel각각 양측 ReaderWriter, 읽기, 데이터를 작성합니다. 다음 다이어그램은 다음과 같습니다
그림 삽입 설명 여기

이러한 계산 섹션에서 아폴로는 노드간에 종속성하고는 파일 DAG에 의해 설명되어 있습니다. 예를 들어, modules/dreamview/conf/hmi_modes/mkz_close_loop.pb.txt전체 시스템 서브 DAG 해당 파일의 열, 각 파일 및 DAG가 복수의 구성 요소가있을 것이다.

   1 cyber_modules {
   2   key: "Computer"
   3   value: {
   4     dag_files: "/apollo/modules/drivers/camera/dag/camera_no_compress.dag"
   5     dag_files: "/apollo/modules/drivers/gnss/dag/gnss.dag"
   6     dag_files: "/apollo/modules/drivers/radar/conti_radar/dag/conti_radar.dag"
   7     dag_files: "/apollo/modules/drivers/velodyne/dag/velodyne.dag"
   8     dag_files: "/apollo/modules/localization/dag/dag_streaming_msf_localization.dag"
   9     dag_files: "/apollo/modules/perception/production/dag/dag_streaming_perception.dag"
  10     dag_files: "/apollo/modules/perception/production/dag/dag_streaming_perception_trafficlights.dag"
  11     dag_files: "/apollo/modules/planning/dag/planning.dag"
  12     dag_files: "/apollo/modules/prediction/dag/prediction.dag"
  13     dag_files: "/apollo/modules/storytelling/dag/storytelling.dag"
  14     dag_files: "/apollo/modules/routing/dag/routing.dag"
  15     dag_files: "/apollo/modules/transform/dag/static_transform.dag"
  16     process_group: "compute_sched"
  17   }
  18 }
  19 cyber_modules {
  20   key: "Controller"
  21   value: {
  22     dag_files: "/apollo/modules/canbus/dag/canbus.dag"
  23     dag_files: "/apollo/modules/control/dag/control.dag"
  24     dag_files: "/apollo/modules/guardian/dag/guardian.dag"
  25     process_group: "control_sched"
  26   }
  27 }
...

그들이 볼 수 그려 경우보다 복잡한 계산도 (안 전체 그림뿐만 아니라,이 숫자보다 실제 복잡한)입니다 :
그림 삽입 설명 여기
질문했다 그래서. 이 실시간 결정 시스템을 달성하기 위해, 실행 시간 제약의 다양성을 충족 할 수 있도록 전체 계산 도표를 예약하는 방법 큰 도전이다. 본 논문에서는 사이버 RT 스케줄링 시스템의 소스에서 아폴로 v5.5.0 모양의 최신 버전에서.

분석을 달성

주로에서 구현 일정, cyber/scheduler디렉토리. 클래스의 핵심이다 Scheduler. 클래식 (기본) 전략 Choreophgray (안무) 전략 : 두 개의 스케줄링 방법에 대응하는 두 개의 클래스를 상속있다. 두 사람은 서로 전자의 연장으로 볼 수있다 배타적 관계 없다. 이들의 소개와 예제는 공식 문서를 참조 사이버 RT 스케줄러 . 예약 정책 구성 파일 protobuf이에 프로토콜 형식의 파일을 정의 cyber/proto디렉토리 : scheduler_conf.proto, classic_conf.protochoreography_conf.proto. 에서 정책 구성 파일을 예약 cyber/conf디렉토리. 위의 내용은 mkz_close_loop.pb.txt두 가지 프로세스 그룹 : compute_sched 및 control_sched, 스케줄링 정책에 따라 두 가지 버전에서 사용할 수 있습니다 :

그룹 \ 정책 권위 있는 Choreophgraphy
계산 compute_sched_classic.conf compute_sched_choreography.conf
제어 control_sched_classic.conf control_sched_choreography.conf

Scheduler하나의 실시 예이며, 이는 Instance()제 호출 초기화 스케줄링 방법에 대한 구성 파일을로드한다. 초기화 유형을 지정하는 구성 파일이 생성됩니다 SchedulerClassic또는 SchedulerChoreography개체를. 다음 그림에서 여러 관련 핵심 클래스의 관계 :그림 삽입 설명 여기

고전적인 스케줄링 정책에서 첫 모습. 작업 스케줄링 구성을 계산하기 위해 compute_sched_classic.conf다음과 같이 아마 긴 :

scheduler_conf {
  policy: "classic"
  classic_conf {
    groups: [
      {
        name: "compute"
        processor_num: 16
        affinity: "range"
        cpuset: "0-7,16-23"
        processor_policy: "SCHED_OTHER"
        processor_prio: 0
        tasks: [
          {
            name: "velodyne_16_front_center_convert"
            prio: 10
          },
          {
            name: "velodyne_16_rear_left_convert"
            prio: 10
          },
...

实现类为SchedulerClassic。它的构造函数中首先载入配置文件(如compute_sched.conf,它是compute_sched_classic.conf的链接)。如果读取配置文件失败,会设置默认值。默认值由GlobalData(单例,保存一些全局数据)从配置文件cyber.pb.conf中读入,如默认线程数为16。如果读取成功,会进行相应的初始化:

  1. 将配置文件中的内部线程信息记录到inner_thr_confs_查询表中。当这些指定的线程起来后会调用SetInnerThreadAttr()根据这里的配置设置线程的affinity和priority等属性。
  2. 根据配置设置进程级别的cpuset,这样就指定了该进程中的所有任务都只能在限定的CPU核上运行。
  3. 配置文件中将所有task分为group。一个group包含多个task。这里将task的配置信息放于cr_confs_查询表中。
  4. 根据配置文件中指定的线程个数创建工作线程。虽然Cyber RT引入了协程,但协程的执行仍需以线程为载体。每一个Processor对象对应一个这样的工作线程。这个名称初看有些容易混淆,因为Processor这个词很多时候是特指CPU的,而这里对应一个线程。因为对于这些协程来说,一个线程可以看作一个逻辑上的CPU。如上面配置文件中指定compute这个group中processor_num为16,则会创建16个Processor,记于processors_结构。同时创建相应数量的ClassicContext实例,记于pctxs_结构,并和Processor绑定,它们是Processor运行的上下文。另外,还会根据配置文件调用函数SetSchedAffinity()SetSchedPolicy()设置每个线程的affinity与priority属性。Affinity有两种选择,一种是range,即这个线程可以跑在cpuset指定的任一核;另一种是1to1,即绑定于单个核上。

注意配置文件中有两个优先级,一个是processor_prio,它就是Linux中线程的优先级,即nice值,范围从-20到19,值越低优先级越高,默认值为0;另一个是task的prio,它是Cyber RT中的协程调度时的优先级,共20级,值越高优先越高。调度配置文件中的每个group对应一个多优先级任务队列。这些队列放在cr_group_中。优先级共20级,所以每组有20个队列。而每个线程对应的ClassicContext结构中的multi_pri_rq_指向所在group对应的任务队列。示意图如下:
그림 삽입 설명 여기
以classic调度策略为例,它将相关任务以组为单位与线程以及CPU物理核作了绑定。示意图如下:
그림 삽입 설명 여기

再来看下choreography调度策略。就像前面提到的,它是classic调试策略的扩展,主要差别是它可以将task与线程绑定。因此,使用它需要开发者对系统中各模块有充分的了解。其配置文件大概长这个样子:

scheduler_conf {
  policy: "choreography"

  choreography_conf {
    choreography_processor_num: 8
    choreography_affinity: "range"
    choreography_cpuset: "0-7"

    pool_processor_num: 12
    pool_affinity: "range"
    pool_cpuset: "8-11,16-23"

    tasks: [
      {
        name: "velodyne_128_convert"
        processor: 0
        prio: 11
      },
      {
        name: "velodyne128_compensator"
        processor: 1
        prio: 12
      },
...

可以看到和classic模式很相似,事实上pool开头的那些就是对应classic模式。不同的是增加了choreography开头的那几个属性,它们用于设置专门的线程,并让下面的task可以通过processor属性与这些线程进行绑定。这体现在实现上是与ClassicContext中多优先级任务队列会在一个group的线程间共享不同,每一个ChoreographyContext有一个单独的优先级队列cr_queue_。而在派发任务 DispatchTask()函数中,如果该任务协程所指定的线程在choreography的线程集中,则将之放入该线程对应ChoreographyContext的任务队列中。
그림 삽입 설명 여기

Scheduler中所有的工作线程起来后都会执行Processor::Run()函数:

void Processor::Run() {
  tid_.store(static_cast<int>(syscall(SYS_gettid)));
  AINFO << "processor_tid: " << tid_;
  snap_shot_->processor_id.store(tid_);

  while (cyber_likely(running_.load())) {
    if (cyber_likely(context_ != nullptr)) {
      auto croutine = context_->NextRoutine();
      if (croutine) {
        snap_shot_->execute_start_time.store(cyber::Time::Now().ToNanosecond());
        snap_shot_->routine_name = croutine->name();
        croutine->Resume();
        croutine->Release();
      } else {
        snap_shot_->execute_start_time.store(0);
        context_->Wait();
      }
    } else {
      std::unique_lock<std::mutex> lk(mtx_ctx_);
      cv_ctx_.wait_for(lk, std::chrono::milliseconds(10));
    }
  }
}

它的核心主循环逻辑很简单,就是不断地调用与之绑定的ProcessorContextNextRoutine()函数取得下一个协程,也就是下一个任务。如果没取到,就调用ProcessorContextWait()等待。如果取到了,就调用协程类的Resume()函数继续运行。

接下来看下NextRoutine()函数是如何挑选下一个任务的。ProcessorContext有两个实现类ClassicContextChoreographyContext。就像前面提到的,它们两个的实现由于其任务优先级队列结构不同也略有不同。前者是按优先级从高到低从所在group对应的任务队列multi_pri_rq_中取任务;而后者也是按优先级从高到低的顺序,但是从cr_queue_中取。取到后,需要判断其状态是否为READY,如果是就返回它。对每个协程,对调用UpdateState()函数检查其状态。这个函数中,对于那些之前睡下去的协程,这里判断是否睡够了,睡够了就将状态设为READY。对于那些之前是因为等待IO或者数据而切走的协程,当等待的东西已经就绪了(SetUpdateFlag()来标记更新),就将协程状态设为READY。

我们知道,这里调度的单位是协程,一个协程对应一个任务。那这些任务主要来自于哪里呢?这就需要看下CreateTask()函数主要在哪些地方被调用:

  • Component:我们知道,整个系统中处理数据的计算图由组件构成。它的初始化函数Initialize()中最后会调用CreateTask()创建task。参数包含RoutinFactory对象,它包含该task的执行体和DataVisitor。这个执行体主要调用ComponentProcess()函数,它继而调用继承类可自定义的纯虚函数Proc()DataVisitor用来管理该组件对指定channel的数据访问。它的RegisterNotifyCallback()函数注册回调。该回调在指定channel有数据到来时被调用,它会调用SchedulerNotifyProcessor()函数通知相应的协程来处理。以SChedulerClassic::NotifyProcessor()为例,它先设置相应协程的状态,然后唤醒该协程所在组的线程池中的线程之一。接下去,正常情况下这个被唤醒线程就会去载入这个因数据到来而就绪的协程运行。
  • Reader:用于读channel上的数据。Node::CreateReader()调用NodeChannelImpl::CreateReader()创建指定消息类型的Reader对象。在Reader类的初始化函数Init()中,它会调用CreateTask()创建task。这个task主要干的是在消息来的时候将之放入队列中,同时调用事先注册的处理回调函数(如有)。
  • AsyncTaskManager用于管理一些自定义异步任务的执行。它会维护一个任务队列task_queue_。通过cyber::Async()函数创建一个异步任务就是往这个任务队列中放入一个新的任务。在TaskManager的构造函数中,会通过Scheduler得到线程个数,并创建同样个数的协程任务。这些协程的执行体就是不断地从这个任务队列中拿任务。如果拿不到,它会将状态设为等待数据并将自己切走;如果拿到了就会执行该任务。

结语

자동 운전 안전은 최우선이며, 보안을위한 필요 조건 중 하나는 확실성의 핵심입니다. 의 하나는 사이버 RT 스케줄링 시스템이 확실성을 개선하는 것입니다 있습니다. 세분화 된 공동 및 CPU 기반의 스케줄링 할당, 자동 조종 시스템 서비스 모듈의 특성에 따라 개발자는 그것들을 조율합니다. 동시에, 우리는 몇 가지 사항에주의해야한다 :

  • 작업 파견에 장치를 코 루틴하지만, 커널, 스케줄은 여전히 ​​스레드됩니다. 커널은 실시간없는 그렇다면, 그것은 버킷 이론 같았다, 전체 시스템은 실시간 결정을 보장 할 수 없습니다. 기본 아키텍처 다이어그램은 일반적으로 RTOS의 이유를 그리는 이유이기도하다.
  • 필요는 명시 적으로 제어를 통합하는 사이버 RT 인터페이스를 사용합니다. 컨트롤의 기본 스레드에서 벌거 벗은. 는 A 원시 스레드에서 이론과이 설정 한 경우 높은 우선 순위는 기존의 배열, 파괴의 확실성을 방해 할 수 있습니다.
  • 프레임 워크는 스케줄링 메커니즘의 더 큰 확실성을 제공하지만 방법 확실성을 준비하는 방법을 완료하는 개발자가 필요합니다. 나는 자동 믿거 나 반자동 레이아웃 생성 전략은 진화의 중요한 방향이다.
게시 된 211 개 원래 기사 · 원의 찬양 (438) · 조회수 1,480,000 +

추천

출처blog.csdn.net/ariesjzj/article/details/104087518