AEGLOSLAB ROS 2 Jazzy 대전

제6부 하드웨어 인터페이스

6.6 실시간 루프와 ros2_control의 제약

6.6.1 이 장에서 배우는 것 / 선수 지식

6.1부터 "실시간"이라는 말을 계속 써 왔고, 6.1.6에서 "ros2_control을 쓰면 실시간이 보장된다는 것은 오해" 라고 명시했다. 이 장은 그 문장을 정확하게 만든다.

무엇이 보장되고 무엇이 아닌가. 제어 루프에서 정확히 무엇이 금지되며 그것을 어떻게 검증하는가. 그리고 실시간이 정말 필요한지 — 이 질문이 의외로 중요하다. 많은 로봇이 실시간 없이도 잘 동작하며, 실시간 시스템의 비용은 크다.

배우는 것:

  • ros2_control이 보장하는 것과 아닌 것 — 정확한 경계.
  • 제어 루프의 금지 목록 — 왜 각각이 문제인가.
  • realtime_tools 전체 — 각 도구의 용도와 원리.
  • 주기 선택 — 통신·계산·제어 요구에서 도출하기.
  • 지터 측정 — 실제로 얼마나 흔들리는지 아는 법.
  • 실시간이 정말 필요한가 — 판단 기준.
  • 실시간 시스템 구축 순서 — 무엇부터.

선수 지식: 6.1~6.5(ros2_control), 2.5(타이머 정밀도·드리프트), 2.6(메모리·할당), 2.9(스레드 안전·우선순위 역전). RT 커널·스케줄링은 12.2, 무할당 경로는 12.3에서 심화한다.

정직: 실시간 성능 수치는 인용하지 않는다(3.6.1의 방침). 하드웨어·커널·부하에 따라 자릿수가 달라지기 때문이다. 이 장은 측정 방법과 지배 요인을 가르친다. realtime_tools API·ros2_control_node의 RT 설정 지원 여부는 배포판 의존이며 (확인 필요)로 표시했다(목차 3).(→ 168차 확인: realtime_tools의 네 도구 전부와 지터 측정 방법론(스레드 수·chrt·pgrep·topic hz)을 Jazzy에서 실측했다 — 6.6.4·6.6.6 실행 확인(168차) 참고. ros2_control_node의 RT 스케줄링 CLI 지원 자체(12.2이 다루는 영역)는 이번에도 시도하지 않았다.)(→ 169차 확인: RealtimePublisher는 168차엔 헤더만 확인했었는데, 이번에 실제 ament_cmake 패키지로 컴파일·실행하고 독립적으로 DDS 발행까지 확인해 네 도구 전부의 실제 실행 확인을 완결했다 — 6.6.4.2 실행 확인(169차) 참고.)

6.6.2 무엇이 보장되고 무엇이 아닌가

ros2_control ✅ 제공하는 것 ❌ 제공하지 않는 것 고정 주기 루프 구조 read→update→write 순서 executor와의 분리 RT 안전 도구 (realtime_tools) OS 스케줄링 보장 코드의 무할당성 하드웨어 통신 지연 최악 지연 상한
항목ros2_control필요한 추가 조치
루프 구조✅ 제공—
실행 순서 결정성✅ 제공—
ROS 통신과의 분리✅ 도구 제공올바르게 사용해야(6.6.4)
스케줄링 우선순위❌RT 우선순위 설정(12.2)
커널 선점성❌PREEMPT_RT(12.2)
CPU 격리❌isolcpus·affinity(12.2)
메모리 잠금·무할당❌mlockall·코드 규율(12.3)
통신 지연❌하드웨어·프로토콜 선택(6.7)
최악 지연 상한❌위 전부 + 측정

*정확한 진술: ros2_control은 실시간을 가능하게 하는 구조를 제공하지만, 실시간성 자체는 시스템 전체가 만든다.*

그리고 사용자 코드가 그것을 깨뜨릴 수 있다. update() 안에서 std::string을 만드는 순간 무할당이 깨지고, 로그 한 줄이 락을 획득한다(6.2.7·6.4.4).

견해: "프레임워크를 썼으니 실시간"이라는 믿음이 가장 위험하다. 측정하지 않으면 지터가 있는지도 모르고, 문제가 생겼을 때 원인을 프레임워크 밖에서 찾게 된다. 6.6.6의 측정을 반드시 하라.

6.6.3 금지 목록과 그 이유

제어 스레드(read()·update()·write())에서 금지되는 것과 근거:

금지왜 문제인가최악 영향
동적 할당malloc이 락을 획득하고, 힙 파편화 시 시간이 길어지며, 커널로 내려가 페이지를 요청할 수 있다수십 µs ~ ms
뮤텍스 대기낮은 우선순위 스레드가 락을 쥐면 우선순위 역전(2.9.5)무제한
로깅위 둘 + I/O + DDS 발행(/rosout — 1.11.3)ms 단위
ROS 통신직렬화·네트워크·블로킹 가능예측 불가
블로킹 I/O무한 대기 가능무제한
페이지 폴트커널이 페이지를 매핑수십 µs ~ ms
예외 던지기스택 언와인딩·RTTI, 구현에 따라 락예측 불가
시스템 호출(불필요한)컨텍스트 스위치µs 단위

"무제한"이 붙은 둘이 특히 위험하다. 뮤텍스 대기와 블로킹 I/O는 상한이 없다 — 최악의 경우 영원히 기다린다. 실시간 시스템에서 이것은 곧 제어 상실이다.

대안 표:

하고 싶은 것실시간 안전한 방법
상태 알리기원자적 카운터 + 별도 진단(1.11.7·6.9)
명령 받기RealtimeBuffer(6.6.4)
데이터 발행RealtimePublisher(6.6.4)
버퍼 필요on_configure에서 사전 할당
오류 표시정수 코드 + 상태 인터페이스
문자열고정 크기 배열·정수 ID
조건부 처리분기는 괜찮음(예측 가능)

할당을 만드는 숨은 것들:

// ❌ 전부 할당을 유발할 수 있다
std::string msg = "error " + std::to_string(code);   // 문자열
vec.push_back(x);                                     // 벡터 성장
std::function<void()> f = [captured]() {...};         // 큰 캡처
auto ptr = std::make_shared<T>();                     // 제어 블록
std::vector<double> tmp(n);                           // 지역 벡터
sstream << value;                                     // 스트림

// ✅ 안전
std::array<double, 8> tmp{};                          // 스택
error_code_.store(code, std::memory_order_relaxed);   // 원자
preallocated_vec_[i] = x;                             // 사전 할당

함정: std::function에 큰 객체를 캡처하면 힙 할당이 일어난다. 콜백을 실시간 경로에 두는 설계는 이 점을 확인해야 한다. 작은 캡처는 SBO(small buffer optimization)로 스택에 들어가지만 보장되지 않는다.

6.6.4 realtime_tools 상세

6.6.4.1 RealtimeBuffer — 비실시간 → 실시간

realtime_tools::RealtimeBuffer<Command> buffer_;

// 비실시간(구독 콜백)
buffer_.writeFromNonRT(cmd);

// 실시간(update)
const Command * cmd = buffer_.readFromRT();

원리: 이중 버퍼 + 원자적 포인터 교체(2.9.4의 패턴).

성질설명
읽기 측절대 블로킹되지 않는다
쓰기 측락을 쓸 수 있다(비실시간이므로 허용)
값항상 최신 완결된 값
손실빠르게 여러 번 쓰면 중간값은 유실

함정: Command 타입이 힙을 쓰면(std::vector 멤버) 복사 시 할당이 일어난다. writeFromNonRT는 비실시간이라 괜찮지만, 읽기 측이 값을 복사하면 실시간에서 할당이 된다. readFromRT()가 포인터를 반환하는 이유이며, 복사하지 말고 포인터로 읽으라.

실행 확인(168차, Jazzy) — 위 writeFromNonRT/readFromRT 패턴을 실제로 컴파일·실행해 핵심 주장(비블로킹·값 완결성)을 정량으로 확인했다. 비실시간 쓰기 스레드(50µs 간격)와 '실시간' 읽기 스레드(블로킹 없이 최대한 자주 읽음)를 2초간 동시 실행했다 — 쓰기 13,152회, 읽기 23,771,005회(초당 약 1,200만 회 — "절대 블로킹되지 않는다"는 표의 주장과 일치), 읽기 최대 지연 74.6µs(버퍼 자체가 아니라 OS 스케줄링 변동으로 추정), 찢어지거나 역전된 값 0건(2,300만 번 넘는 동시 읽기 동안 단 한 번도 불완전한 값을 못 봄) — "값은 항상 최신 완결된 값"이라는 표의 주장이 정량으로 확인됐다. 원본: tools/gap_harness/gap168/rt_buffer_test/(test_realtime_buffer.cpp·run_result.log).

6.6.4.2 RealtimePublisher — 실시간 → 비실시간

auto pub = node->create_publisher<Msg>("~/state", qos);
auto rt_pub = std::make_shared<realtime_tools::RealtimePublisher<Msg>>(pub);

// on_configure — 크기 미리 확보 (6.4.5)
rt_pub->msg_.data.resize(n);

// update() — 실시간
if (rt_pub->trylock()) {
  rt_pub->msg_.data[0] = value;
  rt_pub->unlockAndPublish();
}

원리: 별도 스레드가 실제 발행을 담당한다. update()는 버퍼에 쓰고 조건변수로 신호만 보낸다.

성공 실패 update() (실시간) trylock() msg_ 채우기 unlockAndPublish() = 신호만 이번 주기 건너뜀 발행 스레드 (비실시간) 직렬화 + DDS 발행

함정: trylock() 없이 lock()을 쓰면 블로킹된다. 실시간 경로에서는 항상 trylock()이며, 실패하면 건너뛴다. 상태 발행 한 번을 놓치는 것이 주기를 놓치는 것보다 낫다(6.4.4).

함정: msg_의 크기를 update()에서 바꾸면 할당이다. resize·push_back을 실시간에서 하면 RealtimePublisher를 쓰는 의미가 없어진다.

실행 확인(169차, Jazzy) — 위 trylock/unlockAndPublish 패턴을 실제 ament_cmake 패키지로 빌드해 실행하고, 독립적으로 DDS 발행까지 확인했다. rclcpp::Node+SingleThreadedExecutor로 실제 스핀시키며 5kHz 흉내 루프로 3초간 실행한 결과 trylock 성공 11,728~11,845회, 실패 13~31회(성공률 99.7~99.9%)였다 — "실패하면 건너뛴다" 설계와 일치한다. 별도 프로세스에서 ros2 topic hz로 독립 확인한 결과 평균 3880.249Hz로 실제 DDS 메시지가 발행되고 있었다 — "별도 스레드가 실제 발행을 담당한다"는 원리 설명이 확인됐다. 원본: tools/gap_harness/gap169/rt_pub_test/(test_realtime_publisher.cpp·run_result2.log·topic_hz_during_run2.log).

6.6.4.3 RealtimeBox — 객체 교환

포인터나 객체를 실시간·비실시간 사이에서 안전하게 교환한다(확인 필요: Jazzy에서의 API 형태)(→ 168차 확인: RealtimeBox는 Jazzy에서 폐기(deprecated)됐고 현재 이름은 RealtimeThreadSafeBox다 — 아래 실행 확인(168차) 참고).

도구방향용도
RealtimeBuffer비RT → RT명령·파라미터
RealtimePublisherRT → 비RT상태 발행
RealtimeBox양방향객체 참조 교환
RealtimeServerGoalHandleRT ↔ 액션액션 목표 처리(확인 필요)(→ 168차 확인: 실재하며 폐기 표시 없음 — 아래 실행 확인(168차) 참고)

견해: 이 도구들은 "실시간 안전"을 이름에 걸고 있지만, 잘못 쓰면 안전하지 않다. 담는 타입이 힙을 쓰거나, 크기를 실시간에서 바꾸거나, lock()을 쓰면 깨진다. 도구를 쓰는 것과 실시간 안전한 것은 다르다 — 측정으로 확인해야 한다(6.6.6).

실행 확인(168차, Jazzy) — RealtimeBox·RealtimeServerGoalHandle의 정확한 API 형태를 소스로 확인했다. realtime_box.hpp를 직접 읽으니, RealtimeBox는 여전히 쓸 수 있지만 컴파일 시 #pragma message로 명시적 폐기 경고가 출력된다 — "'RealtimeBox' is deprecated. Please update your code to use 'realtime_thread_safe_box.hpp' header, and class name 'RealtimeThreadSafeBox' instead." 내부적으로 RealtimeBox<T> = RealtimeBoxStandard<T> = RealtimeThreadSafeBox<T, std::mutex>다. RealtimeBoxBestEffort도 별도로 폐기돼 있는데(RealtimeBox 대신 쓰라고 안내), 그 RealtimeBox 자체도 폐기라 이중 폐기 체인이다 — Jazzy에서 RealtimeBox라는 이름은 여전히 동작하지만 폐기됐고, 현재 권장 이름은 RealtimeThreadSafeBox다. realtime_server_goal_handle.hpp에서는 template <class Action> class RealtimeServerGoalHandle을 확인했다 — setAborted·setCanceled·setSucceeded·setFeedback·execute·valid·runNonRealtime 메서드가 실재하고 폐기 표시가 없다. 원본: tools/gap_harness/gap168/rt_tools_test/(header_list.log·realtime_box_hpp.log·realtime_server_goal_handle_hpp.log).

6.6.5 주기 선택

6.3.7의 부등식을 다시 본다:

$$T_{\text{주기}} > t_{\text{read}} + t_{\text{update}} + t_{\text{write}} + t_{\text{여유}}$$

각 항의 지배 요인:

항지배 요인전형적 크기
$t_{\text{read}}$·$t_{\text{write}}$하드웨어 통신프로토콜·버스 속도 의존(6.7)
$t_{\text{update}}$컨트롤러 계산대개 작음(관절 수에 비례)
$t_{\text{여유}}$OS 지터·기타시스템 구성 의존

대부분의 경우 통신이 지배한다. 시리얼 1 Mbps로 관절 6개의 상태를 읽고 명령을 쓰면 프레임 크기와 왕복 지연이 주기를 정한다.

제어 요구에서 도출하기:

대상필요 주기(대략)
이동 로봇 속도 제어20~100 Hz
관절 위치 제어(하드웨어가 서보)50~250 Hz
관절 토크 제어500 Hz ~ 수 kHz
힘 제어·접촉 작업1 kHz 이상
시각 서보카메라 주기(30~60 Hz)

정직: 위 수치는 일반적으로 알려진 범위이며 시스템에 따라 크게 다르다(확인 필요). 제어 대역폭 요구는 로봇 동역학·관절 유연성·목표 성능에서 나오며, 이론적 배경은 자매서 robotics(RB-63·RB-66)가 다룬다.

절차:

아니오 예 주기 결정 1. read+write 시간 측정 2. 제어 요구 대역폭 확인 3. 둘 중 제약이 큰 쪽 4. 여유 3~5배 확보 5. 실측 분포 확인 p99.9가 주기 안? 주기 낮추기 또는 통신 최적화 확정

견해: 통신 시간을 먼저 측정하라(6.3.7의 반복). "1 kHz로 하고 싶다"에서 시작하면 안 되는 이유는, 통신이 그것을 허용하지 않을 수 있기 때문이다. 하드웨어 선택 단계에서 이미 주기 상한이 정해진다 — 이것이 6.7에서 버스 선택이 중요한 이유다.

6.6.6 지터 측정

설정값이 아니라 실측이 진실이다(2.5.6의 반복).

① 컴포넌트 안에서 측정:

// read()/write() 소요 시간 — 실시간 안전하게
hardware_interface::return_type MyRobotHardware::read(
    const rclcpp::Time &, const rclcpp::Duration & period)
{
  const auto t0 = std::chrono::steady_clock::now();     // 1.10.2

  // ... 실제 읽기 ...

  const auto dt_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
      std::chrono::steady_clock::now() - t0).count();

  // 원자적 갱신만 — 할당·로깅 없이
  read_time_ns_.store(dt_ns, std::memory_order_relaxed);
  if (dt_ns > read_max_ns_.load(std::memory_order_relaxed)) {
    read_max_ns_.store(dt_ns, std::memory_order_relaxed);
  }

  // 주기 지터도 기록
  const double period_ms = period.seconds() * 1000.0;
  period_hist_.record(period_ms);     // 락프리 히스토그램 (사전 할당)

  return hardware_interface::return_type::OK;
}

② 진단으로 내보내기(6.9):

지표의미
주기 p50·p99·p99.9·max지터 분포(2.5.6)
read() 최대 시간통신 최악값
write() 최대 시간동일
주기 초과 횟수지연된 횟수

③ 외부 측정:

# 제어 루프 주기의 대리 지표
ros2 topic hz /joint_states --window 1000

# 스레드 확인 (프로세스 이름으로 정확히 — 아래 함정 참고)
PID=$(pgrep -x ros2_control_node)
ps -T -p $PID
top -H -p $PID

# 스케줄링 정책·우선순위 확인 — 스레드마다 다를 수 있다
chrt -p $PID                                   # 메인 스레드만
for t in /proc/$PID/task/*; do chrt -p $(basename $t); done   # 전 스레드
ps -eLo pid,tid,cls,rtprio,comm | grep ros2_control

함정: chrt -p <PID>는 메인 스레드 하나만 보여 준다. ROS 2 노드는 DDS 송수신·트레이싱까지 스레드가 열 개를 훌쩍 넘고(실측 사례는 15개, 12.2.11.2), 정책이 스레드마다 다를 수 있다. /proc/<PID>/task를 순회해 전부 확인하지 않으면 "RT로 설정했다"는 착각이 남는다.

함정: pgrep -f(전체 명령줄 매칭)는 자기 자신까지 매칭한다. 이 책의 다른 진단 예시도 -f를 쓰지만, 정확히는 pgrep -x가 안전하다 — 이유와 대안은 11.1.13에 정리했다.(→ 168차 정정: 이 절이 예시로 쓰는 바로 그 프로세스 이름 ros2_control_node(17자)에서 pgrep -x가 실제로 실패함을 확인했다 — 아래 실행 확인(168차) 참고. 리눅스 커널의 comm 필드가 15자로 잘리기 때문이며, pgrep도 스스로 경고를 낸다.)

실행 확인(168차, Jazzy) — 스레드 수·chrt·pgrep·topic hz를 실제 ros2_control_node로 확인했다. mock_components/GenericSystem + joint_state_broadcaster로 실제 노드를 실행하고 측정했다.

  • 스레드 수: 37개(/proc/PID/task 기준) — 책이 인용한 "실측 사례 15개"(12.2.11.2)보다도 많다. "열 개를 훌쩍 넘는다"는 정성적 주장이 실측으로 확인됐다. chrt -p <메인PID>는 정확히 1개 스레드만 보여 줬고, 37개 전체를 보려면 /proc/PID/task/*를 순회해야 했다 — 위 함정 그대로 재현됐다(이 미조정 상태에서는 37개 전부 SCHED_OTHER, 우선순위 0으로 동일).
  • pgrep -x ros2_control_node가 조용히 매칭 0건이었다. ros2_control_node는 17자인데 리눅스 커널의 comm 필드는 15자로 잘린다(ros2_control_no). pgrep -x는 잘린 comm과 정확히 일치해야 매칭하므로, 원래 이름으로 호출하면 매칭이 안 된다(pgrep 자신도 경고를 출력한다: "pattern... longer than 15 characters will result in zero matches"). 대안 셋을 비교했다 — pgrep -x ros2_control_no(잘린 이름, 동작), pidof ros2_control_node(정상 동작), pgrep -f '[r]os2_control_node'(대괄호 트릭, 동작하지만 래퍼 프로세스까지 4개가 매칭됨). 위 함정의 "정확히는 pgrep -x가 안전하다"는 조언은, 이 절 자신이 예시로 쓰는 프로세스 이름에서는 성립하지 않는다 — 15자를 넘는 이름에는 pidof나 잘린 이름을 쓰거나 대괄호 트릭을 써야 한다.
  • ros2 topic hz /joint_states --window 200: 평균 99.99Hz(설정한 update_rate: 100과 정확히 일치, std dev 0.09~0.47ms) — 제어 루프 주기의 대리 지표라는 주장을 정량으로 확인했다.

컨테이너 gap168j. 원본: tools/gap_harness/gap168/thread_test/(ps_T_result.log·chrt_all_threads.log·topic_hz_result.log).

④ 시스템 수준 지터 측정:

# 커널 스케줄링 지연 (RT 시스템 표준 도구)
sudo cyclictest -t1 -p 80 -n -i 1000 -l 100000
# (rt-tests 패키지 — 12.2)

cyclictest가 시스템의 실시간 능력 상한을 알려준다. 이 값보다 좋은 지터는 애플리케이션에서 얻을 수 없다. 시스템이 1 ms 지터를 보이면 500 µs 제어 주기는 불가능하다.

견해: 순서가 중요하다. ① cyclictest로 시스템 능력 확인 → ② read/write 시간 측정 → ③ 주기 결정 → ④ 실제 분포 검증. 이 순서를 건너뛰고 주기부터 정하면 나중에 되돌아온다.

⑤ 트레이싱(11.6):

ros2_tracing으로 제어 루프의 각 단계를 커널 수준에서 추적할 수 있다. 측정 코드가 대상을 방해하지 않는 장점이 있다.

6.6.7 실시간이 정말 필요한가

이 절이 이 장에서 가장 실무적일 수 있다.

아니오 (하드웨어가 서보) 예 ~100 Hz 100~500 Hz 500 Hz 이상 예 실시간이 필요한가? 제어 루프가 로봇 안에 있나? ❌ 대개 불필요 주기 요구가 몇 Hz인가? ⚠️ 대개 불필요 일반 커널로 충분한 경우가 많다 ⚠️ 상황에 따라 ✅ 필요 안전 기능이 이 루프에 있나? ✅ 필요 (최악 지연 상한이 요구됨)

실시간이 불필요한 경우:

상황이유
하드웨어가 자체 서보 제어상위는 목표만 보내면 됨
이동 로봇 속도 제어(20~50 Hz)여유가 크다
위치 명령 전달(하드웨어가 보간)타이밍 민감도 낮음
시각 서보(카메라가 병목)30 Hz가 상한

실시간이 필요한 경우:

상황이유
토크·전류 제어높은 대역폭
힘 제어·접촉불안정화 위험
안전 감시 루프최악 지연 상한이 요구
EtherCAT 분산 클럭프로토콜이 요구(6.7)
고속 동역학 로봇안정성

견해: "실시간이면 좋겠지"로 도입하지 말라. RT 커널 유지·CPU 격리·무할당 코드 검증은 실제 비용이고, 개발 속도를 떨어뜨린다. 요구가 명확할 때만 지불하라. 많은 이동 로봇과 협동 로봇 응용이 일반 커널에서 잘 동작한다.

견해: 반대로 안전 기능이 그 루프에 있다면 실시간은 협상 대상이 아니다. "평균적으로 빠르다"는 안전 논증이 아니다 — 최악 지연에 상한이 있어야 하며, 그것을 보장하려면 RT 구성과 측정이 필요하다(14.10).

중간 지대: 제어 루프는 일반 커널에서 실행하되, 안전 감시만 하드웨어나 별도 MCU로 분리하는 구성이 실용적이다(6.8). 비상 정지·한계 감시를 소프트웨어 실시간에 의존하지 않는 것이다.

6.6.8 구축 순서

실시간 시스템을 만들기로 했다면 순서가 있다(2.6.8의 확장).

아니오 예 1. 요구 정의 주기 · 최악 지연 상한 2. 시스템 능력 측정 cyclictest 3. 통신 시간 측정 read/write 4. 주기 결정 5. 코드 규율 무할당 · 무로깅 · 무락 6. RT 커널 · 우선순위 12.2 7. CPU 격리 · affinity 12.2 8. mlockall · 워밍업 12.3 9. 측정 · 검증 분포 확인 요구 만족? 병목 식별 → 해당 단계로 운영 중 상시 감시

1·2번이 먼저인 이유: 요구를 모르면 언제 멈출지 모르고, 시스템 능력을 모르면 달성 불가능한 목표를 세운다.

5번(코드 규율)이 6~8번보다 먼저인 이유: RT 커널을 켜도 update()에서 할당하면 소용없다. 애플리케이션 코드가 먼저 정리돼 있어야 시스템 튜닝의 효과가 보인다(2.6.8의 순서 논리와 같다).

9번 검증:

확인방법
주기 분포p99.9·max(6.6.6)
할당 여부페이지 폴트 카운터(2.6.7)
우선순위 적용chrt -p
CPU 격리taskset -p·/proc/*/status
장시간 안정성수 시간 이상 연속 측정

견해: 장시간 측정이 중요하다. 몇 분 실행해서는 안 나타나는 문제가 있다 — 메모리 파편화 누적, 로그 회전, 주기적 시스템 작업(cron·업데이트 확인). 최소 몇 시간, 가능하면 하루 이상 실행해 최악값을 보라.

운영 중 상시 감시:

// 주기 초과를 진단으로 (6.9)
if (period.seconds() > period_limit_) {
  ++overrun_count_;      // 원자적
}
// 별도 진단 타이머가 overrun_count_ 를 보고

현장에서 지터가 악화되는 것을 조기에 아는 것이 목적이다. 부하 증가·하드웨어 열화·설정 변경이 원인일 수 있다.

6.6.9 함정 카탈로그

#함정증상대처
1"프레임워크 = 실시간"으로 믿음지터를 모른 채 운영측정(6.6.6)
2update()에서 문자열·벡터 성장할당 지터사전 할당·정수 코드
3RealtimeBuffer 값 복사실시간에서 할당포인터로 읽기
4RealtimePublisher lock() 사용블로킹trylock()
5msg_ 크기를 실시간에서 변경할당on_configure에서
6뮤텍스 대기우선순위 역전(무제한)락프리·try_lock
7블로킹 I/O무한 대기 가능타임아웃·논블로킹
8주기부터 정하고 통신 맞춤루프 밀림측정 후 결정
9시스템 능력 미확인달성 불가 목표cyclictest 먼저
10코드 규율 없이 RT 커널효과 없음순서 준수(6.6.8)
11짧게 측정하고 확정드문 최악값 놓침장시간 측정
12필요 없는데 실시간 도입비용만 증가요구 판단(6.6.7)
13안전 기능을 소프트 실시간에 의존안전 논증 불가하드웨어·MCU 분리
14운영 중 감시 없음열화를 모름주기 초과 진단
15chrt -p <PID> 만 확인메인 스레드만 보여 RT인 줄 착각/proc/<PID>/task 순회(12.2.11.2)
16pgrep -x로 15자 넘는 프로세스명 검색(168차 실측 발견)조용히 매칭 0건pidof, 잘린 이름, 또는 pgrep -f '[문자]나머지'

6.6.10 체크포인트

  • ros2_control이 제공하는 것 네 가지와 제공하지 않는 것 네 가지를 구분할 수 있는가.
  • "프레임워크를 썼으니 실시간"이 왜 위험한 믿음인가.
  • 제어 루프 금지 목록에서 "무제한" 영향을 갖는 둘은 무엇이며 왜인가.
  • 할당을 유발하는 숨은 코드 패턴 다섯 가지를 들 수 있는가.
  • RealtimeBuffer의 읽기 측이 포인터를 반환하는 이유는?
  • RealtimePublisher에서 trylock()을 쓰고 실패 시 건너뛰는 것이 왜 옳은가.
  • "도구를 쓰는 것과 실시간 안전한 것은 다르다"의 의미를 예로 설명할 수 있는가.
  • 제어 주기 부등식의 각 항과 지배 요인을 말할 수 있는가. 대부분의 경우 무엇이 지배하는가.
  • 주기 결정 절차 다섯 단계를 순서대로 말할 수 있는가. 왜 통신 측정이 먼저인가.
  • cyclictest가 알려주는 것과, 그것이 왜 첫 번째 측정인가.
  • 실시간이 불필요한 경우와 필요한 경우를 각각 세 가지 들 수 있는가. 안전 기능이 있으면 왜 협상 대상이 아닌가.
  • 실시간 구축 순서에서 코드 규율이 RT 커널보다 먼저인 이유는?
  • 장시간 측정이 필요한 이유와, 운영 중 상시 감시의 목적은?

다음 장 6.7는 이 루프의 지배 요인이었던 하드웨어 통신 자체를 다룬다 — 시리얼·CAN·EtherCAT의 특성과 드라이버 작성 원칙.