커널에서 프로그래밍을 하다보면 시간 간격을 두고 작업을 처리해야 하는 경우가 발생한다.
시간은 절대시간과 상대시간 형태로 표현할 수 있는데, 커널에서는 주로 상대시간을 사용하여 작업 흐름을 제어한다. 여기에서는 상대적인 시간을 다루기 위해서 사용하는 몇가지 방법을 살펴보도록 하겠다.
jiffies
jiffies는 리눅스 커널에서 가장 기본이 되는 시간값으로서 특정 클럭 단위로 값이 1씩 증가하는 변수이다. jiffies는 <linux/jiffies.h>에 다음과 같이 선언되어 있다.
;
jiffies 값을 증가시키는 클럭의 횟수는 HZ 매크로로 정의되어 있다. HZ의 값은 아키텍쳐 또는 버전에 따라서 다르다. x86의 경우에 2.4버전에서는 100이었으며, 2.6 오면서 1000으로 증가되었다. 하지만, 현재는 1000보다는 작은 값을 사용하고 있는데, 커널 컴파일시 설정을 바꿀 수 도 있다. HZ의 값은 1초에 발생하는 클럭틱의 수를 나타낸다. 따라서, x86용 2.4 커널의 경우 10밀리초에 한 번씩 클럭틱이 발생하며 jiffies값도 10밀리초마다 증가한다.
jiffies값은 증가형태로만 사용되기 때문에 일정 시간이 지나고 나면 오버플로우가 되고 다시 0부터 증가하게 된다. HZ가 1000일 경우에 50일정도면 오버플로우가 발생한다. 따라서, jiffies를 사용할 때에는 오버플로우에 대한 고려를 해야 한다. 보통 jiffies를 사용하는 이유는 특정 시간 후 작업을 계속하기 위해서인데, 간단한 경우는 다음과 같이 작성할 수 도 있다.
while (my_times > jiffies)
;
이 경우 5초동안 잠시 멈춰 있도록 하는 것이다. 하지만, 5초동안 CPU를 점유하고 있기 때문에 비선점커널로 컴파일 한 경우 전체 시스템이 멈춰있게 되는 문제가 있다. 실제로 위와 같이 작성하는 경우는 거의 없으며, 일정 시간을 딜레이를 해야 한다면 커널 타이머를 사용하게 된다.
위 예제처럼 jiffies는 특정 시점에서 시간값을 비교하기 위해서 사용하게 되는데, 이때 단순히 크기를 비교할 경우 오버플로우 문제에 대처하지 못하는 문제가 있다. 이러한 문제를 해결하기 위해서는 커널에서 제공해주는 다음과 같은 함수들을 사용해야 한다.
time_before(a,b)
time_after_eq(a,b); /* a 가 b보다 나중이거나 같은 시간이면 true가 된다 */
time_before_eq(a,b)
2.6 버전부터는 jiffies값이 증가하게 됨에 따라 오버플로우가 좀 더 자주 발생하게 되었다. 따라서, 32비트 jiffies로는 감당하기 힘들어서 jiffies_64를 같이 제공하고 있다. jiffies와 jiffies_64는 같은 메모리 번지에 맵핑되어 있기 때문에 jiffies를 증가시킬 경우 jiffies_64도 증가가 된다. 정확히는 jiffies_64를 증가시켜서 jiffies 값이 증가되도록 한다. 둘은 선택적으로 사용할 수 있으며, 32비트 환경에서 jiffies는 32비트를 가져오고 64비트 환경에서는 64비트 값을 가져온다. 따라서, 긴 시간 간격에 대한 비교가 필요할 경우에는 jiffies_64를 사용한다.
jiffies_64 변수는 직접 건드리지 않고 다음 함수를 사용하도록 권장하고 있다.
u64 get_jiffies_64(void);
HZ보다 작은 단위의 시간 지연
jiffies를 통해서 대부분의 상대적인 시간 처리를 하지만 경우에 따라서는 HZ보다 더 세밀한 시간 간격(HZ가 1000인 경우, 1밀리세컨드)을 필요로 하는 경우가 있다. 즉, 마이크로초나 나노초 단위의 지연을 하고 싶은 경우에는 다음 매크로를 사용하면 된다.
void mdelay(unsigned long milliseconds);
void udelay(unsigned long microseconds);
void ndelay(unsigned long nanoseconds);
mdelay()는 밀리초단위, udelay()는 마이크로초 단위, ndelay()는 나노초 단위의 지연을 하게 된다. 이 연산도 내부적으로는 반복문을 사용하기 때문에 장시간 지연을 주는 것은 삼가할 필요가 있다. 만약 클럭틱보다 큰 값동안의 딜레이를 원한다면 커널 타이머를 사용하는 것이 바람직하다.
커널 타이머
커널 타이머는 미래의 특정 시간에 지정한 함수를 수행하도록 하는 방법이다. 이것은 커널이 수행을 해주기 때문에 그 때까지 불필요한 코드를 수행하면서 기다릴 필요가 없다.
커널 타이머를 사용하기 위해서 필요한 함수들은 다음과 같다.
#include <linux/timer.h>
struct timer_list {
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
struct tvec_t_base_s *base;
#ifdef CONFIG_TIMER_STATS
void *start_site;
char start_comm[16];
int start_pid;
#endif
};
void init_timer(struct timer_list *timer);
void add_timer(struct timer_list *timer);
void add_timer_on(struct timer_list *timer, int cpu);
void mod_timer(struct timer_list *timer, unsigned long expires);
int del_timer(struct timer_list *timer);
int del_timer_sync(struct timer_list *timer);
이 함수들을 사용하는 유형은 대충 다음과 같다.
void check_hw(unsigned long arg)
{
/* code for checking the hardware */
....
}
void some_func() {
init_timer(&my_timer);
my_timer.function = check_hw();
my_timer.data = &my_arg;
my_timer.expires = jiffies + 5 * HZ;
add_timer(&my_timer);
...
}
timer_list 구조체를 통해 수행할 함수와 수행할 시간을 명시하게 된다. 하지만, 세팅을 하기 전에 반드시 init_timer()를 통해 초기화를 시킨 후 사용해야 한다.
구조체의 data 필드는 function 함수를 호출할 때 인수로 넘길 내용이며, unsigned long 형태로 되어 있다. expires 필드는 수행할 시간을 나타내는데, 상대 시간이 아닌 절대 시간값을 사용한다. 즉, 5초 후에 수행하겠다면 5 * HZ로 표현하는 것이 아니라, jiffies + 5 * HZ로 표현해야 한다. expires는 클럭틱 발생 주기보다 세밀한 단위로 시간값을 줄 수는 없다.
커널에 등록하기 위해서는 add_timer() 함수를 사용해야 한다. 2.6 커널에는 추가로 add_timer_on() 이라는 함수가 생겼는데, 이것은 등록해 놓은 함수가 특정 CPU에서 수행되게 할 때 이용한다. add_timer()로 사용했을 경우에는 현재 CPU에서 나중에 수행된다.
만약, 등록해 놓은 타이머 함수가 아직 호출되지 않은 상태에서 시간값을 바꾸고 싶은 경우에는 mod_timer() 함수를 사용하면 된다.
호출되지 않은 상태의 타이머 함수를 제거하기 위해서는 del_timer() 또는 del_timer_sync() 함수를 사용하면 된다. 이 함수들을 호출했을 때, 해당 타이머 함수가 아직 수행되지 않았다면 제거되고, 이미 수행되었다면 그냥 리턴된다. del_timer_sync()는 해당 타이머 함수가 수행되고 있을 경우, 작업이 끝나길 기다린 후 타이머를 제거하고 빠져나온다. del_timer()는 수행중일 경우에 대한 고려를 하지 않는다.
'Linux > API' 카테고리의 다른 글
| I/O Port 다루기 on x86 (0) | 2008/01/26 |
|---|---|
| 인터럽트 (0) | 2008/01/25 |
| 커널 시간 관리 (0) | 2008/01/25 |
| CPU별 변수 사용 (0) | 2007/10/08 |
이올린에 북마크하기
이올린에 추천하기


댓글을 달아 주세요