Module 14: Kernel Timer ESP30076 임베디드시스템프로그래밍 (Embedded System Programming) 조윤석 전산전자공학부
주차별목표 리눅스에서커널타이머사용법알아보기 HZ, jiffies_64, struct timer_list 커널타이머를활용한하드웨어제어용디바이스드라이버작성 2
Timer 관련변수 HZ 리눅스커널에서주기적으로발생하는시스템타이머 interrupt 횟수 1 초에 HZ 상수값만큼발생 ( 예 ) HZ=100, 1 초에 system timer interrupt 100 번발생 #include <linux/timer.h> #define HZ 100 // <asm/param.h> jiffies_64 리눅스커널에서제공하는 global variable 커널전체에서동일한기준시간값제공 Scheduling 구현을위해 timer interrupt 가필요하고, 리눅스커널은 HZ 라는상수값을초기에설정함 3
get_jiffies_64() Timer interrupt 관련 HZ: 1 초당발생되는 timer interrupt 횟수 USER_HZ: HZ 값을보정하는수 jiffies: 커널 2.4 에서초당 HZ 값만큼증가하는전역변수 jiffies_64: 커널 2.6 에서초당 HZ 값만큼증가하는전역변수 get_jiffies_64(): jiffies_64 값을참조하기위한함수 HZ ARM: 100/128/200/1000 i386: 1000 x86-64: 1000 MIPS: 100/128 4
Timer API struct timer_list #include <linux/timer.h> struct timer_list { /*... */ unsigned long expires; void (*function)(unsigned long); unsigned long data; ; Kernel drivers with a number of functions to declare, register, and remove kernel timers void init_timer(struct timer_list *timer); struct timer_list TIMER_INITIALIZER(_function, _expires, _data); void add_timer(struct timer_list * timer); int del_timer(struct timer_list * timer); 5
timer_list <linux/timer.h> struct timer_list { /* * All fields that change during normal runtime grouped to the * same cacheline */ struct list_head entry; unsigned long expires; /* the timeout, in jiffies */ struct tvec_base *base; void (*function)(unsigned long); /* handler of the timeout */ unsigned long data; /* argument to the handler */ ; int slack; 6
일정주기로 LED 점멸되는드라이버작성 LED 구동시나리오 응용프로그램의인자가 1 8개의 LED가 0.5초단위로지속적으로점멸됨 #./test_timer_led 1 응용프로그램의인자가 0 파일명 모든 LED를끔 #./test_timer_led 0 응용프로그램 : test_timer_led.c 디바이스드라이버 : led_timer_driver.c 디바이스노드 : /dev/led_timer_device Major number: 247 7
구조체정의 Timer 로구동되는 LED 관리구조체 KERNEL_TIMER_STRUCT type struct { struct timer_list timer; unsigned long led_state; attribute ((packed)) KERNEL_TIMER_STRUCT; ptimermgr: LED 상태와 timer 를관리하기위한구조체 led_state 는 LED 점멸을결정하기위한상태변수값임 Timer handler 에 argument 를 timer 구조체주소로지정하는데, LED 점멸을위해 led 상태값 ( 여기서는 led_sate 임 ) 도같이보내기위해추가구조체를생성하고, 그구조체주소값을 argument 로보냄 8
흐름도 % insmod led_timer_driver.ko led_timer_init() - register_chrdev() - ioremap() - ptimermgr 선언 - init ptimermgr - led_timer_register() led_timer_register() - init_timer() - add_timer() led_timer_timeover() - LED blinking if led_blink=1 - LED off if led_blink=0 - led_timer_register() led_timer_exit() - LED turn off - del_timer() - kfree(ptimermgr) - iounmap() - unregister_chrdev() 사용자 system call write(fd, arg, count) - led_blink = arg(0 or 1) % rmmod led_timer_driver 9
타이머연동 LED 구동디바이스드라이버 (led_timer_driver.c) root@esp:~# mkdir /root/work/dd/kernel_timer_led root@esp:~# cd /root/work/dd/kernel_timer_led root@esp:~/work/dd/kernel_timer_led# vi led_timer_driver.c /* LED Kernel Timer Example FILE: led_timer_driver.c */ #ifndef LED_KERNEL_TIMER_DRIVER_ #define LED_KERNEL_TIMER_DRIVER_ #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/module.h> #include <asm/io.h> #include <asm/uaccess.h> #include <linux/errno.h> #include <linux/types.h> #include <linux/fcntl.h> #include <linux/slab.h> /* kmalloc() */ #include <linux/timer.h> /* HZ */ typedef struct { struct timer_list timer; unsigned long led_state; attribute ((packed)) KERNEL_TIMER_STRUCT; #endif 10 #define DEV_NAME "led_timer_device" #define IOM_LEDTIMER_MAJOR_NUM 247 #define IOM_LED_ADDRESS 0xA8000000 #define TIME_STEP (5*HZ/10) /* 0.5 sec */ #define BLINK_MODE 1
led_timer_driver.c (2) static int ledport_usage = 0; static unsigned char *iom_led_addr; static KERNEL_TIMER_STRUCT *ptimermgr = NULL; static unsigned char led_blink = 0; MODULE_LICENSE("GPL"); MODULE_AUTHOR("HGU"); int led_timer_init(void); void led_timer_exit(void); module_init(led_timer_init); module_exit(led_timer_exit); void led_timer_timeover(unsigned long arg); /* timer.function */ void led_timer_register(kernel_timer_struct *pdata, unsigned long timeover); int led_timer_open (struct inode *, struct file *); int led_timer_release (struct inode *, struct file *); ssize_t led_timer_write (struct file *, const char user *, size_t, loff_t *); struct file_operations led_timer_fops = {.owner = THIS_MODULE,.open = led_timer_open,.release = led_timer_release,.write = led_timer_write, ; 11
led_timer_driver.c (3) int init led_timer_init(void) { int major_num; major_num = register_chrdev(iom_ledtimer_major_num, DEV_NAME, &led_timer_fops); if ( major_num < 0 ) { printk(kern_warning"%s: can't get or assign major number %d\n", DEV_NAME, IOM_LEDTIMER_MAJOR_NUM); return major_num; printk("success to load the device %s. Major number is %d\n", DEV_NAME, IOM_LEDTIMER_MAJOR_NUM); iom_led_addr = ioremap(iom_led_address, 0x1); ptimermgr = kmalloc(sizeof(kernel_timer_struct), GFP_KERNEL); /* GFP_KERNEL: Normal allocation of kernel memory. May sleep */ /* GFP_KERNEL: GFP_WAIT( 스케줄러선점가능 ), GFP_IO( 물리적 IO 가능 ), GFP_FS( 저수준 filesystem IO 가능 ) */ if ( ptimermgr == NULL ) return -ENOMEM; memset(ptimermgr, 0, sizeof(kernel_timer_struct)); // GFP_KERNEL GFP_ZERO (Kernel 2.6 가능 ) led_timer_register(ptimermgr, TIME_STEP); return 0; 12
led_timer_driver.c (4) void led_timer_register(kernel_timer_struct *pdata, unsigned long timeover) { /* init_timer(&timer) */ init_timer(&(pdata->timer)); /* timer API: void init_timer(struct timer_list *timer) */ pdata->timer.expires = get_jiffies_64() + timeover; pdata->timer.function = led_timer_timeover; /* handler of the timeout */ pdata->timer.data = (unsigned long) pdata; /* argument to the handler */ add_timer(&(pdata->timer)); void led_timer_timeover(unsigned long arg) { KERNEL_TIMER_STRUCT *pdata = NULL; pdata = (KERNEL_TIMER_STRUCT *) arg; if(led_blink == BLINK_MODE) { /* blink */ pdata->led_state = ~(pdata->led_state); if(pdata->led_state!= 0) outb(0x00, (unsigned int)iom_led_addr); else outb(0xff, (unsigned int)iom_led_addr); else outb(0xff, (unsigned int)iom_led_addr); /* turn off LED */ led_timer_register(pdata, TIME_STEP); 13
led_timer_driver.c (5) int led_timer_open (struct inode *inode, struct file *filp) { if(ledport_usage) return -EBUSY; ledport_usage = 1; return 0; int led_timer_release (struct inode *inode, struct file *filp) { ledport_usage = 0; return 0; ssize_t led_timer_write (struct file *filp, const char user *buf, size_t count, loff_t *f_pos) { if (copy_from_user(&led_blink, buf, count)) return -EFAULT; return count; void exit led_timer_exit(void) { outb(0xff, (unsigned int)iom_led_addr); /* LED turn off */ if(ptimermgr!= NULL ) { del_timer(&(ptimermgr->timer)); kfree(ptimermgr); iounmap(iom_led_addr); unregister_chrdev(iom_ledtimer_major_num, DEV_NAME); 14
타이머연동 LED 구동응용프로그램 (test_timer_led.c) root@esp:~/work/dd/kernel_timer_led# vi test_timer_led.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define DEV_NAME "/dev/led_timer_device" int main(int argc, char **argv) { int led_fd; unsigned char led_blink; if ( argc!= 2 ) { printf("usage: %s [0 or 1] (0: all LED OFF, 1: LED blinking)\n", argv[0]); return -1; led_fd = open(dev_name, O_WRONLY); if ( led_fd < 0 ) { printf("led timer driver open error...\n"); return -1; 15
test_timer_led.c (2) led_blink = (unsigned char) atoi(argv[1]); printf("{a input: %d\n", led_blink); write(led_fd, &led_blink, sizeof(led_blink)); close(led_fd); return 0; 16
컴파일 % vi Makefile CC = arm-linux-gcc KDIR = /root/download/kernel-2.6.35 obj-m = led_timer_driver.o PWD = $(shell pwd) TEST_TARGET = test_timer_led TEST_SRCS = $(TEST_TARGET).c all: module test_pgm module: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules test_pgm: $(CC) $(TEST_SRCS) -o $(TEST_TARGET) clean: rm -rf *.ko *.o rm -rf *.symvers *.order *mod.c *.cmd rm -rf $(TEST_TARGET) % make 17
실행 타겟보드에서실행 Host 컴퓨터의 /root 디렉토리를 NFS 로연결 # cd /root/nfs/work/dd/kernel_timer_led # insmod led_timer_driver.ko # mknod /dev/led_timer_device c 247 0 #./test_timer_led 1 #./test_timer_led 0 18