Art of Pr0gr4m

[Linux Kernel 5] Kernel Timer (jiffies & ktime) 본문

IT/Linux Kernel

[Linux Kernel 5] Kernel Timer (jiffies & ktime)

pr0gr4m 2020. 5. 2. 19:17

프로세서는 내부에 타이머를 내장하고 있다

IRQ 0번은 시스템 타이머이고 IRQ 8번은 RTC이다

OS를 만들어본 경험이 있다면, 대부분 해당 인터럽트를 기반으로 스케줄링을 진행했을 것이다

 

또한 클럭 카운트를 저장하는 tsc 레지스터도 있으며

로컬 APIC 타이머도 존재한다

OS를 만들어본 경험이 있다면 APIC는 떠올리고 싶지 않을 것이다

 

 

 

1. jiffies

 

jiffy는 인터럽트 사이의 시간 간격을 의미하며

jiffies(지피 값)는 타이머에서 발생한 인터럽트 값이다

타이머 인터럽트는 일정한 간격으로 생성되며, 1초당 인터럽트가 호출되는 값을 HZ 상수로 정의한다

따라서 jiffies는 1초에 HZ 값만큼 숫자가 증가한다

참고로 커널 메뉴에서 Timer frequency 값을 바꾸면 HZ 값을 바꿀 수 있다

 

구버전 커널에서는 지피값을 전역변수 jiffies로 직접 이용하였다

현재는 get_jiffies_64() 함수를 이용하여 지피값을 얻어올 수 있다

 

 

 

2. 시간 지연

 

유저 프로세스에서 sleep을 사용하는 것과 같이 커널에서도 시간 지연을 할 수 있다

방법에는 여러가지가 있지만, 크게 두 가지 방법을 알아본다

 

jiffies를 이용한 busy waiting

 

다음과 같은 매크로가 있다

#define time_after(a,b)		\
	(typecheck(unsigned long, a) && \
	 typecheck(unsigned long, b) && \
	 ((long)((b) - (a)) < 0))
#define time_before(a,b)	time_after(b,a)
 
#define time_after_eq(a,b)	\
	(typecheck(unsigned long, a) && \
	 typecheck(unsigned long, b) && \
	 ((long)((a) - (b)) >= 0))
#define time_before_eq(a,b)	time_after_eq(b,a)

 

1초에 HZ 값만큼 jiffies가 증가한다고 했으니,

원하는 초만큼 지연을 시키려면 다음과 같은 코드를 생각할 수 있다

while (time_before(get_jiffies_64(), get_jiffies_64() + sec * HZ));

 

위 코드는 sec 초만큼 busy waiting을 하며 시간을 지연시킨다

물론, 실제 지연을 위해서 위와 같은 코드를 사용하진 않는다 (스핀락도 아니고..)

 

schedule_timeout 함수를 이용한 delay

long schedule_timeout(long timeout);
long schedule_timeout_interruptible(long timeout);
long schedule_timeout_killable(long timeout);
long schedule_timeout_uninterruptible(long timeout);
long schedule_timeout_idle(long timeout);

 

schedule_timeout() 함수는 타임아웃 시간동안 스케줄링 대기를한다

interruptible과 uninterruptible은 이름에서 알 수 있는 것과 같이 인터럽트 허용 여부를 정한다

killable 함수는 대기 중인 루틴 (모듈)이 죽을 수 있는 지 여부를 정한다

 

인자로 들어가는 timeout 값은 초 단위가 아닌 지피값 단위이다

따라서 HZ * sec 값을 인자로 주게 된다

10초를 지연하는 간단한 예제 코드는 다음과 같다

 

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/jiffies.h>
 
int __init delay_init(void)
{
	schedule_timeout_interruptible(10 * HZ);
 
	return 0;
}
 
void __init delay_exit(void)
{
}
 
module_init(delay_init);
module_exit(delay_exit);
 
MODULE_LICENSE("Dual BSD/GPL");

 

 

 

3. ktime

 

jiffies와 HZ 값을 이용해서도 시간 측정을 할 수 있지만,

마이크로초 및 나노초와 같은 정교한 시간 측정에는 한계가 있다

 

정교한 시간 측정을 위해서 rdtsc, xtime, current_kernel_time, do_gettimeofday 등 여러가지 인터페이스가 있었지만

현재는 간단히 ktime을 이용할 수 있다

ktime에 대한 자세한 내용은 다음 링크를 참고한다

ktime 함수의 프로토타입은 다음과 같다

ktime_t ktime_get(void);
void ktime_get_raw_ts64(struct timespec64 *ts);
void ktime_get_ts64(struct timespec64 *ts);
void ktime_get_real_ts64(struct timespec64 *tv);
void ktime_get_coarse_ts64(struct timespec64 *ts);
void ktime_get_coarse_real_ts64(struct timespec64 *ts);

 

다음은 현재 시간은 ns 단위로 출력하고, ktime을 기반으로 타이머(hrtimer)를 등록하는 예제이다

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
 
unsigned long timer_interval_ns = 1e6;
static struct hrtimer hr_timer;
 
enum hrtimer_restart timer_callback(struct hrtimer *timer_for_restart)
{
	ktime_t currtime, interval;
	currtime = ktime_get();
	interval = ktime_set(0, timer_interval_ns);
	hrtimer_forward(timer_for_restart, currtime, interval);
	return HRTIMER_RESTART;
}
 
static int __init timer_init(void)
{
	ktime_t ktime = ktime_set(0, timer_interval_ns);
	printk("ktime_get_ns : %llu\n", ktime_get_ns());
	hrtimer_init(&hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	hr_timer.function = &timer_callback;
	hrtimer_start(&hr_timer, ktime, HRTIMER_MODE_REL);
	return 0;
}
 
static void __exit timer_exit(void)
{
	int ret = hrtimer_cancel(&hr_timer);
	if (ret)
		printk("The timer was still in use...\n");
	printk("HR Timer module uninstalling\n");
}
 
module_init(timer_init);
module_exit(timer_exit);
 
MODULE_LICENSE("GPL");

 

 

 

4. proc 타이머 예제 소스

 

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/ktime.h>
#include <linux/timer.h>
#include <linux/seq_file.h>
#include <asm/processor.h>
#include <asm/tsc.h>
 
static int proc_show(struct seq_file *m, void *v)
{	
	unsigned long m_jiffy = get_jiffies_64();
 
	unsigned long long cycles = 0;
 
	struct timespec64 m_timespec;
 
	seq_printf(m, "[Message] HZ: %d\n", HZ);
	seq_printf(m, "[Message] jiffies: %lu\n", m_jiffy);
	seq_printf(m, "[Message] jiffies after ls: %lu\n", m_jiffy + HZ);
 
	seq_printf(m, "[Message] uptime in min: %lu\n",
			m_jiffy / HZ / 60);
	seq_printf(m, "[Message] uptime in sec: %lu\n",
			m_jiffy / HZ);
 
	seq_printf(m, "[Message] ktime: %llu\n", ktime_get());
	ktime_get_ts64(&m_timespec);
	seq_printf(m, "[Message] get_ts64: %20llu.%09lu\n",
			m_timespec.tv_sec, m_timespec.tv_nsec);
	ktime_get_raw_ts64(&m_timespec);
	seq_printf(m, "[Message] get_raw_ts64: %20llu.%09lu\n",
			m_timespec.tv_sec, m_timespec.tv_nsec);
	ktime_get_real_ts64(&m_timespec);
	seq_printf(m, "[Message] get_real: %20llu.%09lu\n",
			m_timespec.tv_sec, m_timespec.tv_nsec);
	ktime_get_coarse_ts64(&m_timespec);
	seq_printf(m, "[Message] get_coarse: %20llu.%09lu\n",
			m_timespec.tv_sec, m_timespec.tv_nsec);
	ktime_get_coarse_real_ts64(&m_timespec);
	seq_printf(m, "[Message] get_coarse_real: %20llu.%09lu\n",
			m_timespec.tv_sec, m_timespec.tv_nsec);
 
	cycles = get_cycles();
	seq_printf(m, "[Message] cycles: %llu\n", cycles);
	seq_printf(m, "[Message] cpu_khz: %u\n", cpu_khz);
 
	return 0;
}
 
static int proc_open(struct inode *inode, struct file *file)
{
	return single_open(file, proc_show, NULL);
}
 
static const struct proc_ops proc_fops = {
	.proc_open = proc_open,
	.proc_read = seq_read,
	.proc_lseek = seq_lseek,
	.proc_release = single_release
};
 
static int __init timer_init(void)
{
	proc_create("timer", 0, NULL, &proc_fops);
	return 0;
}
 
static void __exit timer_exit(void)
{
	remove_proc_entry("timer", NULL);
}
 
module_init(timer_init);
module_exit(timer_exit);
 
MODULE_LICENSE("GPL");

 

proc_show에서 지피값과 ktime 값들을 출력하고 있다

/proc 디렉토리 하위에 timer 엔트리를 등록하여 해당 파일을 읽으면 시간 값을 알 수 있다

(procfs는 애초에 간단한 시스템 정보를 유저에 제공하는 것이 목적이므로

타이머 예제는 procfs 인터페이스를 이용하기에 매우 적합하다)

 

 

 

5. 실행 결과

 

/proc/timer 출력 결과

 

proc 하위에 등록된 timer 엔트리를 출력하면 보는 것과 같이 정상적으로 시간 관련 값들을 출력한다

정교한 시간 측정이 필요하면 위와 같이 ktime을 사용할 수 있다