1. HZ와 jiffies
HZ
초당 발생하는 타이머 인터럽트의 수를 나타내는 상수로, 플랫폼마다 다르지만 일반적으로 250으로 설정된다.
jiffies
커널에서 사용하는 시간 단위의 전역 변수로, 부팅 이후 매 타이머 인터럽트마다 1씩 증가하는 tick 카운터이다. 즉, jiffies는 초당 HZ의 수 만큼 증가하며, HZ가 250으로 설정된 경우 1 jiffy는 1 / HZ = 1 / 250 = 4ms이다.
현재 시스템의 HZ 값을 터미널에서 확인하는 법
현재 실행 중인 커널의 컴파일 시 설정 정보는 /boot/config-$(uname -r) 이름으로 파일시스템에 노출되는데, 해당 파일에서 다음 명령을 통해 HZ 설정값을 확인할 수 있다.
grep CONFIG_HZ /boot/config-$(uname -r)
현재 jiffies의 값을 터미널에서 확인하는 법
sudo cat /proc/timer_list | grep -i jiffies
한편 jiffies를 통해 부팅 이후 경과 시간을 알아내려고 할 때는 주의가 필요한데, 그 이유는 jiffies가 부팅 시 0으로 초기화 되는게 아니라 INITIAL_JIFFIES라는 상수로 초기화되기 때문이다.
jiffies는 unsigned long 타입※으로 64비트 시스템에서는 64비트 크기, 32비트 시스템에서는 32비트 크기를 가지며, 초기값인 INITIAL_JIFFIES는 동일하게 32비트 unsigned 변수의 최대값 거의 직전의 값으로 설정하고 있다. 이는 32비트 시스템에서 혹은 64비트 시스템이더라도 개발자가 데이터 타입을 잘못 처리했을 때 발생할 수 있는 overflow 관련 문제를 빨리 발견할 수 있도록 유도한 것이다.
(※ 참고로 Windows에서는 unsigned long이 32비트 시스템과 64비트 시스템 모두에서 32비트 크기를 가지며, unsigned long long이 64비트 크기를 가진다.)
/*
* Have the 32-bit jiffies value wrap 5 minutes after boot
* so jiffies wrap bugs show up earlier.
*/
#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ))
jiffies 변수 사용시 <linux/jiffies.h>를 포함한다.
#include <linux/jiffies.h>
사용 예시.
unsigned long start = jiffies;
unsigned long elapsed;
msleep(100);
elapsed = jiffies - start;
printk(KERN_INFO "Elapsed jiffies: %lu\n", elapsed);
2. msecs_to_jiffies와 jiffies_to_msecs
밀리초 단위 시간을 jiffies로 변환하거나, 그 반대로 변환하고자 할 때는 <linux/jiffies.h>에 정의된 아래 함수를 사용할 수 있다.
#include <linux/jiffies.h>
unsigned long msecs_to_jiffies(const unsigned int m);
unsigned int jiffies_to_msecs(const unsigned long j);
사용 예시.
unsigned long jiffies = msecs_to_jiffies(100);
unsigned int ms = jiffies_to_msecs(delay_jiffies);
printk(KERN_INFO "in jiffies: %lu, in ms: %u\n", jiffies, ms);
3. 타이머
커널에서 타이머 인터페이스를 사용하고자 할 때는 <linux/timer.h>와 <linux/jiffies.h> 헤더를 포함한다.
#include <linux/timer.h>
#include <linux/jiffies.h>
3.1 timer_list
리눅스 커널에서 소프트웨어 기반 타이머를 구현하기 위해 사용되는 구조체이다.
struct timer_list {
struct hlist_node entry;
unsigned long expires;
void (*function)(struct timer_list *);
u32 flags;
struct lockdep_map lockdep_map;
};
3.2 timer_setup
타이머를 초기화하고, 타이머 만료 시 호출될 콜백 함수를 설정하는 함수이다. flags는 보통 0으로 설정한다.
void timer_setup(struct timer_list *timer, void (*callback)(struct timer_list *), unsigned int flags);
3.3 mod_timer
타이머의 만료 시간을 재설정하는 함수이다. 보통 jiffies와 함께 사용하여 현재 커널의 시간 기준 상대적 만료 시간을 지정한다.
int mod_timer(struct timer_list *timer, unsigned long expires);
3.4 del_timer
타이머를 삭제하는 함수이다.
del_timer()는 타이머를 즉시 삭제하고, del_timer_sync()는 타이머 콜백 함수가 실행 중인 경우 종료될 때까지 대기하고 삭제하는 차이가 있다.
int del_timer(struct timer_list *timer);
int del_timer_sync(struct timer_list *timer);
사용 예시.
struct timer_list my_timer;
// 타이머 콜백 함수
void my_timer_callback(struct timer_list *timer) {
printk(KERN_INFO "Timer expired!\n");
}
// 타이머 설정 및 실행
timer_setup(&my_timer, my_timer_callback, 0);
mod_timer(&my_timer, jiffies + msecs_to_jiffies(100)); // 100ms 후 실행
// 타이머 삭제
del_timer(&my_timer);
4. 시간 비교 함수
<linux/jiffies.h>에 정의된 time_before()와 time_after() 함수는 jiffies 값을 기반으로 시간 비교를 안전하게 수행하기 위한 매크로 함수이다.
- time_before(a, b)는 a가 b보다 이전인지 확인한다.
- time_after(a, b)는 a가 b보다 이후인지 확인한다.
- 참이면 1, 거짓이면 0을 반환한다.
#include <linux/jiffies.h>
#define time_after(a, b) \
((long)((b) - (a)) < 0)
#define time_before(a, b) time_after(b, a)
사용 예시.
unsigned long timeout = jiffies + msecs_to_jiffies(1000); // 1초 후 타임아웃
while (time_before(jiffies, timeout)) {
if (try_to_do_something()) {
printk(KERN_INFO "작업 성공.\n");
break;
}
msleep(10); // 10ms 대기 후 다시 시도
}
5. 딜레이 함수
리눅스 커널에서 작업 지연 함수를 사용하고자 할 때는 <linux/delay.h>를 포함한다.
#include <linux/delay.h>
커널의 딜레이 함수는 CPU 점유 여부에 따라 크게 두 가지로 나눌 수 있다.
1. CPU를 양보하며 대기하는 함수
msleep()과 usleep_range()는 CPU를 양보하며 스케줄링 기반으로 대기를 수행한다.
msleep(100); // 밀리초 단위 대기
usleep_range(500, 1000); // 최소~최대 설정 시간동안 마이크로초 단위 대기
2. CPU를 점유하며 대기하는 함수 (busy-wait)
ndelay(), udelay(), mdely()와 같이 뒤에 -delay가 붙는 함수는 CPU를 점유하며 loop를 기반으로 딜레이를 구현한다.
ndelay(500); // 나노초 단위 대기
udelay(10); // 마이크로초 단위 대기
mdelay(100); // 밀리초 단위 대기
스케줄링 기반의 msleep() 및 usleep_range() 함수는 CPU를 양보하며 동작하므로 설정된 지연시간보다 늦게 반환될 수 있는 반면, ndelay(), udelay(), mdelay()와 같은 함수는 CPU 클럭에 의존하여 루프 기반으로 지연을 수행하기 때문에, 클럭 변화로 인해 예상보다 빠르게 반환될 가능성이 있다.
일반적으로 지연시간 초과는 시스템 설계에 큰 영향을 미치지 않지만, 지연시간 부족은 하드웨어 제약 사항을 위반할 위험이 있어 더욱 신중히 사용해야 하고 헤더파일에서도 이 점을 경고하고 있다.
6. 고해상도 타이머 (hrtimer와 ktime)
일반 타이머 timer_list가 jiffies 기반으로 동작한다면, 고해상도 타이머인 hrtimer는 고해상도 시간 표현 및 연산에 사용되는 ktime과 함께 사용된다.
관련 API는 <linux/hrtimer.h>와 <linux/ktime.h>에 정의되어 있다.
#include <linux/hrtimer.h>
#include <linux/ktime.h>
hr_timer 사용 예시.
#include <linux/module.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
static struct hrtimer my_timer;
static ktime_t interval;
unsigned int count;
enum hrtimer_restart my_timer_callback(struct hrtimer *timer) {
printk(KERN_INFO "타이머 콜백 실행 : %u\n", count++);
// 타이머 재설정
hrtimer_forward_now(timer, interval);
return HRTIMER_RESTART;
}
static int __init my_module_init(void) {
interval = ktime_set(0, 1000000); // 1ms (1,000,000ns)
hrtimer_init(&my_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
my_timer.function = my_timer_callback;
hrtimer_start(&my_timer, interval, HRTIMER_MODE_REL);
printk(KERN_INFO "고해상도 타이머 시작 (1ms 간격).\n");
return 0;
}
static void __exit my_module_exit(void) {
hrtimer_cancel(&my_timer);
printk(KERN_INFO "타이머 종료.\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
ktime 사용 코드 실행 시간 측정 예시.
int i;
ktime_t start, end;
s64 elapsed_time;
// 시작 시간
start = ktime_get();
// 작업 수행
for(i = 0; i < 10000; i++) {}
// 종료 시간
end = ktime_get();
// ktime_sub로 시간 차이 계산 후 ktime_to_ns를 통해 나노초 단위로 변환
elapsed_time = ktime_to_ns(ktime_sub(end, start));
printk(KERN_INFO "작업 수행 시간: %lld ns\n", elapsed_time);
'임베디드 개발 > 리눅스 디바이스 드라이버' 카테고리의 다른 글
LDD ] PCIe 디바이스 드라이버 작성하기 - (3) with Interrupt (0) | 2025.03.03 |
---|---|
Linux Kernel ] Container_of (0) | 2025.02.06 |
Linux Kernel ] Error Handling (0) | 2025.02.03 |
Device tree compiler 사용법 (0) | 2025.01.27 |
LDD ] PCIe 디바이스 드라이버 작성하기 - (2) (0) | 2025.01.15 |