オートパイロットプラットフォームアポロ5.5読書ノート:サイバーRTタスクのスケジューリング

序文

サイバーRT 3.5で導入されたアポロ自動操縦システムは、ベースのROSの前にバリエーションを置き換えます。サイバーRTそのスケジューリングシステムの主要な機能。個人Anquan江に関連した自動操縦するので、それをリアルタイムことを強調しています。従来のロボットシステムは、この目的のために設計されていない、リアルタイムの要件を満たすことは困難です。非同期タスクの多くは、システム内に存在し、実行して、グラブする許可されている場合、システムは大きな不確実性のものであろう。一方、開発者はコントロールとしてシーンを結合することを可能にする上でタスク実行システムの確実性を高めるために、サイバーRTは、スケジューリングを行うために、ユーザーモードでは、コルーチン(コルーチン)を導入し、回避カーネルスケジューリングタスクは、不確実性を導入し、他のカーネルモード切り替えのオーバーヘッドによって引き起こされる-一方では避けユーザーモードに。春祭りは、昨年は何か書いコルーチンサイバーRT(コルーチン):自動操縦プラットフォームアポロ3.5読書ノートコルーチンサイバーRT下に簡単なチャットメカニズムを。春祭りそのタスクベースのスケジューリング機構について次の、話に今年。

私たちは、自動操縦システムプロセスを認識、意思決定、3つの大きな部分の実装に分けることができます知っています。車両の外部環境を感知するセンサから、ブレーキ、アクセル及び方向を制御するためには、計算モジュールの一連通過します。そこデータモジュールとの間の信頼関係があり、したがって、図のトポロジーとして表すことができます。サイバーRTパイプライン処理、一般的なアルゴリズム・モジュールに対応しますComponent処理アルゴリズム後のモジュールへのデータの流れ、最終的な出力。Component含むNode、図のノードに対応する計算。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(振付)戦略:これは、2つのスケジューリング方法に対応する2つの継承クラスを有しています。二人は元の拡張とみなすことができる相互排他的な関係ではありません。それらの導入および例は、公式ドキュメントを参照してサイバーRTスケジューラスケジューリングポリシーの設定ファイルは、で定義されたプロトコルの形式のファイルをいるProtobuf cyber/protoディレクトリ:scheduler_conf.protoclassic_conf.protochoreography_conf.protoでポリシー設定ファイルのスケジュールcyber/confディレクトリ。上記の場合はmkz_close_loop.pb.txt2つのプロセス・グループ:compute_schedとcontrol_sched、スケジューリングポリシーに応じて、2つのバージョンが用意されています。

グループ\ポリシー クラシック 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スケジューリングシステムは確実性を向上させるために備えられています。ファイングレインCoおよびCPUベースのスケジューリングの割り当ては、自動操縦システムのサービス・モジュールの特性に応じて、開発者はそれらを統合します。同時に、我々はいくつかの点に注意を払う必要があります。

  • タスクは、発送には単位をコルーチンが、カーネルのため、スケジュールはまだスレッドです。カーネルは、リアルタイムでないのであれば、それはシステム全体がリアルタイムで決定論を保証することはできません、バケツ理論のようでした。基本的なアーキテクチャ図は、一般的にRTOSの理由を描く理由でもあります。
  • 明示的にコントロールを組み込むためにサイバーRTインタフェースを使用する必要があります。コントロールのネイティブスレッドから裸。ネイティブスレッドからの理論とは、設定した場合、優先度の高い既存の配置、破壊の確実性を妨害することがあります。
  • フレームワークは、スケジューリングメカニズムのより大きな確実性を提供しますが、どのように確実に配置するためには、完了するまでに、開発者が必要とされます。私は、自動または半自動レイアウト生成戦略は進化の重要な方向であると考えています。
公開された211元の記事 ウォンの賞賛438 ビュー148万+

おすすめ

転載: blog.csdn.net/ariesjzj/article/details/104087518