local_irq_disable(), local_irq_enable() 함수는 모든 IRQ 인터럽트를 막는 것이며, 아래 함수들은 특정 IRQ만 금지하는 명령이다. 이 함수들은 CPU에게 명령을 내려서 CPU가 처리하지 않도록 하는 것이기 때문에 다른 CPU에 인터럽트가 전달되는 것은 막을 수 없다.
인터럽트 핸들러 작성
리눅스에서는 인터럽트 디스크립터 테이블(IDP)를 직접 건드리지 않는다. IDP는 부팅시 초기화 된 이후에는 변경되지 않으며, 디바이스 드라이버 등에서는 리눅스에서 잡아놓은 환경위에서 동작하게 된다. 인터럽트 핸들러를 다루기 위해서는 다음 함수들을 사용한다.
#include <linux/interrupt.h>
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id); void free_irq(unsigned int irq, void *dev_id);
인터럽트 핸들러를 특정 번호에 등록하기 위해서는 request_irq 함수를 사용하면 된다. 이때, irq 번호에 원하는 인터럽트 번호를 지정하고, handler에는 나중에 수행될 인터럽트 핸들러를 지정하면 된다. irqflags는 핸들러의 특성을 지정할 수 있다. devname은 /proc/interrupts에서 확인할 수 있는 핸들러의 이름이다.
인터럽트 핸들러 함수의 프로토타입은 2.4와 2.6에서 약간 차이가 있다. 다음은 2.4에서의 인터럽트 핸들러 함수 프로토타입니다.
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를 사용하는 이유는 특정 시간 후 작업을 계속하기 위해서인데, 간단한 경우는 다음과 같이 작성할 수 도 있다.
이 경우 5초동안 잠시 멈춰 있도록 하는 것이다. 하지만, 5초동안 CPU를 점유하고 있기 때문에 비선점커널로 컴파일 한 경우 전체 시스템이 멈춰있게 되는 문제가 있다. 실제로 위와 같이 작성하는 경우는 거의 없으며, 일정 시간을 딜레이를 해야 한다면 커널 타이머를 사용하게 된다.
위 예제처럼 jiffies는 특정 시점에서 시간값을 비교하기 위해서 사용하게 되는데, 이때 단순히 크기를 비교할 경우 오버플로우 문제에 대처하지 못하는 문제가 있다. 이러한 문제를 해결하기 위해서는 커널에서 제공해주는 다음과 같은 함수들을 사용해야 한다.
time_after(a,b); /* a가 b보다 나중 시간값이면 true가 된다 */ 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를 통해서 대부분의 상대적인 시간 처리를 하지만 경우에 따라서는 HZ보다 더 세밀한 시간 간격(HZ가 1000인 경우, 1밀리세컨드)을 필요로 하는 경우가 있다. 즉, 마이크로초나 나노초 단위의 지연을 하고 싶은 경우에는 다음 매크로를 사용하면 된다.
#include <linux/delay.h>
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;
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()는 수행중일 경우에 대한 고려를 하지 않는다.