커널의 주요한 역할은 바로 컴퓨터에 연결된 하드웨어를 관리하는 것입니다. 이러한 작업을 위해 커널은 각각의 디바이스와 통신해야 합니다. 이러한 작업을 위해 커널은 각각의 디바이스와 통신해야 합니다. 일반적으로 하드웨어와의 통신속도는 프로세서와의 통신 속도보다 훨씬 느리므로, 커널이 하드웨어에 요청을 보내고 응답이 올 때까지 기다리는 것은 부적절한 경우가 많습니다. 대신, 커널은 통신속도가 느린 하드웨어로부터의 응답을 기다리지 않고 그 동안 다른 작업을 처리하다가, 나중에 하드웨어가 정말로 일을 끝낸 다음에 그 응답을 처리할 수 있습니다. 이렇게 하기 위한 한 가지 방법은 폴링입니다. 즉 커널은 주기적으로 시스템에 있는 하드웨어의 상태를 검사하여 그 상태에 따라 응답을 처리하는 것입니다. 하지만 이 방법은 심지어 하드웨어가 일을 하지 않을 때도 부담이 발생하는데 그 이유는 항상 일정한 간격으로 하드웨어의 상태를 반복해서 체크해야 하기 때문입니다. 더 좋은 해결책은 커널의 처리가 요구될 바로 그 시점에 하드웨어가 커널에게 그 사실을 알려주는 메커니즘을 사용하는 것입니다. 이러한 방법은 인터럽트(Interrupt)라 합니다.

인터럽트

 인터럽트는 하드웨어가 프로세서와 통신할 수 있도록 해줍니다. 예를 들어 키보드를 두드리면 키보드 컨트롤러가 인터럽트를 발생시켜 운영체제로 하여금 처리해야 할 키보드 입력이 있음을 알려줍니다. 인터럽트는 이렇게 하드웨어 디바이스로부터 프로세서로 전달되는 특별한 전기적 신호입니다. 프로세서가 인터럽트를 받으면 프로세서는 운영체제가 새 데이터를 처리할 수 있도록 알려주게 됩니다. 하드웨어 디바이스는 프로세서 클럭에 비동기적으로 인터럽트를 발생시키므로 인터럽트는 어느 때나 발생할 수 있습니다. 다시 말하면 커널은 언제라도 인터럽트에 의해 방해받을 수 있는 것입니다.
 인터럽트는 디바이스에서 물리적인 전기 신호의 형태로 발생하여 인터럽트 컨트롤러의 입력 핀(pin)으로 전달됩니다. 그 다음 인터럽트 컨트롤러는 프로세서로 신호를 보내고, 이 신호를 감지한 프로세서는 인터럽트를 처리하기 위해 현재의 실행을 중단합니다. 이제 프로세서가 운영체제로 인터럽트가 발생했음을 알리면 운영체제는 상황에 맞게 인터럽트를 처리하게 됩니다.
 각 인터럽트에는 고유한 값이 할당되므로 각 디바이스들은 각자 고유한 인터럽트를 갖게 됩니다. 따라서 키보드에서 발생한 인터럽트와 하드드라이브에서 발생한 인터럽트가 구별됩니다. 이러한 식의 운영체제는 인터럽트를 식별하여 어떤 디바이스가 인터럽트를 발생시켰는가를 판단합니다. 또한 운영체제는 각 인터럽트마다 고유한 핸들러를 제공합니다.
 이러한 인터럽트 값을 인터럽트 요청(IRQ, Interrupt Request)이라고 부릅니다. 일반적으로 이것은 숫자로 표현되는데, 예를 들어 PC에서 IRQ 0은 타이머 인터럽트를, IRQ 1은 키보드 인터럽트를 의미하게 됩니다. 하지만 모든 인터럽트 번호가 고정돼있는 것은 아닙니다. 예를 들어 PCI 버스(bus)에 연결돼 있는 디바이스에 대한 인터럽트 번호는 동적으로 할당됩니다. 또한 PC가 아닌 다른 아키텍처에서 인터럽트를 동적으로 할당하는 예가 있기도 합니다. 하지만 여기서 요즘은, 특정 인터럽트는 특정 디바이스와 맞물려 있으며 커널이 이것을 알고있다는 사실입니다. 이제 하드웨어는 인터럽트를 발생시켜 커널의 주의를 끕니다.

인터럽트 핸들러

 인터럽트를 처리하는 커널 함수가 바로 인터럽트 핸들러(Interrupt Handler) 혹은 인터럽트 서비스 루틴(ISR, Interrupt Service Routine)입니다. 각각의 디바이스가 발생시키는 인터럽트에는 그와 관련된 인터럽트 핸들러가 존재합니다. 즉 시스템 타이머 인터럽트를 처리하는 핸들러 함수가 있다면 키보드 인터럽트를 처리하는 또다른 핸들러 함수가 있는 식입니다. 이러한 각 디바이스의 인터럽트 핸들러는 모두 디바이스 드라이버(디바이스르 관리하는 커널 코드)의 일부분입니다.
 리눅스에서의 인터럽트 핸들러는 일반적인 C 함수입니다. 물론 커널에서의 표준화된 처리를 위해 특정한 프로토타입으로 되어 있기는 하지만, 그 밖에는 다른 일반 함수와 크게 다를 바가 없습니다. 그러나 인터럽트 핸들러가 다른 커널 함수들과 구별되는 가장 큰 차이점은 커널이 인터럽트를 처리하기 위해 바로 이 핸들러 함수를 호출하면 특별한 컨텍스트, 즉 인터럽트 컨텍스트에서 실행된다는 점입니다.
 인터럽트는 언제라도 발생 가능하므로 인터럽트 핸들러 역시 언제라도 실행될 수 있습니다. 인터럽트 핸들러에서는 그 실행 속도가 중요한데, 왜냐하면 인터럽트에 의해 중단된 코드를 가능한 빨리 실행되도록 해야 하기 때문입니다. 따라서 인터럽트를 발생시킨 하드웨어에 즉각 응답하는 것도 중요하짐나 인터럽트 핸들러의 실행을 최대한 짧게 하는 것 역시 전체 시스템의 관점에서 매우 중요합니다. 최소한의 경우를 고려하면 인터럽트 핸들러의 역할은 하드웨어에게 인터럽트를 받았음을 알리는 일이 될 것입니다. 하지만 일반적으로 다른 작업을 함께 수행해야 하는 경우가 많습니다. 예를 들어 네트웨크 디바이스를 위한 인터럽트 핸들러의 경우, 하드웨어에 응답하는 것 뿐만 아니라 하드웨어에서 메모리로 패킷을 복사하고, 프로세싱하고, 적절한 프로토콜 스택(protocol stack)이나 응용 프로그램으로 밀어 넣는 작업을 함께 수행해야 합니다. 분명 이 모든 작업은 상당한 시간이 필요할 것입니다.

톱하프와 보톰하프

인터럽트 핸들러를 최대한 빨리 수행해야 하지만 한편으로는 많은 추가적인 일을 해야하는 두 가지 목표는 분명 상호대립적입니다. 따라서 대개 인터럽트의 처리를 두 단계로 분리합니다. 이 두 단계 중 인터럽트 핸들러는 톱하프(top half)에 해당합니다. 즉 인터럽트가 발생하는 즉시 실행되어 타임 크리티컬한 작업들, 예를 들면 인터럽트의 수신을 알리고 하드웨어를 리셋하는 작업 등을 수행하는 것입니다. 나중에 해도 무관한 작업들은 보톰하프(bottom half)가 실행될 때까지 지연됩니다. 보톰하프는 좀 더 편한 시간에, 모든 인터럽트를 활성화시킨 상태에서 실행됩니다(일반적으로 인터럽트 핸들러가 리턴되면 실행됩니다). 리눅스에서는 이러한 보톰하프를 위한 여러가지 메커니즘을 제공합니다.
 네트워트 상황을 이용하여 쉽게 설명해보자면, 랜카드는 네트웨크로부터 패킷을 받게 되면, 커널에 그 사실을 알려야 합니다. 이 과정은 네트워크 최대 처리량과 지연시간을 최적화하고, 타임아웃을 방지하기 위해 신속하게 이루어져야 합니다. 따라서 즉시 인터럽트를 발생시킵니다.
 그러면 커널은 랜카드가 등록한 인터럽트를 수행합니다. 인터럽트는 실행되면서, 하드웨어에 자신의 실행 사실을 알린 후, 새로 도착한 네트워크 패킷들을 메인 메모리로 복사합니다. 그리고, 랜카드가 또 다른 패킷들을 받을 수 있도록 준비작업을 합니다. 이러한 작업들은 중요하고, 지체하기 힘들며, 하드웨어에 따라 그 특성이 다릅니다. 해당 패킷들에 대한 처리과정의 남은 부분들은 보톰하프에 수행됩니다.

인터럽트 핸들러 등록

 인터럽트 핸들러들은 해당 하드웨어를 운용하는 디바이스 드라이버가 제공해야 합니다. 각 디바이스는 관련된 드라이버를 하나씩 가지고 있으며, 대개의 경우가 그렇듯 디바이스가 인터럽트들을 이용하는 겨우, 해당 드라이버는 각 인터럽트 핸들러를 등록해야 합니다. 디바이스 드라이버는 다음과 같이 특정 인터럽트가 주어진 핸들러에 의해 처리되도록 인터럽트 핸들러를 등록할 수 있습니다.

int request_irq(unsigned int irq,
                     irqreturn_t (*handler)(int, void*, struct pt_regs*),
                     unsigned long irqflags,
                     const char* devname,
                     void* dev_id)

 첫 번째 매개변수 irq는 할당할 인터럽트 번호입니다. 시스템 타이머나 키보드와 같은 구형 PC 디바이스의 인터럽트 번호는 대개 직접 코딩되어 있지만 다른 디바이스의 경우에는 탐색을 하여 찾거나 정의된 절차에 따라 동적으로 결정합니다.
 두 번째 매개변수 handler는 해당 인터럽트를 처리할 실제 핸들러의 함수 포인터입니다. 운영체제는 인터럽트가 발생할 때마다 이 함수를 호출하게 됩니다. 핸들러 함수는 특정한 형식이 요구되며 3개의 매개변수와 irqreturn_t형의 리턴값을 갖습니다.
 세 번째 매개변수 irqflags는 0 혹은 다음에 해당하는 비트마스크 값을 갖습니다.

SA_INTERRUT : 이 플래그는 해당 인터럽트 핸들러가 빠른 인터럽트  핸들러임을 가리킵니다. 역사적으로 리눅스는 빠른, 그리고 느린 인터럽트 핸들러를 구분해 왔습니다. 빠른 핸들러란 빨리 수행되지만 자주 호출되는 것으로 과거에는 빠른 핸들러를 최대한 빨리 수행하는 것이 인터럽트 처리의 목표 중 하나였습니다. 하지만 현재는 두 핸들러 사이에 단 한가지 차이점만이 있는데, 즉 빠른 핸들러는 모든 인터럽트를 비활성화한 상태로 로컬 프로세서에서 실행된다는 점이 바로 그것입니다. 인터럽트를 비활성화하는 것은 빠른 핸들러로 하여금 다른 것들에 의해 방해받지 않고 최대한 빨리 수행될 수 있도록 하는 역할을 합니다. 이 플래그가 없을 경우, 실행중인 핸들러의 인터럽트를 제외한 모든 인터럽트가 발생가능합니다. 타이머 인터럽트를 제외한 대부분의 인터럽트는 이 플래그를 사용하지 않습니다.

SA_SAMPEL_RANDOM : 이 플래그는 해당 디바이스에 의해 발생한 인터럽트가 커널 엔트로피 풀에 영향을 끼친다는 것을 의미합니다. 커널 엔트로피 풀은 다양한 랜덤 이벤트로부터 추출한 진정한 의미의 랜덤 숫자를 제공합니다. 이 플래그가 사용될 경우, 디바이스로부터의 인터럽트 발생 타이밍이 엔트로피 풀에 적용됩니다. 디바이스가 예측 가능한 주기로 인터럽트를 발생시키거나(예: 시스템 타이머) 혹은 외부의 영향으로 변화될 경우(예: 네트워크 디바이스) 이 플래그를 사용해선 안됩니다. 하지만 다른 대부분의 경우 하드웨어가 비결정적인 타이밍으로 인터럽트를 발생시키므로 엔트로피(무질서도)의 좋은 소스가 됩니다.

SA_SHIRQ : 이 플래그는 인터럽트 번호가 여러 인터럽트 핸들러에 의해 공유될 수 있음을 나타냅니다. 이 플래그가 없는 경우에는 각 번호마다 오직 하나의 핸들러만이 존재해야 합니다.

 네 번째 매개변수 devname은 해당 디바이스의 ASCII 텍스트 이름입니다. 예를 들어 PC의 키보드 인터럽트의 경우 이 값은 "keyboard"입니다. 이 텍스트 이름은 유저와의 통신을 위해 /proc/irq와 /proc/interrupts에 의해 사용됩니다.
 다 섯번째 매개변수 dev_id는 주로 공유 인터럽트를 위해 사용됩니다. 인터럽트 핸들러가 해제될 때, dev_id는 해당 인터럽트에서 특정 인터럽트 핸들러만을 제거하기 위해 사용됩니다. 이 매개변수가 없으면 커널은 해당 인터럽트에서 어떤 핸들러를 제거할 것인가를 결정할 수 없게 됩니다. 공유 인터럽트가 아닌 경우에는 여기에 NULL을 줄 수 있지만 공유 인터럽트인 경우 반드시 식별 가능한 유일한 값을 지정해야 합니다. 또한 이 포인터는 호출마다 인터럽트 핸들러로 전달되므로 드라이버의 디바이스 구조체 포인터를 여기로 넘겨 나중에 핸들러 내부와 디바이스 모델에서 유용하게 사용할 수도 있습니다.

인터럽트 핸들러 제거

 드라이버를 언로드할 때, 관련 인터럽트 핸들러의 등록을 취소하고, 어쩌면 인터럽트도 비활성화해야 합니다. 이를 위해 다음의 함수를 호출합니다.

void free_irq(unsigned int irq, void* dev_id)

 만약 지정된 인터럽트 번호가 공유되지 않았다면 이 함수는 해당 인터럽트 핸들러를 제거하고 이 인터럽트를 비활성화시킵니다. 만약 공유된 경우라면 dev_id가 가리키는 핸들러만이 제거되고 인터럽트 번호는 모든 핸들러가 제거되었을 때만 비활성화됩니다. 이것이 바로 유일한 dev_id값을 사용해야 하는 이유입니다. 공유 인터럽트를 사용할 때에는 이와 같이 다수의 핸들러 중에서 특정 핸들러를 free_irq()로 제거하기 위한 유일한 식별자가 필요합니다. 또 어떠한 경우에든지 만약 dev_id가 NULL이 아니라면 해당 핸들러와 관련이 있는 포인터를 사용해야 합니다. free_irq()는 반드시 프로세스 컨텍스트에서 호출해야 합니다.

'Education > Linux Kernel' 카테고리의 다른 글

우분투 10.04 네트워크 설정  (1) 2010.07.07
vmware 키보드 먹통  (2) 2010.07.07
oops 강제 발생  (1) 2009.11.24
vim 설정  (0) 2009.11.18
커널 소스 트리  (0) 2009.11.18
Posted by 초상큼발랄
l