质量指南: 确保代码质量 [待校准@1031]
此页指导如何提高软件质量ROS 2包,聚焦更具体地区质量做法科 Developer Guide. [待校准@1032]
以下各节旨在讨论ROS 2核心、应用程序和生态系统包以及核心客户端库、c ++ 和Python。提出的解决方案是出于设计考虑提高质量属性 "Reliability" , "Security" , "Maintainability" , "Determinism" 等涉及非功能要求。 [待校准@1033]
作为ament包构建的一部分的静态代码分析 [待校准@1034]
上下文: [待校准@1035]
您已经开发了cproduction生产代码。 [待校准@1036]
您已经创建了一个带有
ament
构建支持的ROS 2包。 [待校准@1037]
问题: [待校准@1038]
库级静态代码分析不作为包构建过程的一部分运行。 [待校准@1039]
库级静态代码分析需要手动执行。 [待校准@1040]
在构建新的包版本之前忘记执行库级静态代码分析的风险。 [待校准@1041]
解决方案: [待校准@1042]
使用
ament
的集成功能执行静态代码分析,作为包构建过程的一部分。 [待校准@1043]
实施: [待校准@1044]
插入包
CMakeLists.txt
文件。 [待校准@1045]
...
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
...
endif()
...
插入
ament_lint
测试依赖的包package.xml
文件。 [待校准@1046]
...
<package format="2">
...
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
...
</package>
示例: [待校准@1047]
rclcpp
: [待校准@1048]rclcpp_lifecycle
: [待校准@1051]
结果上下文: [待校准@1054]
[需手动修复的语法]
ament
支持的静态代码分析工具作为包构建的一部分运行。 [待校准@1055][需手动修复的语法]
ament
不支持的静态代码分析工具需要单独执行。 [待校准@1056]
通过代码注释进行静态线程安全分析 [待校准@1057]
Context上下文: Context [待校准@1058]
您正在开发/调试多线程cproduction生产代码 [待校准@1059]
您可以使用ccode代码从多个线程访问数据 [待校准@1060]
Problem问题: Problem [待校准@1061]
数据竞争和死锁会导致严重的错误。 [待校准@1062]
Solution解决方案: ** [待校准@1063]
通过注释线程代码利用Clang的静态 Thread Safety Analysis [待校准@1064]
Implementation实施背景: ** [待校准@1065]
要启用线程安全分析,必须对代码进行注释,以使编译器更多地了解代码的语义。这些注释是特定于叮当声的属性 -- 例如 __attribute__(capability()))
。ROS 2提供预处理器宏,而不是直接使用这些属性,这些宏在使用其他编译器时会被擦除。 [待校准@1066]
这些宏可以在 rcpputils/thread_safety_annotations.h 中找到 [待校准@1067]
- 线程安全分析文档声明 [待校准@1068]
线程安全分析可以与任何线程库一起使用,但是它确实要求将线程API包装在具有适当注释的类和方法中 [待校准@1069]
我们已经决定,我们希望ROS 2开发人员能够直接为他们的开发版本使用 “std::” 线程原语。我们不想像上面建议的那样提供我们自己的包装类型。 [待校准@1070]
有三个c standard标准库需要注意 * GNU标准库 “libstd c ++”-Linux上的默认值,显式地通过编译器选项 “-stdlib = libstd c ++ `` * The LLVM standard library `` 库c ++” (也调用ed libcxx
) -macOS上的默认值,由编译器选项 “-stdlib = lib c ++” 显式设置 * Windows c Standard标准库-与此用例无关 [待校准@1071]
libcxx
annotates its std::mutex
and std::lock_guard
implementations for Thread Safety Analysis. When using GNU libstdc++
, those annotations are not present, so Thread Safety Analysis cannot be used on non-wrapped std::
types. [待校准@1072]
Therefore, to use Thread Safety Analysis directly with std::
types, we must use libcxx
Implementation实施: ** [待校准@1074]
这里的代码迁移建议绝不是完整的 -- 在编写 (或注释现有) 线程代码时,我们鼓励您尽可能多地使用适合您用例的注释。然而,这是一个很好的开始! [待校准@1075]
为包/目标启用分析 [待校准@1076]
当ccompiler编译器发出响声时,启用
-Wthread-safety
标志。以下基于CMake的项目示例 [待校准@1077]if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wthread-safety) # for your whole package target_compile_options(${MY_TARGET} PUBLIC -Wthread-safety) # for a single library or executable endif()
注释代码 [待校准@1078]
步骤1-注释数据成员 [待校准@1079]
查找 “std::mutex” 用于保护某些成员数据的任何地方 [待校准@1080]
将
RCPPUTILS_TSA_GUARDED_BY(mutex_name)
注释添加到受互斥体保护的数据中 [待校准@1081]
class Foo { public: void incr(int amount) { std::lock_guard<std::mutex> lock(mutex_); bar += amount; } void get() const { return bar; } private: mutable std::mutex mutex_; int bar RCPPUTILS_TSA_GUARDED_BY(mutex_) = 0; };
步骤2-修复警告 [待校准@1082]
在上面的示例中-“foo::get” 将产生编译器警告!要修复它,请在返回杆前锁定 [待校准@1083]
void get() const { std::lock_guard<std::mutex> lock(mutex_); return bar; }
步骤3-(可选但推荐) 将现有代码重构为私有互斥模式 [待校准@1084]
线程化ccode代码中推荐的模式是始终将您的
mutex
作为数据结构的 “私有:” 成员。这使得数据安全成为包含结构的关注点,卸载结构用户的责任,并最小化受影响代码的表面积。 [待校准@1085]将锁设为私有可能需要重新考虑数据接口。这是一个很棒的练习 -- 这里有几件事需要考虑 [待校准@1086]
您可能希望提供专门的接口来执行需要复杂锁定逻辑的分析,例如对互斥保护的地图结构的过滤集合中的成员进行计数,而不是实际将底层结构返回给消费者 [待校准@1087]
在数据量很小的地方,考虑复制以避免阻塞。这可以让其他线程继续访问共享数据,这可能导致更好的整体性能。 [待校准@1088]
步骤4-(可选) 启用负能力分析 [待校准@1089]
https://clang.llvm.org/docs/ThreadSafetyAnalysis.html# 消极能力 [待校准@1090]
负能力分析允许您指定 “调用此函数时不得持有此锁”。它可以揭示其他注释无法揭示的潜在死锁情况。 [待校准@1091]
在指定
-Wthread-safety
的地方,添加附加标志-Wthread-safety-negative
[待校准@1092]在任何获取锁的函数上,使用 “rcpputils_tsa_need (!mutex)” 模式 [待校准@1093]
如何运行分析 [待校准@1094]
ROS CI构建农场与
libcxx
一起运行夜间工作,当线程安全分析引发警告时,它将通过标记为 "Unstable" 来解决ROS 2核心堆栈中的任何问题 [待校准@1095]对于本地运行,您有以下选项,所有选项都等效 [待校准@1096]
使用colcon clang-libcxx mixin [待校准@1097]
colcon build --mixin clang-libcxx
[待校准@1098]只有当你有 configured mixins for your colcon installation 时,你才能使用这个 [待校准@1099]
将编译器传递给CMake [待校准@1100]
colcon build --cmake-args -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS='-stdlib=libc++ -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS' -DFORCE_BUILD_VENDOR_PKG=ON --no-warn-unused-cli
[待校准@1101]
重载系统编译器 [待校准@1102]
CC=clang CXX=clang++ colcon build --cmake-args -DCMAKE_CXX_FLAGS='-stdlib=libc++ -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS' -DFORCE_BUILD_VENDOR_PKG=ON --no-warn-unused-cli
[待校准@1103]
Resulting结果上下文: ** [待校准@1104]
当使用Clang和
libcxx
时,潜在的死锁和竞争条件将在编译时浮出水面 [待校准@1105]
动态分析 (数据循环和死锁) [待校准@1106]
Context上下文: Context [待校准@1058]
您正在开发/调试多线程C++ 生产代码。 [待校准@1107]
您可以使用plines或c11 11线程 + llvm lib c ++ (在线程消毒器的情况下)。 [待校准@1108]
您不使用Libc/libstdc + + 静态链接 (在线程消毒器的情况下)。 [待校准@1109]
您不能构建与职位无关的可执行文件 (在线程清理程序的情况下)。 [待校准@1110]
Problem问题: Problem [待校准@1061]
数据竞争和死锁会导致严重的错误。 [待校准@1062]
使用静态分析无法检测到数据竞争和死锁 (原因: 静态分析的局限性)。 [待校准@1111]
在开发版本调试/测试期间,数据竞争和死锁不得出现 (原因: 通常不是所有可能的控制路径都通过生产代码执行)。 [待校准@1112]
Solution解决方案: ** [待校准@1063]
使用动态分析工具,该工具专注于查找数据种族和死锁 (此处为clang线程消毒器)。 [待校准@1113]
Implementation实施: ** [待校准@1074]
使用选项
-fsanitize=thread
编译生产代码并将其与叮当声联系起来 (这表示生产代码)。 [待校准@1114]如果在分析期间执行不同的生产代码,请考虑条件编译,例如 ThreadSanitizers _has_feature(thread_sanitizer) 。 [待校准@1115]
在某些代码不应该被仪表化的情况下,考虑 ThreadSanitizers _/*attribute*/_((no_sanitize("thread"))) 。 [待校准@1116]
在某些文件不应该被仪表化的情况下,考虑文件或功能级别排除 ThreadSanitizers blacklisting 乙,更具体的是: ThreadSanitizers Sanitizer Special Case List 或 ThreadSanitizers no_sanitize("thread") ,并使用选项
--fsanitize-blacklist
。 [待校准@1117]
Resulting结果上下文: ** [待校准@1118]
在部署生产代码之前,发现数据竞争和死锁的机会更高。 [待校准@1119]
分析结果可能缺乏可靠性,工具处于 β 阶段阶段 (在螺纹消毒剂的情况下)。 [待校准@1120]
由于生产代码仪表产生的开销 (为仪表化/非仪表化生产代码维护单独的分支,等等)。 [待校准@1121]
检测代码每个线程需要更多内存 (如果是线程消毒器)。 [待校准@1122]
仪表化代码映射大量虚拟地址空间 (如果是线程消毒器)。 [待校准@1123]