夜雨聆风学习资料网

ARTICLE · 1077218

Ros2源码(7)rclcpp::init()

Ros2源码(7)rclcpp::init()
一,背景
    原计划写创建 publisher 的代码分析。但是,publisher 创建的过程有点复杂,而且,创建过程中用到一些 struct 的数据成员,可能是在之前的某些步骤创建的。怎么来的稀里糊涂,那还不如不动。所以,还是从头开始吧。
二,rclcpp::init() 函数原型
代码块 1
RCLCPP_PUBLICvoidinit(  int argc,  char const * const * argv,  const InitOptions & init_options = InitOptions(),  SignalHandlerOptions signal_handler_options = SignalHandlerOptions::All);
    通常情况下,我们的第一行代码,往往是:
代码块 2
rclcpp::init(argc, argv);
    然后,也不知道为啥这么干,反正 demo 啥的都这么写。可能没啥需要的话,都不会去看一下它的函数原型是什么,也就不一定知道它后面还有俩参数。
    接下来,先看一下 InitOptions 的定义:
代码块 3
class InitOptions{public:  /// 省略部分代码  RCLCPP_PUBLIC  explicit InitOptions(      rcl_allocator_t allocator = rcl_get_default_allocator());  /// 省略部分代码private:  /// 省略部分代码  std::unique_ptr<rcl_init_options_t> init_options_;  /// 省略部分代码};
    其成员变量:
代码块 4
std::unique_ptr<rcl_init_options_t> init_options_;
    是用来保存初始化信息的。
    以下是 rcl_init_options_t 结构定义
代码块 5
//// 注意这一行,rcl_init_options_impl_s 的定义在后面////typedef struct rcl_init_options_impl_s rcl_init_options_impl_t;/// Encapsulation of init options and implementation defined init options.typedef struct rcl_init_options_s{  /// Implementation specific pointer.  rcl_init_options_impl_t * impl;} rcl_init_options_t;
代码块 6
struct rcl_init_options_impl_s{  rcl_allocator_t allocator;  rmw_init_options_t rmw_init_options;};
代码块 7
typedef rcutils_allocator_t rcl_allocator_t;typedef struct rcutils_allocator_s{  void * (*allocate)(size_t size, void * state);  void (* deallocate)(void * pointer, void * state);  void * (*reallocate)(void * pointer, size_t size, void * state);  void * (*zero_allocate)(size_t number_of_elements, size_t size_of_element, void * state);  void * state;} rcutils_allocator_t;
    这是一个内存处理器,支持自定义。如果不是自定义的话,ros2 提供了默认实现,实际上就是 malloc,free 等。
代码块 8
typedef struct RMW_PUBLIC_TYPE rmw_init_options_s{  /// Locally (process local) unique ID that represents this init/shutdown cycle.  /**   * This should be set by the caller of `rmw_init()` to a number that is   * unique within this process.   * It is designed to be used with `rcl_init()` and `rcl_get_instance_id()`.   */  uint64_t instance_id;  /// Implementation identifier, used to ensure two different implementations are not being mixed.  const char * implementation_identifier;  /// ROS domain id  size_t domain_id;  /// Security options  rmw_security_options_t security_options;  /// Enable localhost only  rmw_localhost_only_t localhost_only;  /// Enclave, name used to find security artifacts in a sros2 keystore.  char * enclave;  // TODO(wjwwood): replace with rmw_allocator_t when that refactor happens  /// Allocator used during internal allocation of init options, if needed.  rcutils_allocator_t allocator;  /// Implementation defined init options.  /** May be NULL if there are no implementation defined options. */  rmw_init_options_impl_t * impl;} rmw_init_options_t;
    其中 rmw_init_options_t 的数据成员 impl,在 rcl 层是个空实现,留给 rmw 层视情况使用。
    其他数据成员,从名字上来看,它们都是 rmw 层用到的参数,功能从明早上也能猜个大概。我们这里暂时不关注 security 相关的成员。
    上面的几个 struct 展示了 InitOptions::init_options_ 成员的细节。接下来我们看一下 InitOptions 的构造函数。
    因为它的构造函数有一个默认参数,所以,我们先看一下构造函数的原型。
代码块 9    
RCLCPP_PUBLIC  explicit InitOptions(rcl_allocator_t allocator       = rcl_get_default_allocator());
代码块 10
#define rcl_get_default_allocator rcutils_get_default_allocator//////rcutils_allocator_trcutils_get_default_allocator(){  static rcutils_allocator_t default_allocator = {    .allocate = __default_allocate,    .deallocate = __default_deallocate,    .reallocate = __default_reallocate,    .zero_allocate = __default_zero_allocate,    .state = NULL,  };  return default_allocator;}
可以看到,这里提供了默认实现。我们挑其中一个看一下:
代码块 11
static void *__default_allocate(size_t size, void * state){  RCUTILS_CAN_RETURN_WITH_ERROR_OF(NULL);  RCUTILS_UNUSED(state);  return malloc(size);}
    可以看到,提供的默认实现,显示做了安全检查,然后就是调用 malloc。
    这个 state 有什么用,目前还没看出来,看起来不是在 state 上分配内存。也许是给那些非默认实现使用的。
    至此,我们对 InitOptions 的重要数据成员和构造函数的默认参数都已经熟悉了,可以去看一下构造函数做的事情了。
代码块 12
InitOptions::InitOptions(rcl_allocator_t allocator): init_options_(new rcl_init_options_t){  *init_options_ = rcl_get_zero_initialized_init_options();  rcl_ret_t ret = rcl_init_options_init(init_options_.get(),                                         allocator);  if (RCL_RET_OK != ret) {    rclcpp::exceptions::throw_from_rcl_error(ret, "failed to initialize rcl init options");  }}
代码块 13
rcl_init_options_trcl_get_zero_initialized_init_options(void){  return (const rcl_init_options_t) {           .impl = 0,  };  // NOLINT(readability/braces): false positive}
这个函数比较清晰,创建并初始化一下 rcl_init_options_t,然后返回。
代码块 14
rcl_ret_trcl_init_options_init(rcl_init_options_t * init_options,                       rcl_allocator_t allocator){  RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_INVALID_ARGUMENT);  RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_ALREADY_INIT);  RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_BAD_ALLOC);  RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_ERROR);  RCL_CHECK_ARGUMENT_FOR_NULL(init_options, RCL_RET_INVALID_ARGUMENT);  if (NULL != init_options->impl) {    RCL_SET_ERROR_MSG("given init_options (rcl_init_options_t) is already initialized");    return RCL_RET_ALREADY_INIT;  }  RCL_CHECK_ALLOCATOR(&allocator, return RCL_RET_INVALID_ARGUMENT);  rcl_ret_t ret = _rcl_init_options_zero_init(init_options,                                               allocator);  if (RCL_RET_OK != ret) {    return ret;  }  rmw_ret_t rmw_ret = rmw_init_options_init(                            &(init_options->impl->rmw_init_options),                             allocator);  if (RMW_RET_OK != rmw_ret) {    allocator.deallocate(init_options->impl, allocator.state);    RCL_SET_ERROR_MSG(rmw_get_error_string().str);    return rcl_convert_rmw_ret_to_rcl_ret(rmw_ret);  }  return RCL_RET_OK;}
代码块 15
这里,我们看一下代码块 14的第 16 行的接口实现:
static inlinercl_ret_t_rcl_init_options_zero_init(rcl_init_options_t * init_options,                             rcl_allocator_t allocator){  init_options->impl = allocator.allocate(                sizeof(rcl_init_options_impl_t),                 allocator.state);  RCL_CHECK_FOR_NULL_WITH_MSG(    init_options->impl,    "failed to allocate memory for init options impl",    return RCL_RET_BAD_ALLOC);  init_options->impl->allocator = allocator;  init_options->impl->rmw_init_options = rmw_get_zero_initialized_init_options();  return RCL_RET_OK;}
因为我们前面介绍过 allocator 的类型是 rcl_allocator_t,所以,allocator 的行为很好理解,其实就可以把他当成 malloc 就行了。
这个家伙在 ros2 的源码里会经常遇到。建议看代码的时候,翻翻 rcl_allocator_t 对象是怎么赋值的。
impl 这种使用方式,在 ros2 里已经不能说是经常了,而是全这么干的。而且,impl 里面包含的 struct,可能还嵌套其他 impl。所以经常会不记得自己看的是什么。
回到这个接口的实现,它就是对 init_options 做一个初始化。这些初始化的数据项,在后续步骤中,有些有可能会被修改,有些数据项未必初始化完毕,在其他某个神奇的地方再初始化。反正就得多翻代码,搞糊涂的话,还得往前翻。
代码块 16
这里,我们看一下代码块 14的第 22 行的接口实现:
rmw_ret_trmw_init_options_init(rmw_init_options_t * init_options, rcutils_allocator_t allocator){  return rmw_fastrtps_shared_cpp::rmw_init_options_init(    eprosima_fastrtps_identifier, init_options, allocator);}
代码块 17
rmw_ret_trmw_init_options_init(  const char * identifier,   rmw_init_options_t * init_options,   rcutils_allocator_t allocator){  assert(identifier != NULL);  RMW_CHECK_ARGUMENT_FOR_NULL(init_options, RMW_RET_INVALID_ARGUMENT);  RCUTILS_CHECK_ALLOCATOR(&allocator, return RMW_RET_INVALID_ARGUMENT);  if (NULL != init_options->implementation_identifier) {    RMW_SET_ERROR_MSG("expected zero-initialized init_options");    return RMW_RET_INVALID_ARGUMENT;  }  init_options->instance_id = 0;  init_options->implementation_identifier = identifier;  init_options->allocator = allocator;  init_options->impl = nullptr;  init_options->enclave = NULL;  init_options->domain_id = RMW_DEFAULT_DOMAIN_ID;  init_options->security_options = rmw_get_default_security_options();  init_options->localhost_only = RMW_LOCALHOST_ONLY_DEFAULT;  return RMW_RET_OK;}
这里,已经是 rmw 层的接口了,初始化的是 rmw 相关的数据成员。
看它传入的参数,也叫 init_options,名字长得都很像,所以,看代码就还是得来回翻。
这个接口也没做什么复杂的事,是对 rmw 的参数做初始化。
目前看,比较重要的就是这个 allocator,在 rclcpp::init() 之后的步骤中,经常会看到它,到时候能想起来它是哪来的,干什么的就行了,就按默认的看待就行。
从 代码块 13 到 代码块 17 都是在做 代码块 12 的初始化工作。
代码块 12 这个构造函数,还只是我们今天主角代码块 1的默认参数。为了方便阅读,把 代码块 1 的代码原样贴过来,不过还是得重新编号:
代码块 18
RCLCPP_PUBLICvoidinit(  int argc,  char const * const * argv,  const InitOptions & init_options = InitOptions(),  SignalHandlerOptions signal_handler_options = SignalHandlerOptions::All);
下面是它的实现:
代码块 19
voidinit(  int argc,  char const * const * argv,  const InitOptions & init_options,  SignalHandlerOptions signal_handler_options){  using rclcpp::contexts::get_global_default_context;  get_global_default_context()->init(argc, argv, init_options);  // Install the signal handlers.  install_signal_handlers(signal_handler_options);}
出现一个很重要的东西:
    rclcpp::contexts::get_global_default_context
看一下它是什么:
代码块 20
DefaultContext::SharedPtrrclcpp::contexts::get_global_default_context(){  static DefaultContext::SharedPtr default_context             = DefaultContext::make_shared();  return default_context;}
代码块 21
classDefaultContext : publicrclcpp::Context{public:  RCLCPP_SMART_PTR_DEFINITIONS(DefaultContext)  RCLCPP_PUBLIC  DefaultContext();};
代码块 22
class Context : public std::enable_shared_from_this<Context>{/// 省略部分代码private:  std::shared_ptr<rcl_context_t> rcl_context_;  rclcpp::InitOptions init_options_;};

Context 是 ros2 重要的一个 class,它通过 rcl_context_ 成员保存很多运行时信息。从始至终都存在。

代码块 23
typedef struct rcl_context_s{  rcl_arguments_t global_arguments;  rcl_context_impl_t * impl;  RCL_ALIGNAS(8) uint8_t instance_id_storage[RCL_CONTEXT_ATOMIC_INSTANCE_ID_STORAGE_SIZE];} rcl_context_t;

这个结构有 3 个主要成员。

global_arguments:保存命令行参数,进程级。

impl:它是 rcl 开头的,也就说,它是属于 rcl 的一些参数。当然,肯定会有 rmw 层的一些信息。也是进程级。

这里有个有意思的地方,之前没注意到:

rcl_context_s 这个结构定义的文件是在 include/rcl/ 目录下面的。

rcl_arguments_t 也是 include/rcl/ 这个目录。

然后我们看 struct rcl_arguments_impl_s定义的位置:

在 src/rcl 目录下的 .h 文件中定义。也就是说,这个结构属于内部结构,外部程序是不能直接访问到细节的。

当然,rcl_context_impl_t 这个 struct 看代码,也是这样一个使用方式。

可能前面我们提到的 struct 中,也有这样使用的,不过之前没有注意到。

代码块 24
struct rcl_arguments_impl_s{  /// Array of indices to unknown ROS specific arguments.  int * unparsed_ros_args;  /// Length of unparsed_ros_args.  int num_unparsed_ros_args;  /// Array of indices to non-ROS arguments.  int * unparsed_args;  /// Length of unparsed_args.  int num_unparsed_args;  /// Parameter override rules parsed from arguments.  rcl_params_t * parameter_overrides;  /// Array of yaml parameter file paths  char ** parameter_files;  /// Length of parameter_files.  int num_param_files_args;  /// Array of rules for name remapping.  rcl_remap_t * remap_rules;  /// Length of remap_rules.  int num_remap_rules;  /// Log levels parsed from arguments.  rcl_log_levels_t log_levels;  /// A file used to configure the external logging library  char * external_log_config_file;  /// A boolean value indicating if the standard out handler should be used for log output  bool log_stdout_disabled;  /// A boolean value indicating if the rosout topic handler should be used for log output  bool log_rosout_disabled;  /// A boolean value indicating if the external lib handler should be used for log output  bool log_ext_lib_disabled;  /// Enclave to be used.  char * enclave;  /// Allocator used to allocate objects in this struct  rcl_allocator_t allocator;};

这里贴一下 rcl_arguments_impl_s,从成员上来看,主要还是跟参数有关。后面看代码的话,也不在这里花什么精力,知道它是存命令行参数就行了。

代码块 25
struct rcl_context_impl_s{  /// Allocator used during init and shutdown.  rcl_allocator_t allocator;  /// Copy of init options given during init.  rcl_init_options_t init_options;  /// Length of argv (may be `0`).  int64_t argc;  /// Copy of argv used during init (may be `NULL`).  char ** argv;  /// rmw context.  rmw_context_t rmw_context;};
    这个 struct 里,前 4 个看起来都比较眼熟。前两个,前面都提到过,argc 和 argv 那就更不用说了。
    这也可以看出来一点,就是同样的信息,可能在不同的 struct 里重复出现,这主要是 ros2 框架每层用的信息虽然有不同,但是也会有交叉,虽然重复,但是所处的层级不一样。我们自己写代码的时候,有时候也是不得已,也得这么做。这也不能算冗余吧,逻辑分层?应该就是这意思。
代码块 26
typedef struct RMW_PUBLIC_TYPE rmw_context_s{  /// Locally (process local) unique ID that represents this init/shutdown cycle.  uint64_t instance_id;  /// Implementation identifier, used to ensure two different implementations are not being mixed.  const char * implementation_identifier;  /// Options used to initialize the context.  rmw_init_options_t options;  /// Domain id that is being used.  size_t actual_domain_id;  /// Implementation defined context information.  /** May be NULL if there is no implementation defined context information. */  rmw_context_impl_t * impl;} rmw_context_t;
代码块 27
typedef struct RMW_PUBLIC_TYPE rmw_context_s{  /// Locally (process local) unique ID that represents this init/shutdown cycle.  uint64_t instance_id;  /// Implementation identifier, used to ensure two different implementations are not being mixed.  const char * implementation_identifier;  /// Options used to initialize the context.  rmw_init_options_t options;  /// Domain id that is being used.  size_t actual_domain_id;  /// Implementation defined context information.  /** May be NULL if there is no implementation defined context information. */  rmw_context_impl_t * impl;} rmw_context_t;
代码块 28
typedef struct rmw_context_impl_s rmw_context_impl_t;
代码块 29
// Definition of struct rmw_context_impl_s as declared in rmw/init.hstruct rmw_context_impl_s{  /// Pointer to `rmw_dds_common::Context`.  void * common;  /// Pointer to `rmw_fastrtps_shared_cpp::CustomParticipantInfo`.  void * participant_info;  /// Mutex used to protect initialization/destruction.  std::mutex mutex;  /// Reference count.  uint64_t count;  /// Shutdown flag.  bool is_shutdown;};
代码块 25到 代码块 29,是 rcl_context_impl_s 结构的层层展开,可以看到,这都是 rmw 层用的一些信息了。
代码块 29 的 participant_info 先提一下,这个东西不在 rclcpp::init() 里初始化,应该是在 Node 构造的某个步骤初始化的,我记不清了。就是因为看 create-publisher 源码的时候,遇到这个家伙,不知道在哪初始化的,通过 debug 才找到初始化的地点。然后才从 create-publisher 源码跳出来,重新回到 init() 这里。这种跳来跳去的情况,估计不会少,因为 ros2 代码还挺复杂的,不能保证当前的方向就是对的。

    前面一大堆 struct 的定义,虽然很多,但是不贴出来,看 init() 就会稀里糊涂。

代码块 19  的第 9 行:
get_global_default_context()->init(argc, argv, init_options);
代码块 30 
voidContext::init(  int argc,  char const * const * argv,  const rclcpp::InitOptions & init_options){  std::lock_guard<std::recursive_mutex> init_lock(init_mutex_);  if (this->is_valid()) {    throw rclcpp::ContextAlreadyInitialized();  }  this->clean_up();  rcl_context_t * context = new rcl_context_t;  if (!context)   {    throw std::runtime_error("failed to allocate memory for rcl context");  }  *context = rcl_get_zero_initialized_context();  rcl_ret_t ret = rcl_init(argc, argv,                           init_options.get_rcl_init_options(),                           context);  if (RCL_RET_OK != ret) {    delete context;    rclcpp::exceptions::throw_from_rcl_error(ret, "failed to initialize rcl");  }  rcl_context_.reset(context, __delete_context);  if (init_options.auto_initialize_logging())   {    logging_mutex_ = get_global_logging_mutex();    std::lock_guard<std::recursive_mutex> guard(*logging_mutex_);    size_t & count = get_logging_reference_count();    if (0u == count)     {      ret = rcl_logging_configure_with_output_handler(        &rcl_context_->global_arguments,        rcl_init_options_get_allocator(init_options.get_rcl_init_options()),        rclcpp_logging_output_handler);      if (RCL_RET_OK != ret)       {        rcl_context_.reset();        rclcpp::exceptions::throw_from_rcl_error(ret, "failed to configure logging");      }    }     else     {      RCLCPP_WARN(        rclcpp::get_logger("rclcpp"),        "logging was initialized more than once");    }    ++count;  }  try   {    std::vector<std::string> unparsed_ros_arguments           = detail::get_unparsed_ros_arguments(                           argc, argv,                            &(rcl_context_->global_arguments),                            rcl_get_default_allocator());    if (!unparsed_ros_arguments.empty())     {      throw exceptions::UnknownROSArgsError(std::move(unparsed_ros_arguments));    }    init_options_ = init_options;    weak_contexts_ = get_weak_contexts();    weak_contexts_->add_context(this->shared_from_this());  }   catch (const std::exception & e)   {    ret = rcl_shutdown(rcl_context_.get());    rcl_context_.reset();    if (RCL_RET_OK != ret) {      std::ostringstream oss;      oss << "While handling: " << e.what() << std::endl <<        "    another exception was thrown";      rclcpp::exceptions::throw_from_rcl_error(ret, oss.str());    }    throw;  }}

    前面铺垫了那么多,又是 InitOption,又是 context,实际都是为了这个函数准备的。

第 13 ~ 22 行,创建一个 context ,然后把它传给  rcl_init(),于是,我们看看 rcl_init() 函数:

代码块 31
rcl_ret_trcl_init(  int argc,  char const * const * argv,  const rcl_init_options_t * options,  rcl_context_t * context){  rcl_ret_t fail_ret = RCL_RET_ERROR;  if (argc > 0)   {    RCL_CHECK_ARGUMENT_FOR_NULL(argv, RCL_RET_INVALID_ARGUMENT);    for (int i = 0; i < argc; ++i)     {      RCL_CHECK_ARGUMENT_FOR_NULL(argv[i], RCL_RET_INVALID_ARGUMENT);    }  }   else   {    if (NULL != argv)     {      RCL_SET_ERROR_MSG("argc is <= 0, but argv is not NULL");      return RCL_RET_INVALID_ARGUMENT;    }  }  RCL_CHECK_ARGUMENT_FOR_NULL(options, RCL_RET_INVALID_ARGUMENT);  RCL_CHECK_ARGUMENT_FOR_NULL(options->impl, RCL_RET_INVALID_ARGUMENT);  rcl_allocator_t allocator = options->impl->allocator;  RCL_CHECK_ALLOCATOR(&allocator, return RCL_RET_INVALID_ARGUMENT);  RCL_CHECK_ARGUMENT_FOR_NULL(context, RCL_RET_INVALID_ARGUMENT);  RCUTILS_LOG_DEBUG_NAMED(    ROS_PACKAGE_NAME,    "Initializing ROS client library, for context at address: %p", (void *) context);  // test expectation that given context is zero initialized  if (NULL != context->impl)   {    // note that this can also occur when the given context is used before initialization    // i.e. it is declared on the stack but never defined or zero initialized    RCL_SET_ERROR_MSG("rcl_init called on an already initialized context");    return RCL_RET_ALREADY_INIT;  }  // Zero initialize global arguments.  context->global_arguments           = rcl_get_zero_initialized_arguments();  // Setup impl for context.  // use zero_allocate so the cleanup function will not try to clean up uninitialized parts later  context->impl = allocator.zero_allocate(1,                           sizeof(rcl_context_impl_t),                           allocator.state);  RCL_CHECK_FOR_NULL_WITH_MSG(    context->impl, "failed to allocate memory for context impl", return RCL_RET_BAD_ALLOC);  // Zero initialize rmw context first so its validity can by checked in cleanup.  context->impl->rmw_context = rmw_get_zero_initialized_context();  // Store the allocator.  context->impl->allocator = allocator;  // Copy the options into the context for future reference.  rcl_ret_t ret = rcl_init_options_copy(options, &(context->impl->init_options));  if (RCL_RET_OK != ret)   {    fail_ret = ret;  // error message already set    goto fail;  }  // Copy the argc and argv into the context, if argc >= 0.  context->impl->argc = argc;  context->impl->argv = NULL;  if (0 != argc && argv != NULL)   {    context->impl->argv = (char **)allocator.zero_allocate(argc,                                             sizeof(char *),                                              allocator.state);    RCL_CHECK_FOR_NULL_WITH_MSG(      context->impl->argv,      "failed to allocate memory for argv",      fail_ret = RCL_RET_BAD_ALLOC; goto fail);    int64_t i;    for (i = 0; i < argc; ++i)     {      size_t argv_i_length = strlen(argv[i]) + 1;      context->impl->argv[i] = (char *)allocator.allocate(argv_i_length, allocator.state);      RCL_CHECK_FOR_NULL_WITH_MSG(        context->impl->argv[i],        "failed to allocate memory for string entry in argv",        fail_ret = RCL_RET_BAD_ALLOC; goto fail);      memcpy(context->impl->argv[i], argv[i], argv_i_length);    }  }  // Parse the ROS specific arguments.  ret = rcl_parse_arguments(argc, argv, allocator, &context->global_arguments);  if (RCL_RET_OK != ret) {    fail_ret = ret;    RCUTILS_LOG_ERROR_NAMED(ROS_PACKAGE_NAME, "Failed to parse global arguments");    goto fail;  }  // Set the instance id.  uint64_t next_instance_id = rcutils_atomic_fetch_add_uint64_t(&__rcl_next_unique_id, 1);  if (0 == next_instance_id)   {    // Roll over occurred, this is an extremely unlikely occurrence.    RCL_SET_ERROR_MSG("unique rcl instance ids exhausted");    // Roll back to try to avoid the next call succeeding, but there's a data race here.    rcutils_atomic_store(&__rcl_next_unique_id, -1);    goto fail;  }  rcutils_atomic_store((atomic_uint_least64_t *)(&context->instance_id_storage), next_instance_id);  context->impl->init_options.impl        ->rmw_init_options.instance_id = next_instance_id;  size_t * domain_id = &context->impl->init_options.impl->rmw_init_options.domain_id;  if (RCL_DEFAULT_DOMAIN_ID == *domain_id)   {    // Get actual domain id based on environment variable.    ret = rcl_get_default_domain_id(domain_id);    if (RCL_RET_OK != ret)     {      fail_ret = ret;      goto fail;    }  }  rmw_localhost_only_t * localhost_only =    &context->impl->init_options.impl->rmw_init_options.localhost_only;  if (RMW_LOCALHOST_ONLY_DEFAULT == *localhost_only)   {    // Get actual localhost_only value based on environment variable, if needed.    ret = rcl_get_localhost_only(localhost_only);    if (RCL_RET_OK != ret)     {      fail_ret = ret;      goto fail;    }  }  if (context->global_arguments.impl->enclave)   {    context->impl->init_options.impl->rmw_init_options.enclave = rcutils_strdup(      context->global_arguments.impl->enclave,      context->impl->allocator);  }   else   {    context->impl->init_options.impl->rmw_init_options.enclave = rcutils_strdup(      "/", context->impl->allocator);  }  if (!context->impl->init_options.impl->rmw_init_options.enclave) {    RCL_SET_ERROR_MSG("failed to set context name");    fail_ret = RCL_RET_BAD_ALLOC;    goto fail;  }  int validation_result;  size_t invalid_index;  ret = rcl_validate_enclave_name(    context->impl->init_options.impl->rmw_init_options.enclave,    &validation_result,    &invalid_index);  if (RCL_RET_OK != ret)   {    RCL_SET_ERROR_MSG("rcl_validate_enclave_name() failed");    fail_ret = ret;    goto fail;  }  if (RCL_ENCLAVE_NAME_VALID != validation_result)   {    RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(      "Enclave name is not valid: '%s'. Invalid index: %zu",      rcl_enclave_name_validation_result_string(validation_result),      invalid_index);    fail_ret = RCL_RET_ERROR;    goto fail;  }  rmw_security_options_t * security_options =    &context->impl->init_options.impl->rmw_init_options.security_options;  ret = rcl_get_security_options_from_environment(    context->impl->init_options.impl->rmw_init_options.enclave,    &context->impl->allocator,    security_options);  if (RCL_RET_OK != ret) {    fail_ret = ret;    goto fail;  }  // Initialize rmw_init.  rmw_ret_t rmw_ret = rmw_init(    &(context->impl->init_options.impl->rmw_init_options),    &(context->impl->rmw_context));  if (RMW_RET_OK != rmw_ret)   {    RCL_SET_ERROR_MSG(rmw_get_error_string().str);    fail_ret = rcl_convert_rmw_ret_to_rcl_ret(rmw_ret);    goto fail;  }  TRACEPOINT(rcl_init, (const void *)context);  return RCL_RET_OK;fail:  __cleanup_context(context);  return fail_ret;}

第 8 ~ 26 行:做参数的合法性校验,不展开宏定义了。

第 28 行:allocator,这个家伙前面反复提到,可以看到下面的代码它经常出现。

第 46 行:给 context 的 argument 参数做初始化。

第 51 行:这个 impl 就是 rmw-context,它这个名字取的就比较 der,你哪怕叫 ctx-impl,我也能知道你大概是个啥。。。不过这部分代码也不是对外的,叫啥可能也无所谓。

第 51 ~ 100行:对 context 的部分参数做初始化,比较明显。

第 103 ~ 108行:对命令行参数的解析。这里解析函数不展开,太复杂,且不是关注重点。就是 ros2 run pkg node [一堆参数],解析这个一堆参数的。有的是 ros2 框架的,有的是 node 自己的。还有非法参数,比如字母写错了。

第 111 ~ 124行:对 next_instance_id 做处理,应该就是 +1.这里也没细看,反正就是对 next_instance_id 做处理。下次取上次处理的结果。这里有个疑问:init() 函数只会调一次,后面什么场景会再次处理这个 next_instance_id 。搞清楚的话,再水一篇。

第 131 行:如果前面没取到 domain-id,它会从环境变量里取,这里代码不展开了。

第 139 ~ 151行:就是判断 local-host 参数,是不是用 127.0.0.1 这个 ip。

第 153 ~ 207 行:是安全相关的配置,这些跳过,不是现阶段关注的点。就是那些证书名,路径之类的初始化,校验啥的。

第 210 行:调用 rmw_init() 函数。这个地方有个有意思的事情。用 VSCode,按住 ctrl 然后鼠标左键点击这个函数名,它会弹出一堆候选函数:

到底是哪个呢?

这就涉及到这篇 Ros2 源码中如何知道当前使用哪个 DDS 中间件提到的东西啦。感兴趣可以看看。

我们用的是 fastdds,它的目录就是

src/ros2/rmw_fastrtps/rmw_fastrtps_cpp/src/rmw_init.cpp

这个接口的代码我不贴了,不让阅读跳来跳去了。

一句话 ,就是初始化 context 中 rcl-context 相关成员,这些成员在文档前面都提到过。 

    这个 rcl_init() 的就结束了。它是代码块 30 的第 20 行。为了方便阅读,把 代码块 30 重新贴一遍:

代码块 32
voidContext::init(  int argc,  char const * const * argv,  const rclcpp::InitOptions & init_options){  std::lock_guard<std::recursive_mutex> init_lock(init_mutex_);  if (this->is_valid()) {    throw rclcpp::ContextAlreadyInitialized();  }  this->clean_up();  rcl_context_t * context = new rcl_context_t;  if (!context)   {    throw std::runtime_error("failed to allocate memory for rcl context");  }  *context = rcl_get_zero_initialized_context();  rcl_ret_t ret = rcl_init(argc, argv,                           init_options.get_rcl_init_options(),                           context);  if (RCL_RET_OK != ret) {    delete context;    rclcpp::exceptions::throw_from_rcl_error(ret, "failed to initialize rcl");  }  rcl_context_.reset(context, __delete_context);  if (init_options.auto_initialize_logging())   {    logging_mutex_ = get_global_logging_mutex();    std::lock_guard<std::recursive_mutex> guard(*logging_mutex_);    size_t & count = get_logging_reference_count();    if (0u == count)     {      ret = rcl_logging_configure_with_output_handler(        &rcl_context_->global_arguments,        rcl_init_options_get_allocator(init_options.get_rcl_init_options()),        rclcpp_logging_output_handler);      if (RCL_RET_OK != ret)       {        rcl_context_.reset();        rclcpp::exceptions::throw_from_rcl_error(ret, "failed to configure logging");      }    }     else     {      RCLCPP_WARN(        rclcpp::get_logger("rclcpp"),        "logging was initialized more than once");    }    ++count;  }  try   {    std::vector<std::string> unparsed_ros_arguments           = detail::get_unparsed_ros_arguments(                           argc, argv,                            &(rcl_context_->global_arguments),                            rcl_get_default_allocator());    if (!unparsed_ros_arguments.empty())     {      throw exceptions::UnknownROSArgsError(std::move(unparsed_ros_arguments));    }    init_options_ = init_options;    weak_contexts_ = get_weak_contexts();    weak_contexts_->add_context(this->shared_from_this());  }   catch (const std::exception & e)   {    ret = rcl_shutdown(rcl_context_.get());    rcl_context_.reset();    if (RCL_RET_OK != ret) {      std::ostringstream oss;      oss << "While handling: " << e.what() << std::endl <<        "    another exception was thrown";      rclcpp::exceptions::throw_from_rcl_error(ret, oss.str());    }    throw;  }}

第 29 行:rcl_context_ 是 class Context 的成员变量,它用刚刚初始化完毕的 context 做了个赋值。至此,Context 的初始化可以认为是完成了。

第 31 行:这里返回的是 true,它获取的是 class InitOptions 的成员 initialize_logging_ 的值。这个家伙默认是 true:

代码块 33
private:  void  finalize_init_options_impl();  mutable std::mutex init_options_mutex_;  std::unique_ptr<rcl_init_options_t> init_options_;  bool initialize_logging_{true};

第 31 ~ 56 行:对 ros2 的日志接口做初始化处理。具体不展开了,这里细节不是关注重点。

第 60 ~ 68 行:对命令行参数中,那些不认识的参数做解析,就是写错名字啊,不在 ros2系统内,也不是 node 参数列表里的那些参数做处理。也不展开。

第 71 行:这个比较重要。前两遍读代码把这个给漏了。

代码块 34

staticWeakContextsWrapper::SharedPtrget_weak_contexts(){  static WeakContextsWrapper::SharedPtr           weak_contexts = WeakContextsWrapper::make_shared();  if (!weak_contexts) {    throw std::runtime_error("weak contexts vector is not valid");  }  return weak_contexts;}

第 1 行:static,这是个 static 函数,可以直接调用的。

第 5 行:static WeakContextsWrapper,又一个 static 类对象,那它就是个全局的了。WeakContextsWrapper 不展开,其内部有个 vector:

std::vector<std::weak_ptr<rclcpp::Context>> weak_contexts_;

也就是说,这个接口相当于返回一个全局的 vector 了。

我们再回到 代码块 32 的 72 行:

weak_contexts_->add_context(this->shared_from_this());

把当前 context 插入到一个全局的 vector 里。

为什么要有这一步?把context 加入到全局 vector,难道还会有别的 context?

    水平有限,我还没看那么多代码,我直接问 AI 了,以下是千问给的答案:    

大多数场景下确实只有一个(就是 rclcpp::init() 创建的全局默认 Context),但 ROS 2 确实支持多 Context,典型场景包括:1. 隔离不同 DDS domain 一个进程里可以创建两个 Context,分别设置不同的 domain_id,让它们连接到不同的 DDS domain,实现通信隔离。2. 组件化架构(Component)ROS 2 的 Component 机制允许多个 Node 在同一个进程里运行,共享一个 Context 以实现进程内零拷贝通信。 但如果你想在同一个进程里同时运行两套互不干扰的通信栈,就需要两个 Context。3. 测试和仿真单元测试里经常需要创建多个独立的 Context,模拟多个进程的行为,但又不想真的 fork 进程。4. 动态加载/卸载某些高级场景下,程序可能需要动态创建和销毁 Context(比如插件式架构),每个插件有自己的 Context,互不影响。

    我会继续深入学习,搞清楚这些场景对 context 的使用,也加深对 context 的理解。

    上面这些内容,只是 Context::init() 做的事情,在代码块 19 的第 9 行。为了方便阅读,重复贴一下代码块 19 的内容:

代码块 35

voidinit(  int argc,  char const * const * argv,  const InitOptions & init_options,  SignalHandlerOptions signal_handler_options){  using rclcpp::contexts::get_global_default_context;  get_global_default_context()->init(argc, argv, init_options);  // Install the signal handlers.  install_signal_handlers(signal_handler_options);}

第 11 行:Linux 信号处理逻辑。这个也做了很多事,这个后面单水一篇。

这里只贴几个代码片段:

代码块 36

boolinstall_signal_handlers(SignalHandlerOptions signal_handler_options){  return SignalHandler::get_global_signal_handler().install(signal_handler_options);}

代码块 37

boolSignalHandler::install(SignalHandlerOptions signal_handler_options){  std::lock_guard<std::mutex> lock(install_mutex_);  bool already_installed = installed_.exchange(true);  if (already_installed) {    return false;  }  if (signal_handler_options == SignalHandlerOptions::None) {    return true;  }  signal_handlers_options_ = signal_handler_options;  try   {    // 省略一大堆代码    signal_handler_thread_ =       std::thread(&SignalHandler::deferred_signal_handler, this);  }   catch (...)   {   // 省略  }  // 省略}

代码块 37 第 19 行的线程函数:

代码块 38

voidSignalHandler::deferred_signal_handler(){  while (true) {    if (signal_received_.exchange(false)) {      RCLCPP_INFO(SignalHandler::get_logger(), "signal_handler(SIGINT/SIGTERM)");      RCLCPP_DEBUG(get_logger(), "deferred_signal_handler(): shutting down");      for (auto context_ptr : rclcpp::get_contexts()) {        if (context_ptr->get_init_options().shutdown_on_signal) {          RCLCPP_DEBUG(            get_logger(),            "deferred_signal_handler(): "            "shutting down rclcpp::Context @ %p, because it had shutdown_on_signal == true",            static_cast<void *>(context_ptr.get()));          try {            context_ptr->shutdown("signal handler");          } catch (const std::exception & exc) {            // an uncaught exception on this thread would call std::terminate(),            // taking down the whole process, so log the failure instead            RCLCPP_ERROR(              get_logger(),              "deferred_signal_handler(): failed to shutdown rclcpp::Context @ %p: %s",              static_cast<void *>(context_ptr.get()), exc.what());          } catch (...) {            RCLCPP_ERROR(              get_logger(),              "deferred_signal_handler(): failed to shutdown rclcpp::Context @ %p",              static_cast<void *>(context_ptr.get()));          }        }      }    }    if (!is_installed()) {      RCLCPP_DEBUG(get_logger(), "deferred_signal_handler(): signal handling uninstalled");      break;    }    RCLCPP_DEBUG(      get_logger(), "deferred_signal_handler(): waiting for SIGINT/SIGTERM or uninstall");    wait_for_signal();    RCLCPP_DEBUG(      get_logger(), "deferred_signal_handler(): woken up due to SIGINT/SIGTERM or uninstall");  }}

第 8 行:rclcpp::get_contexts(),这里不展开它的代码,它就是获取那个全局的 context 的 vector。然后遍历这个 vector。

第 16 行:调用 context 的 shutdown() 接口,走关闭流程。

    我们运行 ros2 run pkg node,然后 ctrl + C 触发的就是这个。

这里终于看到一个多 context 的场景。如果当前运行多个 node,ctrl + C 的时候,它得判断一下,到底 kill 哪个 node 的进程。

    浅薄了~

相关学习资料