# 실시간 노드 구현을 위한 타이머 활용 전략

#### 타이머의 역할과 개념

실시간 시스템에서 타이머는 노드의 주기적인 작업을 제어하는 핵심 메커니즘이다. 타이머는 설정된 주기에 따라 작업을 수행하며, 이를 통해 실시간 성능을 보장할 수 있다. 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에서는 타이머를 설정할 때, 주기적으로 호출될 콜백 함수와 함께 설정한다. 타이머 콜백은 타이머가 만료될 때마다 호출되며, 이를 통해 실시간 작업을 수행한다. 예시는 다음과 같다:

```cpp
#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()` 함수를 사용하여 실시간 스케줄링 정책을 적용하고, 우선순위를 조정하는 방식이다.

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

```cpp
#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. **우선순위 반전 문제 해결**: 우선순위 상속 메커니즘을 통해 높은 우선순위 작업이 차단되지 않도록 함.
