본문 바로가기
임베디드 개발/리눅스 디바이스 드라이버

LDD ] 커널 시간 관련 함수 사용법

by eteo 2025. 2. 27.

 

 

 

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);