ARTICLE · 1077218
Ros2源码(7)rclcpp::init()
RCLCPP_PUBLICvoidinit(int argc,char const * const * argv,const InitOptions & init_options = InitOptions(),SignalHandlerOptions signal_handler_options = SignalHandlerOptions::All);
rclcpp::init(argc, argv);class InitOptions{public:/// 省略部分代码RCLCPP_PUBLICexplicit InitOptions(rcl_allocator_t allocator = rcl_get_default_allocator());/// 省略部分代码private:/// 省略部分代码std::unique_ptr<rcl_init_options_t> init_options_;/// 省略部分代码};
std::unique_ptr<rcl_init_options_t> init_options_;//// 注意这一行,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;
struct rcl_init_options_impl_s{rcl_allocator_t allocator;rmw_init_options_t rmw_init_options;};
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;
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 idsize_t domain_id;/// Security optionsrmw_security_options_t security_options;/// Enable localhost onlyrmw_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;
RCLCPP_PUBLICexplicit InitOptions(rcl_allocator_t allocator= rcl_get_default_allocator());
#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;}
static void *__default_allocate(size_t size, void * state){RCUTILS_CAN_RETURN_WITH_ERROR_OF(NULL);RCUTILS_UNUSED(state);return malloc(size);}
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");}}
rcl_init_options_trcl_get_zero_initialized_init_options(void){return (const rcl_init_options_t) {.impl = 0,}; // NOLINT(readability/braces): false positive}
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;}
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;}
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);}
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;}
RCLCPP_PUBLICvoidinit(int argc,char const * const * argv,const InitOptions & init_options = InitOptions(),SignalHandlerOptions signal_handler_options = SignalHandlerOptions::All);
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);}
DefaultContext::SharedPtrrclcpp::contexts::get_global_default_context(){static DefaultContext::SharedPtr default_context= DefaultContext::make_shared();return default_context;}
classDefaultContext : publicrclcpp::Context{public:RCLCPP_SMART_PTR_DEFINITIONS(DefaultContext)RCLCPP_PUBLICDefaultContext();};
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_ 成员保存很多运行时信息。从始至终都存在。
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 个主要成员。
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 中,也有这样使用的,不过之前没有注意到。
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 pathschar ** 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 librarychar * external_log_config_file;/// A boolean value indicating if the standard out handler should be used for log outputbool log_stdout_disabled;/// A boolean value indicating if the rosout topic handler should be used for log outputbool log_rosout_disabled;/// A boolean value indicating if the external lib handler should be used for log outputbool log_ext_lib_disabled;/// Enclave to be used.char * enclave;/// Allocator used to allocate objects in this structrcl_allocator_t allocator;};
这里贴一下 rcl_arguments_impl_s,从成员上来看,主要还是跟参数有关。后面看代码的话,也不在这里花什么精力,知道它是存命令行参数就行了。
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;};
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;
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;
typedef struct rmw_context_impl_s rmw_context_impl_t;// 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;};
前面一大堆 struct 的定义,虽然很多,但是不贴出来,看 init() 就会稀里糊涂。
get_global_default_context()->init(argc, argv, init_options);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() 函数:
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 initializedif (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 initializedRCL_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 latercontext->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 setgoto 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 重新贴一遍:
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:
private:voidfinalize_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::SharedPtrweak_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 insteadRCLCPP_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 的进程。
浅薄了~