동적 타이머 혹은 커널 타이머로도 불리는 타이머는, 커널에서 시간의 흐름을 관리하는데 있어서 필수 불가결한 요소.
커널코드는 종종 어떤 함수의 실행을 일정 시간만큼 지연해야하는 경우가 있다.
원하는 것은 어떤 특정한 시간 만큼의 작업을 지연시킬 수 있는 방법이며, 그 방법은 바로 커널 타이머이다.
타이머는 사용하기 아주 쉽다. 즉, 초기값을 설정하고, 만료 시간을 정하고, 만료시 실행할 함수를 지정한 후 타이머를 활성화하면 된다. 여기서 지정한 함수는 타이머가 만료될 때 실행된다.
또한 타이머는 주기적이 아니다. 즉 타이머는 만료가 되면 정지하게 된다. 이것이 바로 동적인 타이머가 필요한 또 하나의 이유이다. 이렇게 타이머는 끊임없이 생성되고 소멸되며, 타미어 수에는 제한이 없다. 또한 타이머는 커널 전체에서 널리 사용된다.
타이머 사용
타이머는 struct time_list 자료형으로 표현되며 이것은 <linux/timer.h>에 정의되어 있다.
struct timer_list{
struct list_head entry; //타이머는 연결리스트의 일부이다.
unsigned long expires; //만료시간, jiffy의 단위
spinlock_t lock; //이 타이머를 보호하는 락
void (*function)(unsigned long); //타이머 핸들러 함수
unsigned long data; //핸들로로의 매개변수
struct tvec_t_base_s *base; //내부적으로 사용되는 필드, 변경하지 말것.
}
타이머를 생성하기 위한 첫번째과정은 타이머를 정의하는 것이다.
struct timer_list my_timer;
그 다음에는 타이머의 내부 값을 초기화해야 한다. 이는 도우미 함수를 통해 이뤄지며, 타이머에 대한 어떠한 다른 함수를 호출하기 이전에 반드시 이 함수를 호출해야한다.
init_timer(&my_timer);
이제 필요에 따라 남은 값들을 채워나간다.
my_timer.expires = jiffies + delay; //타이머는 하나의 delay틱 이후에 만료됨
my_timer.data = 0; //타이머 핸들러에 매개변수 0을 전달
my_timer.function = my_function; //타이머가 만료되었을 때 실행할 함수
my_timer.expires에는 타임아웃 값을 틱의 절대값으로 지정한다. 만약 현재 jiffies 카운트가 my_timer.expires보다 크다면, 핸들러 함수인 my_timer.function이 my_timer.data를 매개변수로 하여 실행된다. 앞에서 timer_list 정의에서 보았던 것처럼. 핸들러 함수는 다음 프로토타입에 따라 작성돼야한다.
void my_timer_fucntion(unsigned long data); //이와 같은 함수프로토타입을 사용해야 함
매겹ㄴ수 data를 통해 동일한 핸들러를 여러 타이머에서 구분하여 사용할 수 있다. 매개변수가 필요치 않다면 간단히 0을 전달하면 된다.
커널은 현재 틱 카운트가 지정된 만료값 이상일 때 타이머 핸들러를 실행한다.
(* 타이머클래스를 생각해보라.
while(1)
{
do_handler(unsignd long data);
sleep(1000);
}
여기서 우리는 1초후에 핸들러가 다시 수행할 것이라고 예측한다.
*)
만료가 되고 약간의 지연이 생길 수 있다. 일반적인 겨웅 타이머 핸들러는 거의 만료 시점과 동일한 시점에서 실행되지만, 때로는 만료 이후 첫번째 틱가지 지연될 수 있다. 따라서 어떤 경우이든 엄격한 실시간 처리가 요구될 때에는 타이머를 사용해서는 안된다.
지정된 타이머의 만료시점을 변경할 수 있다.
mod_timer(&my_timer, jiffies + new_delay); //새로운 만료시점 설정
또한 mod_timer() 함수는, 초기화됐지만 아직 활성화가 안된 타이머도 다룰 수 있다. 만약 타이머가 비활성화 상태였따면 mod_timer() 함수를 호출할 때 자동으로 활성화된다. 또한 이 함수는 타이머가 비활성화 상태라면 0을 활성화상태라면 1을 리턴한다. 두 경우 모두 mod_timer()애서 리턴될 때 타이머가 활성화되며, 새 만료 시점이 설정된다.
타이머를 만료 이전에 비활성화하면 del_timer() 함수를 사용한다.
멀티 프로세싱 시스템의 경우에는 다른 프로세서에서 타이머 핸들러가 실행 중일 수 있다. 이와 같은 경우 타이머를 비활성화하고 실행중인 해당 타이멀의 핸들러가 종료될 때까지 기다리려면 del_timer_sync()를 사용한다.
del_timer_sync(&my_timer);
del_timer(&my_timer); <-- 동기화가 되어있지 않으므로, 동기화 시키는 함수 del_timer_sync(&my_timer)와 같이 사용한다.
my_timer -> expires = jiffies + new_delay;
add_timer(my_timer);
- TIMER_SOFTIRQ softirq는 run_timer_softirq()에서 처리된다. 이 함수는 현재 프로세서에 존재하는 모든 만료된 타이머를 실행한다.
- 타이머는 연결 리스트 형태로 저장된다. 하지만 커널이 끊임없이 전체 리스트를 탐색하여 만료된 타이머를 찾아내거나, 타이머의 삽입/삭제 따라 전체 리스트를 만료시간 기준으로 재정렬하는 것은 매우 부담이 되므로 적절하지 않다. 대신 커널은 만료시간을 기준으로 타이머를 5개로 분할하고, 만료시간이 가까워진 타이머일수록 하위 그룹으로 이동시키는 방법을 사용한다. 이러한 방법은 대부분의 경우, 커널이 실행해야 할 만료된 타이머를 찾아내는 부담을 덜어준다.