Module 14: Kernel Timer
ESP30076 임베디드 시스템 프로그래밍 (Embedded System Programming)
조 윤 석 전산전자공학부
리눅스에서 커널 타이머 사용법 알아보기
– HZ, jiffies_64, struct timer_list
커널 타이머를 활용한 하드웨어 제어용 디바이스 드라이버 작성
주차별 목표
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라는 상수값을 초기에 설정함
Timer 관련 변수
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
get_jiffies_64()
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);
Timer API
<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;
};
timer_list
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
일정주기로 LED 점멸되는 드라이버 작성
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로 보냄
구조체 정의
흐름도
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()
사용자 system call write(fd, arg, count)
- led_blink = arg(0 or 1) led_timer_exit()
- LED turn off - del_timer()
- kfree(ptimermgr) - iounmap()
- unregister_chrdev()
% insmod led_timer_driver.ko
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 구동 디바이스 드라이버 (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
#define DEV_NAME "led_timer_device"
#define IOM_LEDTIMER_MAJOR_NUM 247
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, };
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;
}
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);
elseoutb(0xff, (unsigned int)iom_led_addr);
}else
outb(0xff, (unsigned int)iom_led_addr); /* turn off LED */
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);
root@esp:~/work/dd/kernel_timer_led# vi test_timer_led.c
타이머 연동 LED 구동 응용 프로그램 (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;
}
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;
}
% vi Makefile
% make
컴파일
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)
타겟보드에서 실행
– 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
실행