타이머 드라이버(timer, hrtimer)

sungho·2025년 1월 5일

디바이스 드라이버

목록 보기
10/12

1. HZ(진동수)와 Jiffies

진동수(HZ)

  • HZ는 초당 발생하는 타이머 인터럽트 횟수
  • 100HZ는 1초에 100번(10ms마다 인터럽트가 발생)
  • 250HZ는 1초에 250번(4ms마다 인터럽트가 발생)
  • 32-bit ARM 머신은 100HZ, 64-bit 라즈베리파이는 250HZ를 사용
  • HZ 값에 따른 트레이드오프
    • 높은 HZ
      • 더 정밀한 시간 제어 가능
      • 더 많은 CPU 자원 소모(Throughput 감소)
    • 낮은 HZ
      • CPU 자원 소모 감소
      • 시간 정밀도 저하(Latency 증가)

Jiffies

  • Jiffies는 시스템 부팅 이후 경과한 타이머 틱(tick) 수를 나타내는 전역 변수
  • 타이머 인터럽트가 발생할 때마다 1씩 증가
  • 저해상도 타이머 설정에 Jiffies를 활용
    • 500ms 후에 타이머를 설정하려면 현재 jiffies 값에 msecs_to_jiffies(500)를 더한 값을 만료 시간으로 설정

Jiffies 오버플로우

  • 32-bit 시스템에서 jiffies는 약 49.7일 후에 오버플로우가 발생
  • 방지하기 위해 64-bit jiffies_64 변수가 사용
  • time_after(), time_before(), time_after_eq(), time_before_eq()와 같은 매크로를 사용하여 오버플로우를 고려한 시간 비교를 수행

Jiffies 관련 함수

static inline unsigned long msecs_to_jiffies(const unsigned int m)
{
    if (__builtin_constant_p(m)) {
        if ((int)m < 0)
            return MAX_JIFFY_OFFSET;
        return _msecs_to_jiffies(m);
    } else {
        return __msecs_to_jiffies(m);
    }
}

static inline unsigned long _usecs_to_jiffies(const unsigned int u)
{
    return (u + (USEC_PER_SEC / HZ) - 1) / (USEC_PER_SEC / HZ);
}
  • msecs_to_jiffies()
    • 밀리초(ms)를 Jiffies로 변환
  • _usecs_to_jiffies()
    • 마이크로초(µs)를 Jiffies로 변환

2. 저해상도 타이머(timer)

  • 초기화만료 시간 설정콜백 함수 등록타이머 활성화 단계
  • timer_setup 매크로를 통해 타이머를 초기화
    • 내부적으로 setup_timer를 사용
  • add_timer 함수는 타이머를 시스템에 등록하여 활성화
    • 내부적으로 mod_timer를 사용하여 만료 시간을 현재시간 + 특정시간으로 설정

timer_list 구조체

struct timer_list {
    struct hlist_node entry;   // 타이머 리스트
    unsigned long expires;     // 만료 시간 (Jiffies 단위)
    void (*function)(struct timer_list *); // 타임아웃 시 호출될 콜백 함수
    u32 flags;                 // 타이머 동작 제어 플래그 ex) TIMER_DEFERRABLE, TIMER_IRQSAFE
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

API

  • timer_setup(timer, callback, flags)
    • 타이머를 초기화
    • setup_timer를 호출
  • add_timer(timer)
    • 타이머를 시스템에 등록하여 활성화
    • 내부적으로 mod_timer를 사용하여 만료 시간을 현재시간 + 특정시간으로 설정
  • del_timer(timer)
    • 타이머를 삭제
  • mod_timer(timer, expires)
    • 타이머의 만료 시간을 변경

k_timer.c

#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/timer.h>static struct timer_list test_timer;

// 타이머 콜백 함수
void test_timer_callback(struct timer_list *t)
{
    pr_info("Timer Callback! Jiffies: %ld\n", jiffies);
    // 타이머 재설정 (500ms 후)
    mod_timer(&test_timer, jiffies + msecs_to_jiffies(500));
}

static int __init k_timer_init(void)
{
    pr_info("k_timer module loaded\n");

    // 타이머 초기화
    timer_setup(&test_timer, test_timer_callback, 0);

    // 타이머 만료 시간 설정 (500ms 후)
    mod_timer(&test_timer, jiffies + msecs_to_jiffies(500));

    return 0;
}

static void __exit k_timer_exit(void)
{
    // 타이머 삭제
    del_timer(&test_timer);
    pr_info("k_timer module unloaded\n");
}

module_init(k_timer_init);
module_exit(k_timer_exit);
MODULE_LICENSE("GPL");

빌드

  1. make로 모듈을 빌드
  2. scp를 사용하여 타겟 보드에 모듈을 복사
  3. insmod k_timer.ko로 모듈을 적재
  4. dmesg를 통해 로그를 확인

한계

  • 밀리초(ms) 이하의 정밀한 제어가 어려움(250HZ 기준 4ms가 한계)

3. 고해상도 타이머(hrtimer)

  • 저해상도 타이머의 한계를 극복하기 위해 도입되었습니다.
  • 마이크로초(µs) 단위의 정밀한 제어가 가능합니다.
  • HRTIMER_MODE_REL (상대적 시간) 또는 HRTIMER_MODE_ABS (절대적 시간) 모드로 설정할 수 있습니다.

관련 시스템 콜

  • nanosleep
  • itimers
  • posix-timers

k_hrtimer.c

#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/hrtimer.h>#include <linux/ktime.h>#define TIMEOUT_NSEC 1000000000L // 1초#define TIMEOUT_SEC 4           // 4초static struct hrtimer k_hrtimer;

// 타이머 콜백 함수
enum hrtimer_restart timer_callback(struct hrtimer *timer)
{
    pr_info("hrtimer Callback! Jiffies: %ld\n", jiffies);
    // 4초 후로 타이머 재설정 (상대적 시간)
    hrtimer_forward_now(timer, ktime_set(TIMEOUT_SEC, TIMEOUT_NSEC));
    return HRTIMER_RESTART;
}

static int __init k_hrtimer_init(void)
{
    ktime_t ktime;

    pr_info("k_hrtimer module loaded\n");

    // ktime 설정 (4초)
    ktime = ktime_set(TIMEOUT_SEC, TIMEOUT_NSEC);

    // hrtimer 초기화 (상대적 시간 모드)
    hrtimer_init(&k_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    k_hrtimer.function = &timer_callback;

    // hrtimer 시작 (4초 후)
    hrtimer_start(&k_hrtimer, ktime, HRTIMER_MODE_REL);

    return 0;
}

static void __exit k_hrtimer_exit(void)
{
    // hrtimer 취소
    hrtimer_cancel(&k_hrtimer);
    pr_info("k_hrtimer module unloaded\n");
}

module_init(k_hrtimer_init);
module_exit(k_hrtimer_exit);
MODULE_LICENSE("GPL");

4. 지연 처리

짧은 지연

  • Busy waiting 방식 사용
  • mdelay(), udelay(), ndelay() 함수 활용

긴 지연

  • schedule_timeout()
    • 지정된 시간 동안 프로세스를 sleep 상태로 전환
    • TASK_INTERRUPTIBLE
      • 시그널에 의해 sleep이 중단
    • TASK_UNINTERRUPTIBLE
      • 시그널에 의해 sleep이 중단되지 않음
  • usleep_range(min, max)
    • min과 max 사이의 마이크로초(µs) 동안 sleep
  • hrtimer를 이용한 정밀 sleep 코드
#include <linux/hrtimer.h>#include <linux/ktime.h>// ...

ktime_t ktime;
ktime = ktime_set(0, 100000000L); // 100ms

hrtimer_start(&hr_timer, ktime, HRTIMER_MODE_REL);
// 100ms 동안 sleep

// ...
  • usleep
    • 마이크로초 단위 sleep

5. RTC(Real Time Clock)

개념

  • 시스템 전원이 꺼져도 시간 정보를 유지하는 하드웨어 장치
  • PC의 메인보드 배터리가 RTC에 전원을 공급
  • SoC 내부에 RTC HW가 존재하며 외부 배터리로 동작
  • 절대 시간을 읽고 설정하거나 알람 기능에 활용
  • Jiffies는 시스템 부팅 이후의 상대적 시간
  • RTC는 xtime 변수에 시간 정보를 저장하며 부팅 시 RTC로부터 읽어와 설정
  • NTP(Network Time Protocol) 를 사용하여 인터넷 시간과 동기화
  • /dev/rtc 디바이스 파일을 통해 RTC에 접근
  • 관련 시스템 콜
    • adjtimex()
      • RTC 시간 조정
    • settimeofday()
      • 시스템 시간 설정
  • 사용자 공간 도구
    • hwclock
      • RTC 시간 조회 및 설정

6. 타이머를 활용한 엔진 모듈

구성

  • 모터 1
    • 좌측 LED 4개 (2개씩 짝지어 좌/우 번갈아 깜박임)
  • 모터 2
    • 우측 LED 4개 (2개씩 짝지어 좌/우 번갈아 깜박임)

기능

  • 속도 조절
    • 1~100 사이의 값으로 LED 깜박임 속도 조절
    • 1: 2초 주기, 100: 20ms 주기
  • 정지
    • 버튼 인터럽트를 통해 구현
    • 모든 타이머 종료 및 LED OFF(Emergency Stop)
#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/hrtimer.h>#include <linux/ktime.h>#include <linux/gpio.h>#include <linux/interrupt.h>#include <linux/ioctl.h>// ... (GPIO 핀 정의)

#define MOTOR_1_SET_SPEED _IOW('w', '1', int32_t *)
#define MOTOR_2_SET_SPEED _IOW('w', '2', int32_t *)

// ... (타이머 구조체, 변수 정의)

static struct hrtimer motor_1_left_timer;
static struct hrtimer motor_1_right_timer;
static struct hrtimer motor_2_left_timer;
static struct hrtimer motor_2_right_timer;

static int motor_1_speed = 1; // 초기 속도
static int motor_2_speed = 1;

static ktime_t motor_1_left_ktime;
static ktime_t motor_1_right_ktime;
static ktime_t motor_2_left_ktime;
static ktime_t motor_2_right_ktime;

// ... (LED 제어 함수)

void motor_1_left_led_on(void) {
    gpio_set_value(MOTOR_1_LED_LEFT_1, 1);
    gpio_set_value(MOTOR_1_LED_LEFT_2, 1);
}

void motor_1_left_led_off(void) {
    gpio_set_value(MOTOR_1_LED_LEFT_1, 0);
    gpio_set_value(MOTOR_1_LED_LEFT_2, 0);
}

// ... (다른 LED 제어 함수들도 동일하게 구현)

// 타이머 콜백 함수
enum hrtimer_restart motor_1_left_timer_callback(struct hrtimer *timer)
{
    // LED 토글
    if (gpio_get_value(MOTOR_1_LED_LEFT_1)) {
        motor_1_left_led_off();
    } else {
        motor_1_left_led_on();
    }

    // 타이머 재설정
    hrtimer_forward_now(timer, motor_1_left_ktime);
    return HRTIMER_RESTART;
}

// ... (다른 타이머 콜백 함수들도 동일하게 구현)

// 모터 속도 설정 함수
long toy_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int32_t speed;

    switch (cmd) {
        case MOTOR_1_SET_SPEED:
            if (copy_from_user(&speed, (int32_t *)arg, sizeof(int32_t)))
                return -EFAULT;
            motor_1_speed = speed;
            motor_1_left_ktime = ktime_set(0, (2000 / motor_1_speed) * 1000000L); // ns 단위로 변환
            motor_1_right_ktime = ktime_set(0, (2000 / motor_1_speed) * 1000000L);
            hrtimer_start(&motor_1_left_timer, motor_1_left_ktime, HRTIMER_MODE_REL);
            hrtimer_start(&motor_1_right_timer, motor_1_right_ktime, HRTIMER_MODE_REL);
            break;
        case MOTOR_2_SET_SPEED:
            // ... (MOTOR_1_SET_SPEED와 동일하게 구현)
            break;
        default:
            return -ENOTTY;
    }

    return 0;
}

// 인터럽트 핸들러
static irqreturn_t button_irq_handler(int irq, void *dev_id)
{
    // 모든 타이머 정지
    hrtimer_cancel(&motor_1_left_timer);
    hrtimer_cancel(&motor_1_right_timer);
    hrtimer_cancel(&motor_2_left_timer);
    hrtimer_cancel(&motor_2_right_timer);

    // 모든 LED 끄기
    // ...

    return IRQ_HANDLED;
}

// ... (파일 오퍼레이션 구조체, 모듈 초기화/종료 함수)

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = toy_ioctl,
    // ...
};

static int __init toy_engine_init(void)
{
    int ret;
    // ... (문자 디바이스 드라이버 등록)

    // GPIO 초기화
    // ...

    // 타이머 초기화
    hrtimer_init(&motor_1_left_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    motor_1_left_timer.function = &motor_1_left_timer_callback;

    // ... (다른 타이머 초기화)

    // 인터럽트 등록
    ret = request_irq(gpio_to_irq(BUTTON_GPIO), button_irq_handler, IRQF_TRIGGER_FALLING, "button_irq", NULL);
    if (ret) {
        // ... (에러 처리)
    }

    return 0;
}

static void __exit toy_engine_exit(void)
{
    // 타이머 취소
    hrtimer_cancel(&motor_1_left_timer);
    // ... (다른 타이머 취소)

    // 인터럽트 해제
    free_irq(gpio_to_irq(BUTTON_GPIO), NULL);

    // GPIO 해제
    // ...

    // 문자 디바이스 드라이버 등록 해제
    // ...
}

module_init(toy_engine_init);
module_exit(toy_engine_exit);
MODULE_LICENSE("GPL");

0개의 댓글