타이머의 역할과 개념

실시간 시스템에서 타이머는 노드의 주기적인 작업을 제어하는 핵심 메커니즘이다. 타이머는 설정된 주기에 따라 작업을 수행하며, 이를 통해 실시간 성능을 보장할 수 있다. ROS2에서는 rclcpp::Timer 클래스를 통해 타이머를 설정하고 사용할 수 있다.

타이머는 일반적으로 다음 두 가지 방식으로 사용된다: 1. 정해진 시간 간격으로 주기적 작업 수행: 주기적으로 데이터를 수집하거나, 상태를 업데이트하는 작업에 활용된다. 2. 딜레이 후 작업 수행: 특정 조건이 만족될 때까지 대기하거나, 일정 시간 후 작업을 수행하는 데 유용하다.

실시간 노드를 위한 주기 설정

타이머 활용의 첫 번째 단계는 주기 설정이다. 주기는 노드가 얼마나 자주 작업을 수행해야 하는지를 결정한다. 주기를 잘못 설정하면 실시간 성능이 저하되거나, 노드의 응답 속도가 늦어질 수 있다. 일반적으로 실시간 작업은 일정한 주기를 유지해야 하므로, 타이머의 설정과 관리가 매우 중요하다.

주기는 다음과 같은 수식으로 설정할 수 있다:

T_{\text{period}} = \frac{1}{f_{\text{desired}}}

여기서, - T_{\text{period}}는 타이머의 주기 (초), - f_{\text{desired}}는 원하는 작업 수행 빈도 (Hz)이다.

예를 들어, 주기가 10ms인 작업은 f_{\text{desired}} = 100 \, \text{Hz}로 설정할 수 있다.

타이머의 설정과 노드 통합

ROS2에서는 타이머를 설정할 때, 주기적으로 호출될 콜백 함수와 함께 설정한다. 타이머 콜백은 타이머가 만료될 때마다 호출되며, 이를 통해 실시간 작업을 수행한다. 예시는 다음과 같다:

#include "rclcpp/rclcpp.hpp"

class MyNode : public rclcpp::Node
{
public:
  MyNode() : Node("my_node")
  {
    // 100ms 주기로 콜백을 실행하는 타이머 설정
    timer_ = this->create_wall_timer(
      std::chrono::milliseconds(100),
      std::bind(&MyNode::timer_callback, this));
  }

private:
  void timer_callback()
  {
    // 실시간 작업 수행
    RCLCPP_INFO(this->get_logger(), "Timer callback triggered");
  }

  rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char *argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MyNode>());
  rclcpp::shutdown();
  return 0;
}

위 코드에서는 100ms 주기로 timer_callback 함수를 실행하는 타이머를 설정하였다. 이와 같이 주기적으로 콜백을 실행함으로써, 실시간 노드가 원하는 작업을 일정한 간격으로 수행할 수 있다.

실시간 타이머에서 고려할 요소

실시간 시스템에서는 다음 요소들을 고려해야 한다:

  1. 정확한 주기 유지: 타이머는 지정한 주기를 정확히 유지해야 하며, 지연이나 스케줄링 문제를 최소화해야 한다.
  2. 처리 시간: 타이머 콜백 내의 작업이 주기 내에 완료되지 않으면 다음 작업이 지연될 수 있으므로, 처리 시간을 최소화하는 것이 중요하다.
  3. 우선순위 제어: 중요한 작업일수록 높은 우선순위를 부여하여 타이머가 지연되지 않도록 설정해야 한다.

타이머와 실시간 우선순위 제어

실시간 시스템에서 타이머의 주기적 실행이 정확하게 유지되려면, 노드의 우선순위를 적절하게 설정해야 한다. 리눅스 기반 시스템에서는 실시간 스케줄링 정책과 스레드 우선순위를 조정하여 이를 구현할 수 있다.

ROS2에서는 노드의 스레드를 관리할 때 pthread 라이브러리를 사용하여 우선순위를 설정할 수 있다. sched_setscheduler() 함수를 사용하여 실시간 스케줄링 정책을 적용하고, 우선순위를 조정하는 방식이다.

다음은 실시간 스레드 우선순위를 설정하는 예시이다:

#include <pthread.h>
#include <sched.h>

void set_realtime_priority()
{
  struct sched_param param;
  param.sched_priority = 99; // 최고 우선순위 설정

  if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &param) != 0) {
    perror("Failed to set real-time priority");
  }
}

위 코드는 현재 스레드의 우선순위를 99로 설정하여 실시간 우선순위를 최대로 설정하는 방법을 보여준다. SCHED_FIFO 스케줄링 정책은 우선순위가 높은 작업을 먼저 실행하는 정책이며, 이를 통해 실시간 타이머의 정밀도를 높일 수 있다.

실시간 타이머의 동작 보장

타이머의 정확한 주기적 동작을 보장하려면 시스템 전반에 걸쳐 우선순위 반전이나 지연(Latency) 문제를 피해야 한다. 이러한 문제는 실시간 시스템에서 중요한 문제이며, 특히 타이머의 정확한 실행 주기를 유지하는 데 직접적인 영향을 미친다.

우선순위 반전 문제

우선순위 반전은 높은 우선순위를 가진 작업이 낮은 우선순위를 가진 작업에 의해 차단될 때 발생하는 문제이다. 실시간 시스템에서 이 문제를 방지하기 위해 우선순위 상속(Priority Inheritance) 메커니즘을 사용할 수 있다. 이는 낮은 우선순위 작업이 높은 우선순위 작업에 의해 차단되지 않도록 보장한다.

지연 문제

실시간 시스템에서 타이머 지연은 다양한 요인으로 발생할 수 있다. 이를 방지하기 위해서는 타이머 콜백 내의 작업을 가능한 한 최소화하고, 비동기 처리를 통해 작업의 부하를 분산하는 것이 중요하다.

\Delta t_{\text{latency}} = T_{\text{expected}} - T_{\text{actual}}

여기서, - T_{\text{expected}}는 예상된 실행 시간, - T_{\text{actual}}은 실제 실행 시간이다. - \Delta t_{\text{latency}}는 지연 시간이다.

지연 시간을 최소화하기 위해서는 타이머 콜백의 실행 시간을 줄이는 최적화가 필요하다.

실시간 타이머 활용 전략 요약

  1. 주기 설정: 원하는 주기(frequency)를 기반으로 타이머를 설정하고, 필요한 작업을 콜백 함수에 배정.
  2. 우선순위 관리: 실시간 스케줄링 정책을 사용해 노드의 스레드 우선순위를 조정.
  3. 지연 문제 방지: 타이머 콜백의 작업 부하를 줄이고, 비동기 처리로 시스템의 응답성을 높임.
  4. 우선순위 반전 문제 해결: 우선순위 상속 메커니즘을 통해 높은 우선순위 작업이 차단되지 않도록 함.