리눅스 운영체제 상에 커널타이머는 100Hz로 설정되어있다.(그래서 f=100HZ인 것이다.)
그러므로 타이머 틱값은 10ms마다 1씩 증가한다.(T=1/100=10ms)
그래서 우리는 이 타이머를 이용하여 1초마다 타이머 인터럽트가 발생하기를 기대하고 있는 것이다. (100*10ms=1sec)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#define DEBUG 1
static int timerVal = 100; // 커널타이머 f=100HZ, T=1/100=10ms, 100*10ms=1sec
static int ledVal = 0;
struct timer_list timerLed; // 이 타이머 구조체가 있어야 타이머를 가져다가 사용할 수 있음
module_param(timerVal, int, 0);
module_param(ledVal, int, 0);
void kerneltimer_registertimer(unsigned long timeover);
void kerneltimer_func(struct timer_list *t);
void kerneltimer_registertimer(unsigned long timeover)
{
timer_setup(&timerLed, kerneltimer_func, 0); // 일정시간마다 등록된 함수포인터를 실행하겠다는 뜻
timerLed.expires = get_jiffies_64() + timeover; // 10ms * 100 = 1sec // 만료시간을 현재 틱 + 100으로 설정
add_timer(&timerLed); // 커널 타이머 등록
}
void kerneltimer_func(struct timer_list *t)
{
#if DEBUG
printk("ledVal : %#04x\n", (unsigned int)(ledVal));
#endif
ledVal = ~ledVal & 0xff; // 보수 취해주고 최하위 1바이트만 보겠다는 뜻
mod_timer(t, get_jiffies_64() + timerVal); // 타이머는 1회성이기 때문에 계속 쓸라면 갱신해줘야한다. 그때 쓰는 함수가 mod_timer()이다.
}
int kerneltimer_init(void)
{
#if DEBUG
printk("timerVal : %#04x\n", (unsigned int)(timerVal));
#endif
kerneltimer_registertimer(timerVal);
return 0;
}
void kerneltimer_exit(void)
{
if (timer_pending(&timerLed)) // pending()은 타이머가 등록되어있는지 안등록되있는지 확인하는 함수
del_timer(&timerLed); // 등록되어있다면 타이머 삭제
/* 그냥 생짜로 del_timer()를 쓰지 않은 이유는... */
// 타이머가 만료되어 kerneltimer_func() 콜백으로 함수가 실행되고,
// "mod_timer(t, get_jiffies_64() + timerVal);"가 실행되기 전까지
// 순간적으로 타이머가 존재하지 않는 타이밍이 있기 때문이다.
// del_timer(NULL)이 들어가면 exception이 나면서 프로그램이 죽기 때문에, 위에처럼 if문으로 한번 감싸서 예외처리를 해준 것이다.
}
module_init(kerneltimer_init);
module_exit(kerneltimer_exit);
MODULE_AUTHOR("KCCI-AIOT KSH");
MODULE_DESCRIPTION("led key test module");
MODULE_LICENSE("Dual BSD/GPL");
file 포인터 구조체 안에 void* 멤버변수가 있어서 그걸로 전역변수 ledVal
의 기능을 대체할 수 있다.
MCU에서 타이머는 아주 핵심적인 역할을 담당한다.
리눅스 운영체제에서 헤르츠 값에 대한 정의는 커널 소스의 .config
파일에 정의되어있다.
위 사진은 라즈베리파이에 올라가는 커널 소스의 config
파일인데, 라즈베리파이는 커널 헤르츠가 100hz로 설정되어있는 것을 확인할 수 있다.
즉 라즈베리파이는 10ms마다 문맥교환을 할 수 있다는 것이다. 이 값은 우분투의 경우 4ms로 설정되어있다. 헤르츠 값이 차이나는 이유는 두 기기의 컴퓨팅 파워가 다르기 때문이다. 라즈베리파이는 최소 10ms는 줘야 프로세스에서 어느정도 작업을 진행하고 문맥교환을 해줄 수 있는데, 우분투는 성능이 좋다보니까 4ms만 줘도 프로세스에서 왠만한 작업을 다 진행하고 문맥교환을 할 수 있다.
긴 지연은 hz를 기반으로 지연시간을 만들어서 jiffies값이랑 비교하여 그동안 while() 무한 루프에 묶어두는 식으로 처리한다.
#define DELAY_TIME_MSEC (3*HZ /10)
u64 endtime = get_jiffies_64() + DELAY_TIME_MSEC;
while(jiffies < endtime)
/*nothing*/ ;
라즈베리파이를 기준으로 보면 Hz는 100이다. 그러므로 (3*HZ /10)
의 계산 결과는 30
이다. 라즈베리파이가 한번 헤르츠를 도는데 T=1/100=10ms
이므로, 결과적으로 (3*HZ /10)
는 300ms(=30*10ms)
만큼 기다리겠다는 말이 된다.