ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 디바이스 드라이버 / 시간 처리와 커널 타이머
    Linux/Linux Device Driver 2020. 9. 1. 12:45

    디바이스 드라이버를 작성하다 보면 시간에 따른 여러 가지 처리가 필요하다. 커널은 이런 시간에 따른 처리를 하기 위해 jiffies라는 전역 변수를 여러 시간의 기준이 되는 시간값으로 사용하고 있다. 디바이스 드라이버는 이 기준값을 이용하여 상대적인 시간의 흐름을 측정하고, 그에 따라 하드웨어를 제어하게 된다.

     

    HZ: 1초당 발생되는 타이머 인터럽트 횟수

    USER_HZ: HZ값을 보정하는 수

    jiffies: 초당 HZ 만큼 증가하는 전역 변수

    jiffies_64: 초당 HZ 만큼 증가하는 전역 변수

    get_giffies_64(): jiffies_64를 참조하기 위한 함수

     

     

    #include <linux/jiffies.h> : 커널 디렉터리/include/linux/jiffies.h

    u64 get_jiffies_64(void)

    +#include <asm/param.h> : 커널 디렉터리/arch/arm/include/generated/uapi/asm/param.h

    ++#include <asm-generic/param.h> : 커널 디렉터리/include/asm-generic/param.h

    #define HZ

    #define USER_HZ

     

    ※ jiffies_64 변수를 직접 사용하기 보다는 가급적 get_jiffies_64() 함수를 이용하여 시스템의 현재 시간을 참조하도록 권장하고 있다.

     

     

    시간처리

    하드웨어를 제어하다 보면 종종 일부러 동작 시간을 지연시켜야 할 경우가 필요하다. 예를들어 어떤 하드웨어와 통신을 하려고 할때 통신하기 전 하드웨어 초기화가 필요한 경우가 있다. 이때 하드웨어에 따라서 짧은 지연 시간이나 긴 지연 시간이 필요하다.(초기화가 아직 안됬는데 통신을 하려고 데이터를 읽거나 쓰면 예기치못한 일이 발생할 수도 있다.)

     

    짧은 지연의 처리

    #include <linux/delay.h> : 커널 디렉터리/include/linux/delay.h

    #define mdelay(n)

    void ndelay(unsigned int x)

    +#include <asm/delay.h> : 커널 디렉터리/arch/arm/include/asm/delay.h

    #define udelay(n)

     

    이 함수들을 사용하면 이 시간 동안 CPU는 아무런 일도 하지 않기 때문에 시스템이 정지 된다.

    ※ 1s = 1,000ms = 1,000,000us = 1,000,000,000ns

     

    긴 지연의 처리

    디바이스 드라이버에서 1ms 이상의 시간을 지연시키는 루틴을 사용하는 것은 다른 방법을 이용해서라도 가급적 피해야 한다.  그러나 어쩔 수 없이 비교적 긴 시간 동안 지연시켜야 한다면 jiffies 변수와 while문을 이용해 지연시켜야 된다.

     

    #define DELAY_TIME_MSEC   (3*HZ/10)

    u64 endtime = get_giffies_64() + DELAY_TIME_MSEC;

    while(jiffies < endtime);

     

    여기서 주의할 젓은 정확한 지연 시간을 보장하지 않는다는 점이다.

     

    아니 어쩌라고..

     

    그래서 커널 타이머가 등장한다. 특정 함수를 동작하기 전 지연을 해야한다면 함수 전에 위와 같은 코드를 작성할 것이다. 하지만 커널 타이머를 사용하면 만료시간과 함수를 등록해서 사용하기 때문에 코드도 깔끔하고 정확한 지연 시간을 보장한다.

    ※ 참고로 등록된 함수는 인터럽트 핸들러가 수행 후 실행되는 SoftIRQ(후반부 처리)로 동작한다.

     

    커널 타이머

    디바이스 드라이버를 작성하다 보면 하드웨어 상태를 주기적으로 감시하거나 하드웨어를 제어한 이후에 정상적으로 동작하는지 감시하기 위해 정해진 시간이 초과되면 특정 함수를 수행하는 기능이 필요할 때가 많다. 이를 위해 리눅스 커널은 커널 타이머라는 기능을 제공한다.

     

    커널 타이머는 타이머 인터럽트를 통해 구현된다. 리눅스 커널은 타이머 인터럽트가 발생하면 스케줄링에 필요한 처리를 끝낸 후 커널 타이머 목록이라는 데이터 구조를 검사한다. 커널 타이머 목록은 수행할 함수와 함수가 수행되어야하는 시간에 대한 정보를 담고 있는 연결 리스트 구조체다. 이 구조체의 시간 필드를 검사하여 정해진 시간을 초과한 함수가 있다면 커널은 해당 함수를 호출한다. 이때 수행이 끝난 함수를 포함한 구조체는 커널 타이머 목록에서 제거된다.

     

    커널 타이머에 의해 수행되는 함수는 타이머 인터럽트 서비스 함수에서 수행이되기 때문에 인터럽트 서비스 루틴과 같은 성격을 갖는다. 그러므로 커널 타이머에 등록된 함수의 구현 방법은 인터럽트 서비스 함수와 같은 형식으로 처리해야 한다. 

     

    커널 타이머를 제거할 때 주의할점이 있다.

    커널 타이머가 등록된 이후에 정해진 시간이 초과하여 커널 타이머에 등록된 함수가 호출되면 등록된 커널 타이머는 자동으로 제거된다. 그렇다고 해서 커널 타이머의 임의 제거가 필요없다는 뜻은 아니다. 커널 타이머에 등록된 함수가 모듈에 의해 삽입된 디바이스 드라이버의 함수라고 한다면 모듈이 제거되었을 때 제거된 번지에 속해있던 함수가 호출될 경우도 발생할 수 있다. 왜냐하면 커널은 호출되는 함수의 주소가 유효한지 아닌지를 해당 함수를 호출하기 전에 검사하지 않기 때문이다. 그래서 디바이스 드라이버는 등록된 커널 타이머가 확실하게 호출되어 제거될 것이라는 확신이 있더라도 모듈 형식으로 디바이스 드라이버를 작성했다면 디바이스 드라이버의 종료 루틴에 del_time() 함수를 호출하여 등록된 커널 타이머를 제거해야 한다.

     

     

    #include <linux/timer.h> : 커널 디렉터리/include/linux/timer.h

    struct timer_list{

    struct hlist_node entry;

    unsigned long expire;

    void (*function)(struct timer_list *);

    u32 flags;

    #ifdef CONFIG_LOCKDEP

    struct lockdep_map lockdep_map;

    #endif

    };

    #define timer_setup(timer, callback, flags)

    void add_timer(struct timer_list *timer)

    int mod_timer(struct timer_list *timer, unsigned long expires)

    int del_timer(struct timer_list *timer)

     

    ※ 4.19.115버전 커널의 timer_list 구조체는

    unsigned long data 없고

    void (*function)(unsigned long)가 아닌 void (*function)(struct timer_list *)다.

     

     

     

    이 예제는 0.1초 간격으로 LED를 점멸시키는 코드다.

    응용프로그램은 사용하지 않았는 예제이기 때문에 디바이스 파일을 만들 필요가 없다.

     

     

    pi@raspberrypi:~/Downloads/devicedriver/timer $ nano timer_test_gpio.h

    #define GPIO_BASE	0x3F200000
    #define GPIO_SIZE	180
    
    #define GPIO_IN(g)	(*(gpio + (g / 10)) &= ~(7 << ((g % 10) * 3)))
    #define GPIO_OUT(g)	(*(gpio + (g / 10)) |= (1 << ((g % 10) * 3)))
    #define GPIO_SET(g)	(*(gpio + 7) = 1 << g)
    #define GPIO_CLR(g)	(*(gpio + 10) = 1 << g)
    #define GPIO_GET(g)	(*(gpio + 13) & (1 << g))
    
    #define GPIO_LED	18
    
    volatile unsigned *gpio;

     

     

    pi@raspberrypi:~/Downloads/devicedriver/timer $ nano timer_test_driver.c

    #include <linux/module.h>
    #include <linux/io.h>
    #include <linux/timer.h>
    
    #include "timer_test_gpio.h"
    
    #define DELAY_TIME_MSEC		(1*HZ/10)
    
    struct timer_list timer;
    
    void led_on_off(struct timer_list *timer)
    {
    	if(GPIO_GET(GPIO_LED) == 0)
    	{
    		GPIO_SET(GPIO_LED);
    	}
    	else
    	{
    		GPIO_CLR(GPIO_LED);
    	}
    
    	mod_timer(timer, get_jiffies_64() + DELAY_TIME_MSEC);
    }
    
    void kernel_timer_register(void)
    {
    	timer_setup(&timer, led_on_off, 0);
    	timer.expires = get_jiffies_64() + DELAY_TIME_MSEC;
    	add_timer(&timer);
    }
    
    int timer_test_init(struct inode *inode, struct file *file)
    {
    	void *map;
    
    	printk("timer_test_init\n");
    
    	map = ioremap(GPIO_BASE, GPIO_SIZE);
    
    	if(!map)
    	{
    		printk("ioremap error\n");
    
    		return -EBUSY;
    	}
    
    	gpio = (volatile unsigned int *)map;
    
    	GPIO_OUT(GPIO_LED);
    
    	kernel_timer_register();
    
    	return 0;
    }
    
    void timer_test_exit(struct inode *inode, struct file *file)
    {
    	printk("timer_test_exit\n");
    
    	GPIO_CLR(GPIO_LED);
    
    	del_timer(&timer);
        
    	iounmap(gpio);
    }
    
    module_init(timer_test_init);
    module_exit(timer_test_exit);
    
    MODULE_AUTHOR("icjk1003@gmail.com");
    MODULE_LICENSE("GPL");

     

     

     

    pi@raspberrypi:~/Downloads/devicedriver/timer $ nano Makefile

    obj-m := timer_test_driver.o
    KDIR := /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    
    default:
    	make -C $(KDIR) M=$(PWD) modules
    clean:
    	make -C $(KDIR) M=$(PWD) clean

     

    pi@raspberrypi:~/Downloads/devicedriver/timer $ make

     

    pi@raspberrypi:~/Downloads/devicedriver/timer $ sudo insmod timer_test_driver.ko

     

     

     

    성공..

     

     

     

    댓글

Designed by Tistory.