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()
- _usecs_to_jiffies()
2. 저해상도 타이머(timer)
- 초기화, 만료 시간 설정, 콜백 함수 등록, 타이머 활성화 단계
- timer_setup 매크로를 통해 타이머를 초기화
- add_timer 함수는 타이머를 시스템에 등록하여 활성화
- 내부적으로 mod_timer를 사용하여 만료 시간을 현재시간 + 특정시간으로 설정
timer_list 구조체
struct timer_list {
struct hlist_node entry;
unsigned long expires;
void (*function)(struct timer_list *);
u32 flags;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
API
- timer_setup(timer, callback, flags)
- 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);
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);
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");
빌드
- make로 모듈을 빌드
- scp를 사용하여 타겟 보드에 모듈을 복사
- insmod k_timer.ko로 모듈을 적재
- 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
enum hrtimer_restart timer_callback(struct hrtimer *timer)
{
pr_info("hrtimer Callback! Jiffies: %ld\n", jiffies);
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 = ktime_set(TIMEOUT_SEC, TIMEOUT_NSEC);
hrtimer_init(&k_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
k_hrtimer.function = &timer_callback;
hrtimer_start(&k_hrtimer, ktime, HRTIMER_MODE_REL);
return 0;
}
static void __exit k_hrtimer_exit(void)
{
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
- TASK_UNINTERRUPTIBLE
- 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);
hrtimer_start(&hr_timer, ktime, HRTIMER_MODE_REL);
5. RTC(Real Time Clock)
개념
- 시스템 전원이 꺼져도 시간 정보를 유지하는 하드웨어 장치
- PC의 메인보드 배터리가 RTC에 전원을 공급
- SoC 내부에 RTC HW가 존재하며 외부 배터리로 동작
- 절대 시간을 읽고 설정하거나 알람 기능에 활용
- Jiffies는 시스템 부팅 이후의 상대적 시간
- RTC는 xtime 변수에 시간 정보를 저장하며 부팅 시 RTC로부터 읽어와 설정
- NTP(Network Time Protocol) 를 사용하여 인터넷 시간과 동기화
- /dev/rtc 디바이스 파일을 통해 RTC에 접근
- 관련 시스템 콜
- adjtimex()
- settimeofday()
- 사용자 공간 도구
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>
#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;
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);
}
enum hrtimer_restart motor_1_left_timer_callback(struct hrtimer *timer)
{
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);
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:
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);
return IRQ_HANDLED;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = toy_ioctl,
};
static int __init toy_engine_init(void)
{
int ret;
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);
}
module_init(toy_engine_init);
module_exit(toy_engine_exit);
MODULE_LICENSE("GPL");