关于Apollo Cyber RT 中Component如何绑定到CPU Group、具体线程及优先级队列

Apollo Scheduler 模块的工作原理如下图所示: 

 原图引用链接:【架构分析】Apollo CyberRT Framework分析 - Scheduler调度器_HaoBBNuanMM的博客-CSDN博客_cyberrt scheduler

 以SchedulerClassic为例,在SchedulerClassic构造函数中会读取调度配置文件信息并将配置文件中的groups条目下信息记录到classic_conf_成员变量内。

在构造函数结尾调用CreateProcessor()函数创建processor_num 个Processor,CreateProcessor()函数代码如下:

void SchedulerClassic::CreateProcessor() {
  for (auto& group : classic_conf_.groups()) {
    auto& group_name = group.name();
    auto proc_num = group.processor_num();
    if (task_pool_size_ == 0) {
      task_pool_size_ = proc_num;
    }

    auto& affinity = group.affinity();
    auto& processor_policy = group.processor_policy();
    auto processor_prio = group.processor_prio();
    std::vector<int> cpuset;
    ParseCpuset(group.cpuset(), &cpuset);

    for (uint32_t i = 0; i < proc_num; i++) {
      auto ctx = std::make_shared<ClassicContext>(group_name);
      pctxs_.emplace_back(ctx);

      auto proc = std::make_shared<Processor>();
      proc->BindContext(ctx);
      SetSchedAffinity(proc->Thread(), cpuset, affinity, i);
      SetSchedPolicy(proc->Thread(), processor_policy, processor_prio,
                     proc->Tid());
      processors_.emplace_back(proc);
    }
  }
}

Apollo中的Processor实际对应于Linux中的thread,CreateProcessor()首先读取要创建Processor(也就是thread)的优先级及绑定的CPU核的配置信息,然后在for循环中依次创建proc_num个thread并将其与ClassicContext和CPU核的绑定。

绑定完成的后每个thread将不断从ClassicContext中获取可执行的Component,调用Component中的Proc()函数,实现Component运行。

另一方面,在创建Component时,Component初始化过程中会调用SchedulerClassic中DispatchTask()函数将不同的Component分配到ClassicContext中,DispatchTask()函数代码如下:

bool SchedulerClassic::DispatchTask(const std::shared_ptr<CRoutine>& cr) {
  // we use multi-key mutex to prevent race condition
  // when del && add cr with same crid
  MutexWrapper* wrapper = nullptr;
  if (!id_map_mutex_.Get(cr->id(), &wrapper)) {
    {
      std::lock_guard<std::mutex> wl_lg(cr_wl_mtx_);
      if (!id_map_mutex_.Get(cr->id(), &wrapper)) {
        wrapper = new MutexWrapper();
        id_map_mutex_.Set(cr->id(), wrapper);
      }
    }
  }
  std::lock_guard<std::mutex> lg(wrapper->Mutex());

  {
    WriteLockGuard<AtomicRWLock> lk(id_cr_lock_);
    if (id_cr_.find(cr->id()) != id_cr_.end()) {
      return false;
    }
    id_cr_[cr->id()] = cr;
  }

  if (cr_confs_.find(cr->name()) != cr_confs_.end()) {
    ClassicTask task = cr_confs_[cr->name()];
    cr->set_priority(task.prio());
    cr->set_group_name(task.group_name());
  } else {
    // croutine that not exist in conf
    cr->set_group_name(classic_conf_.groups(0).name());
  }

  if (cr->priority() >= MAX_PRIO) {
    AWARN << cr->name() << " prio is greater than MAX_PRIO[ << " << MAX_PRIO
          << "].";
    cr->set_priority(MAX_PRIO - 1);
  }

  // Enqueue task.
  {
    WriteLockGuard<AtomicRWLock> lk(
        ClassicContext::rq_locks_[cr->group_name()].at(cr->priority()));
    ClassicContext::cr_group_[cr->group_name()]
        .at(cr->priority())
        .emplace_back(cr);
  }

  ClassicContext::Notify(cr->group_name());
  return true;
}

上述代码中最核心的部分其实只有一句:

    ClassicContext::cr_group_[cr->group_name()]
        .at(cr->priority())
        .emplace_back(cr);

上述代码实现了根据group_name及Component对应的task(调度配置文件中的tasks条目)的优先级放入到ClassicContext中对应的cr_group_变量内。

从代码上看,整个功能实现比较怪异,直接在一个类中使用的一个类的成员变量,不太符合常规的软件设计原则。另一方面,SchedulerClassic的CreateProcessor()函数是如下创建ClassicContext并绑定到thread的:

    for (uint32_t i = 0; i < proc_num; i++) {
      auto ctx = std::make_shared<ClassicContext>(group_name);
      pctxs_.emplace_back(ctx);

      auto proc = std::make_shared<Processor>();
      proc->BindContext(ctx);
      SetSchedAffinity(proc->Thread(), cpuset, affinity, i);
      SetSchedPolicy(proc->Thread(), processor_policy, processor_prio,
                     proc->Tid());
      processors_.emplace_back(proc);
    }

从代码上看似乎是为每一个thead创建了一个ClassicContext,如果是这样的话将不太好理解Component是如何选择thread对应的ClassicContext的。

答案就在ClassicContext的类定义及构造函数中:

ClassicContext定义了几个静态成员变量:

  alignas(CACHELINE_SIZE) static CR_GROUP cr_group_;
  alignas(CACHELINE_SIZE) static RQ_LOCK_GROUP rq_locks_;
  alignas(CACHELINE_SIZE) static GRP_WQ_CV cv_wq_;
  alignas(CACHELINE_SIZE) static GRP_WQ_MUTEX mtx_wq_;
  alignas(CACHELINE_SIZE) static NOTIFY_GRP notify_grp_;

并在构造函数中调用InitGroup()函数实现如下功能:

void ClassicContext::InitGroup(const std::string& group_name) {
  multi_pri_rq_ = &cr_group_[group_name];
  lq_ = &rq_locks_[group_name];
  mtx_wrapper_ = &mtx_wq_[group_name];
  cw_ = &cv_wq_[group_name];
  notify_grp_[group_name] = 0;
  current_grp = group_name;
}

我们可以看出核心问题是cr_group_是静态成员变量,(这点其实从ClassicContext::cr_group_这种使用方法也可推断出来),所以本质上是所有的ClassicContext类对象共享CR_GROUP cr_group_这个静态变量,所以在执行

std::make_shared<ClassicContext>(group_name)

时,只要group_name不变,就可以得到相同的ClassicContext::cr_group_[cr->group_name()],从而使thread绑定到同一个MULTI_PRIO_QUEUE中。

至此,我们可以总结出:Component对应的task根据调度配置文件的group name分配到MULTI_PRIO_QUEUE中;Processor(即thread)根据group name绑定到相同group name的MULTI_PRIO_QUEUE,同时Processor根据调度配置文件中的优先级及内核绑定配置配置线程优先级并绑定到特定内核。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值