디바이스 드라이버7 - 커널 타이머

kenGwon·2024년 2월 26일
0

[Embedded Linux] BSP

목록 보기
30/36

모듈 프로그래밍으로 커널타이머 맛보기

리눅스 운영체제 상에 커널타이머는 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에서 타이머는 아주 핵심적인 역할을 담당한다.

hz

리눅스 운영체제에서 헤르츠 값에 대한 정의는 커널 소스의 .config파일에 정의되어있다.

위 사진은 라즈베리파이에 올라가는 커널 소스의 config 파일인데, 라즈베리파이는 커널 헤르츠가 100hz로 설정되어있는 것을 확인할 수 있다.
즉 라즈베리파이는 10ms마다 문맥교환을 할 수 있다는 것이다. 이 값은 우분투의 경우 4ms로 설정되어있다. 헤르츠 값이 차이나는 이유는 두 기기의 컴퓨팅 파워가 다르기 때문이다. 라즈베리파이는 최소 10ms는 줘야 프로세스에서 어느정도 작업을 진행하고 문맥교환을 해줄 수 있는데, 우분투는 성능이 좋다보니까 4ms만 줘도 프로세스에서 왠만한 작업을 다 진행하고 문맥교환을 할 수 있다.

짧은 지연 처리

  • mdelay(): 밀리초단위의 지연
  • udelay(): 마이크로초 단위의 지연
  • ndelay(): 나노초 단위의 지연

긴 지연 처리

긴 지연은 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)만큼 기다리겠다는 말이 된다.

profile
스펀지맨

0개의 댓글