제15부 이행·확장
15.4 커스텀 rmw와 확장 포인트
15.4.1 이 장에서 배우는 것 / 선수 지식
이 책은 2.1에서 계층 구조를 세웠다 — 사용자 코드 → rclcpp/rclpy → rcl → rmw → 구현 → DDS. 그 계층이 존재하는 이유 중 하나가 교체 가능성이다. 각 층이 인터페이스로 분리되어 있어, 원하면 갈아 끼울 수 있다.
이 장은 그 확장 포인트들을 정리한다. 대부분의 독자는 이것들을 직접 만들지 않겠지만, 어디에 확장 가능한 지점이 있는지 아는 것은 두 가지 이유로 유용하다. 첫째, 문제를 해결할 수단을 하나 더 갖게 된다. 둘째, 기존 구현의 동작을 이해하는 데 도움이 된다.
배우는 것:
- 확장 지점의 지도 — 어디를 확장할 수 있는가.
- pluginlib — 가장 흔한 확장 수단.
- rmw 구현 — 무엇을 만들어야 하는가.
- 실행기·할당자 — 실행 계층 확장.
- 판단 — 만들 것인가 쓸 것인가.
- 유지 비용 — 확장의 대가.
선수 지식: 2.1(계층), 3.5(rmw), 2.2~2.4(실행기), 12.3.4(할당자), 6.2(하드웨어 인터페이스), 11.4.8(RViz2 플러그인).
정직: rmw 구현 작성은 이 책의 범위를 크게 넘는 작업이며, 여기서는 "무엇이 필요한가"의 개관만 다룬다. 실제 구현은
rmw헤더와 기존 구현의 소스가 정본이다. 15.4.6이 인용하는 함수 개수·시그니처·계약 문구는ros:jazzy-ros-base컨테이너 안의/opt/ros/jazzy/include/rmw/rmw/rmw.h를 직접 읽어 확인했다(실행 확인) — 이 책의 다른 실행 검증(목차 3)과 같은 방식이다. 다만 각 함수의 정확한 사전조건·에러 코드 전체 목록까지 다 옮기지는 않았으니, 실제 구현을 시작한다면 헤더 원문을 봐야 한다(확인 필요: 전체 계약). 각 확장 포인트의 API는 배포판 의존이다.
15.4.2 확장 지점의 지도
| 층 | 확장 가능한 것 | 난이도 |
|---|---|---|
| 도구 | RViz2 디스플레이·패널(11.4.8), rqt, CLI 명령 | 낮음~중간 |
| 상위 프레임워크 | Nav2 플러그인(8.5·8.3), MoveIt, ros2_control(6.2·6.3) | 중간 — 가장 흔함 |
| 메시지·인터페이스 | 커스텀 타입(1.2) | 낮음 |
| rclcpp | 실행기·콜백 그룹·할당자·전송 | 중간~높음 |
| rmw | 미들웨어 구현 | 매우 높음 |
| 빌드 | ament 확장·colcon 확장 | 중간 |
견해: 실무에서 만드는 확장의 대부분은 "상위 프레임워크 플러그인"이다. Nav2 컨트롤러·코스트맵 레이어,
ros2_control하드웨어 컴포넌트·컨트롤러, MoveIt 플러그인. 이것들은 설계된 확장 지점이므로 문서와 예제가 있고, 다른 부분에 영향을 주지 않는다.
견해: "확장 지점이 있는지 먼저 찾으라"가 실용적 조언이다. 프레임워크를 포크하거나 소스를 고치기 전에, 의도된 확장 방법이 있는지 확인한다. 대개 있고, 그 경로가 유지보수에 훨씬 유리하다(14.7.6의 포크 회피).
15.4.3 pluginlib
ROS 2 확장의 공통 기반이다.
// 기반 클래스를 상속
namespace my_plugins {
class MyController : public nav2_core::Controller
{
public:
void configure(...) override;
void activate() override;
geometry_msgs::msg::TwistStamped computeVelocityCommands(...) override;
};
}
#include <pluginlib/class_list_macros.hpp>
PLUGINLIB_EXPORT_CLASS(my_plugins::MyController, nav2_core::Controller)
<!-- plugins.xml -->
<library path="my_plugins">
<class type="my_plugins::MyController" base_class_type="nav2_core::Controller">
<description>커스텀 지역 계획기</description>
</class>
</library>
pluginlib_export_plugin_description_file(nav2_core plugins.xml)
| 요소 | 역할 |
|---|---|
| 기반 클래스 | 프레임워크가 정한 인터페이스 |
| 매크로 | 심볼 등록 |
| plugin.xml | 이름↔타입 매핑 |
| CMake 내보내기 | 검색 가능하게 |
| 파라미터로 지정 | 설정에서 이름으로 선택 |
함정: 플러그인 로드 실패의 원인 대부분은 등록 문제다 — 매크로 누락,
plugin.xml경로 오류, CMake 내보내기 누락, 라이브러리 이름 불일치. 오류 메시지가 "플러그인을 못 찾음"으로만 나와 진단이 어렵다. 하나씩 확인하는 체크리스트를 두면 빠르다.
견해: 플러그인 인터페이스가 프레임워크의 계약이다. 기반 클래스의 메서드 의미(언제 호출되는지·무엇을 반환해야 하는지·스레드 안전 요구)를 문서로 확인해야 하며, 추측하면 미묘한 버그가 생긴다. 특히 실시간 경로에서 호출되는지(12.3.6) 여부가 중요하다.
견해: 기존 플러그인의 소스를 읽는 것이 가장 빠른 학습이다. 문서만으로는 계약이 불분명한 경우가 많은데, 기본 제공 구현이 사실상의 참조 구현이다.
15.4.4 상위 프레임워크 확장
| 프레임워크 | 확장 지점 | 장 |
|---|---|---|
| Nav2 | 계획기·컨트롤러·복구·코스트맵 레이어·BT 노드 | 8.3·8.5·8.6·8.7 |
ros2_control | 하드웨어 컴포넌트·컨트롤러 | 6.2·6.3 |
| MoveIt | 계획기·제약·어댑터 | 9.2·9.3 |
| RViz2 | 디스플레이·패널·툴 | 11.4.8 |
| rqt | 플러그인 | 11.5.2 |
robot_localization 등 | 설정 중심 | 7.6 |
견해:
ros2_control하드웨어 컴포넌트가 가장 자주 만들게 되는 확장이다(6.2). 새 로봇을 추가할 때마다 필요하고, 인터페이스가 명확하며, 만들면 컨트롤러·MoveIt·Nav2가 전부 연결된다. 이 확장 하나로 생태계 전체를 얻는 구조가 ROS 2 설계의 이점을 잘 보여준다.
견해: Nav2 코스트맵 레이어가 두 번째로 실용적이다(8.3). 특수 센서·업무 규칙(진입 금지 구역·시간대별 제약)을 반영하는 데 적합하고, 나머지 항법 스택을 그대로 쓸 수 있다.
함정: 플러그인이 실시간·고빈도 경로에서 호출되면 성능 요구가 엄격하다. 컨트롤러(6.3)는 제어 주기마다, 코스트맵 레이어는 갱신마다 호출된다. 할당·로깅·블로킹을 피하는 규율(12.3.6)이 적용된다.
15.4.5 rclcpp 층 확장
| 확장 | 용도 |
|---|---|
| 커스텀 실행기 | 결정성·우선순위(12.5) |
| 커스텀 할당자 | 실시간(12.3.4) |
| 메모리 전략 | 실행기 내부 할당 |
| 콜백 그룹 구성 | 확장이라기보다 활용(2.3) |
| 컨텍스트·노드 옵션 | 세밀한 제어 |
견해: 커스텀 실행기는 만들기 전에 기존 선택지를 소진하라. 콜백 그룹 분리 + 별도 실행기(2.4·12.4.6)로 대부분의 요구가 해결된다. 실행기를 직접 구현하는 것은 rclcpp 내부에 깊이 의존하게 되어 이행 부담이 크다(15.2.5).
견해: 할당자 주입은 상대적으로 접근하기 쉬운 확장이다(12.3.4). 인터페이스가 표준 C++ 할당자 개념을 따르고, 실패해도 영향 범위가 명확하다. 다만 전 경로에 적용되지 않을 수 있다는 한계를 기억해야 한다.
함정: rclcpp 내부 구조는 배포판마다 변한다
(확인 필요). 내부에 의존하는 확장은 이행 때마다 수정해야 하므로, 공개 인터페이스만 사용하는 것이 원칙이다(15.2.5).
15.4.6 rmw 구현
가장 깊은 확장이다.
| 구현해야 할 것 | 비고 |
|---|---|
| 엔티티 생명주기 | 노드·발행자·구독자·서비스 |
| 대기 집합 | 실행기의 기반(2.2.3) |
| 그래프 정보 | ros2 node list 등이 의존 |
| QoS 의미론 | 1.6의 정책을 어떻게 실현할지 |
| 타입 지원 | 직렬화(1.3) |
| 이벤트 | 상태 변화 알림 |
| 보안 | DDS-Security 상당(13.3) |
규모를 숫자로 보면 "매우 높음" 난이도를 가늠할 수 있다. Jazzy 컨테이너에서 rmw.h를 직접 세어 보면(실행 확인) 공개 함수가 71개, 관련 헤더가 45개다 — 노드 생애주기 하나만이 아니라 발행·구독·서비스·클라이언트·가드 컨디션·wait set·QoS 질의·그래프 조회·직렬화까지 전부 이 71개 함수의 계약을 만족해야 한다.
| 범주 | 대표 함수(일부) |
|---|---|
| 노드 생애주기 | rmw_create_node · rmw_destroy_node |
| 발행·구독 | rmw_create_publisher · rmw_publish · rmw_create_subscription · rmw_take · rmw_take_with_info |
| 서비스·클라이언트 | rmw_create_service · rmw_take_request · rmw_create_client · rmw_take_response |
| 대기 집합 | rmw_create_wait_set · rmw_wait |
| 그래프 조회 | rmw_get_topic_names_and_types · rmw_get_node_names |
계약이 실제로 무엇을 요구하는지, 하나를 그대로 옮기면 이렇다. rmw_create_node의 문서 주석(Jazzy 헤더에서 그대로, 실행 확인)은 함수 시그니처 외에 속성 표와 사전조건을 명시한다:
rmw_node_t *
rmw_create_node(
rmw_context_t * context,
const char * name,
const char * namespace_);
| Attribute | Adherence |
|---|---|
| Allocates Memory | Yes |
| Thread-Safe | No |
| Uses Atomics | No [1] |
| Lock-Free | No [1] |
[1] rmw 구현이 정의 — 구현 문서를 확인해야 한다.
rmw_destroy_node는 반환값으로 RMW_RET_OK·RMW_RET_INVALID_ARGUMENT·RMW_RET_INCORRECT_RMW_IMPLEMENTATION·RMW_RET_ERROR를 구분해 반환해야 하고, "이 노드에서 만든 발행자·구독자·서비스·클라이언트가 모두 먼저 파괴돼 있어야 한다"는 사전조건을 명시한다(위반해도 일부 구현만 감지하며 보장되지 않으므로 호출자가 스스로 지켜야 한다). 이 정도 밀도의 계약이 71개 함수 전부에 있다 — "인터페이스가 있으니 구현하면 되겠지"가 왜 조직 차원 작업인지가 여기서 드러난다.
정직: REP 2000이 Jazzy 기준으로 명시하는 rmw 구현 지원 등급도 이 "조직 차원" 판단을 뒷받침한다 — Tier 1은
rmw_fastrtps_cpp(eProsima, 기본값)·rmw_cyclonedds_cpp(Eclipse)·rmw_connextdds(RTI) 세 곳뿐이고,rmw_gurumdds_cpp(GurumNetworks)는 Tier 3다(확인 필요: 확인 시점 이후 변경 여부). 넷 다 회사나 재단이 뒤에 있는 조직이며, 개인 프로젝트로 Tier 1에 오른 rmw 구현은 이 표에 없다.
견해: rmw 구현은 "새 미들웨어를 ROS 2에 연결하는" 작업이며, 조직 차원의 프로젝트다. 개인이나 소규모 팀이 부업으로 할 일이 아니다. 기존 구현(Fast DDS·Cyclone DDS 등)의 소스가 유일한 실질적 참조다.
견해: "우리 전용 전송이 필요하다"는 요구는 대개 다른 방법으로 해결된다. 특수 하드웨어 연결이면 드라이버 노드로(6.1), 특수 네트워크면 DDS 설정이나 브리지로, 성능 문제면 제로 카피·컴포넌트화로(12.3.5·2.7). rmw 구현이 답인 경우는 드물다.
정직: 대체 rmw·전송이 등장하는 영역이므로(3.10.10), 새 미들웨어가 필요하다고 느낀다면 먼저 기존 선택지를 조사하는 것이 순서다
(확인 필요: 현재 선택지).
15.4.7 도구·빌드 확장
| 확장 | 용도 |
|---|---|
CLI 명령(ros2 <새 명령>) | 팀 전용 도구 |
| colcon 확장 | 빌드 절차 |
| ament 확장 | 빌드 매크로 |
| 린터 | 코드 규약 |
| launch 액션 | 커스텀 기동 로직 |
# ros2 CLI 확장 (개념 — setuptools 엔트리포인트, 0.7.2)
entry_points={
'ros2cli.command': ['myteam = my_tools.command.myteam:MyTeamCommand'],
}
견해: 팀 전용 CLI 명령이 의외로 유용하다.
ros2 myteam check처럼 자주 하는 진단·점검을 명령으로 만들면, 현장에서 명령 하나로 실행된다(11.1.5의 수집 스크립트와 같은 발상). 만드는 비용이 낮고 반복 이득이 크다.
견해: 린터를 팀 규약에 맞게 확장하는 것도 값을 한다(14.7.7). "안전 관련 파라미터는 읽기 전용이어야 한다", "콜백에서 로깅 금지" 같은 규약을 자동 검사로 만들면, 리뷰 부담이 줄고 일관성이 유지된다.
15.4.8 만들 것인가 쓸 것인가
| 판단 | 기준 |
|---|---|
| 설정으로 해결 | 가장 저렴 — 먼저 시도 |
| 플러그인 | 의도된 경로 — 유지 가능 |
| 상류 기여 | 시간이 걸리나 부채 없음(15.5) |
| 포크·내부 확장 | 마지막 수단 |
| 비용 | 설명 |
|---|---|
| 작성 | 초기 개발 |
| 이행 유지 | 배포판마다 확인(15.2) |
| 문서화 | 팀이 쓰려면 |
| 디버깅 난이도 | 프레임워크 내부와 얽힘 |
| 지식 집중 | 만든 사람만 앎 |
견해: "확장의 실제 비용은 유지"다(14.1.2의 인터페이스 유지와 같은 논리). 만드는 데 이틀 걸린 플러그인이 배포판 이행마다 반나절씩 들고, 만든 사람이 떠나면 아무도 못 고친다. 정말 필요한지 두 번 묻는 것이 값을 한다.
견해: 가능하면 상류에 기여하라(15.5). 우리에게 필요한 기능이 남에게도 필요하다면, 상류에 들어가는 순간 유지 부담이 사라진다. 시간이 걸리지만 장기적으로 가장 비용이 낮다.
견해: 확장을 만들었다면 문서와 시험을 함께 만들라(14.7.7). 확장은 프레임워크의 계약에 의존하므로, 계약이 바뀌면 조용히 깨진다. 시험이 그 감지 수단이다.
15.4.9 함정 카탈로그
| # | 함정 | 증상 | 대처 |
|---|---|---|---|
| 1 | 확장 지점 찾지 않고 포크 | 유지 부채 | 먼저 조사 |
| 2 | 플러그인 등록 누락 | "못 찾음" 오류 | 체크리스트 |
| 3 | 계약을 추측 | 미묘한 버그 | 문서·참조 구현 |
| 4 | 실시간 경로에서 할당·로깅 | 지터 | 12.3 규율 |
| 5 | 커스텀 실행기 성급히 | 깊은 의존 | 기존 수단 소진 |
| 6 | 내부 API 의존 | 이행 시 파손 | 공개 API |
| 7 | rmw 구현을 소규모로 시도 | 미완성 | 다른 해법 |
| 8 | 확장 유지 비용 미고려 | 부채 누적 | 두 번 묻기 |
| 9 | 상류 기여 검토 안 함 | 영구 유지 | 기여 검토 |
| 10 | 확장에 시험·문서 없음 | 조용한 파손 | 함께 작성 |
| 11 | 지식이 한 사람에 | 유지 불가 | 문서·리뷰 |
| 12 | rmw 함수의 Thread-Safe 속성을 확인 안 함 | 여러 스레드에서 동시 호출 시 미정의 동작 | 각 함수의 Attribute 표 확인 |
| 13 | 사전조건(엔티티 파괴 순서 등) 무시 | 구현에 따라 감지되거나 안 됨 | 문서의 \pre 준수 |
15.4.10 체크포인트
- 확장 지점 여섯 층과 각각의 난이도를 말할 수 있는가.
- 실무에서 가장 흔한 확장 종류는 무엇이며 왜인가.
- "확장 지점이 있는지 먼저 찾으라"는 조언의 근거는?
- pluginlib의 다섯 요소와, 로드 실패의 흔한 원인은?
- 플러그인 계약에서 확인해야 할 것 셋은?
- 상위 프레임워크 확장 여섯 가지 중, 가장 자주 만들게 되는 것과 그 이점은?
- 코스트맵 레이어가 실용적인 이유는?
- 플러그인이 실시간 경로에 있을 때의 규율은?
- rclcpp 층 확장 다섯 가지와, 커스텀 실행기에 대한 경계는?
- 내부 API 의존이 왜 위험한가.
- rmw 구현이 요구하는 것 일곱 가지와, 이것이 조직 차원 프로젝트인 이유는?
- "우리 전용 전송이 필요하다"는 요구의 대안 셋은?
- 도구·빌드 확장 다섯 가지와, 팀 전용 CLI의 가치는?
- 만들 것인가 쓸 것인가의 판단 흐름 네 단계는?
- 확장의 다섯 비용과, "실제 비용은 유지"의 의미는?
- 상류 기여가 장기적으로 가장 비용이 낮은 이유는?
- Jazzy의
rmw.h가 실제로 선언하는 공개 함수는 몇 개이며, 그 규모가 무엇을 말해 주는가? rmw_create_node의 Attribute 표가 명시하는 네 속성과, 그것이 왜 "계약"인가?- REP 2000의 rmw 구현 지원 등급에서 Tier 1과 Tier 3의 차이가 보여 주는 것은?
다음 장 15.5은 제15부의 마지막 — 기여·거버넌스·생태계, 즉 이 소프트웨어가 어떻게 만들어지고 유지되는가를 다룬다.