본문 바로가기

Linux Kernel

[Linux Kernel] 인터럽트와 후반부 처리의 개념

반응형

Interrupt

컴퓨터는 프로세서만으로 이루어져있지 않다. 디스크로부터 데이터를 읽고 쓰거나, 네트워크 카드로부터 데이터가 들어오거나, 사용자가 마우스나 키보드로 입력을 할 수 있다. 이처럼 프로세서는 주변 장치로부터 어떤 이벤트가 발생했다는 것을 처리할 방법이 필요한데, 그것이 바로 인터럽트이다. 마우스로 무언가를 입력하면 프로세서는 실행을 잠시 중단하고, 마우스가 발생시킨 인터럽트를 처리한 후 다시 원래 실행하던 코드로 돌아간다.

 

그런데 마우스가 발생시키는 인터럽트와 키보드가 발생시키는 인터럽트는 다를 것이다. 커널은 서로 다른 인터럽트에 IRQ 번호를 부여하여 관리한다. 또한, 부팅시, 혹은 실행 중에 IRQ 번호에 따라 인터럽트 핸들러라는 함수를 미리 등록하여, 인터럽트가 발생했을 때 실행될 콜백함수를 정의할 수 있다. 따라서 인터럽트가 발생하면, 이에 해당하는 인터럽트 핸들러가 호출된다.

프로세서가 인터럽트를 처리하는 모습

Hardware Interrupt vs Software Interrupt

우리가 위에서 말했던 주변 장치로부터 발생하는 인터럽트는 하드웨어 인터럽트라고 부른다. 하드웨어 인터럽트는 하드웨어가 발생시키는 것이므로 비동기적이다. 즉, 발생하는 시점을 알 수 없다. 하지만 이와는 달리 프로세서의 명령(instruction)으로 직접 인터럽트를 일으키는 방법도 있으며, 이를 소프트웨어 인터럽트라고 한다. 소프트웨어 인터럽트는 하드웨어 인터럽트와는 달리 동기적이다. 시스템 호출의 경우 소프트웨어 인터럽트로 구현되며, SoftIRQ, Tasklet 등의 일부 후반부처리 매커니즘도 소프트웨어 인터럽트로 구현된다.

Interrupt Context

커널은 어느 한 순간에, 사용자 프로세스를 실행중이거나, 커널 프로세스를 실행중이거나, 인터럽트를 실행중일 것이다. 사용자 프로세스던 커널 프로세스던 프로세스는 모두 struct task_struct라는 구조체로 관리되며, 실행중인 프로세스에 대한 정보를 기록하고, 커널의 스케줄러는 이를 스케줄링한다.

 

"인터럽트를 실행중인 상태"라는 것은 매우 특별하다. 이를 보통 커널이 interrupt context에 있다고 하는데, 인터럽트는 프로세스와는 달리 이와 대응되는 구조체가 없다. 다시 말해서 인터럽트는 스케줄링이 될 수 없다. 따라서 인터럽트가 한 번 발생하면 인터럽트의 처리가 끝날 때까지 sleep할 수 없다. 인터럽트 컨텍스트에 대한 정보를 따로 저장하지 않으므로, sleep을 한 후에 다시 돌아올 곳이 없기 때문이다.

 

커널이 휴면 상태로 전환할 수 없다는 것은 매우 중요한데, 다시 말해서 인터럽트 컨텍스트에서는 휴면 상태로 전환될 수 있는 함수는 호출할 수 없다. 따라서 실행할 수 있는 함수에 제약이 생긴다.

 

그리고 인터럽트를 실행중인 경우에는, 해당 인터럽트는 비활성화된다. 다시 말해서, 마우스 인터럽트를 처리하고 있다면 마우스 인터럽트가 비활성화 된다. 따라서 인터럽트를 처리하는 도중에 들어오는 마우스 인터럽트는 모두 놓치게 된다. 다만 마우스 외의 다른 모든 인터럽트는 무시하지 않는다. 요약하면, 같은 종류의 인터럽트는 중첩하여 발생하지 않는다.

Top Half and Bottom Half

인터럽트 컨텍스트의 특성을 생각해봤을 때, 인터럽트는 매우 빨리 처리되어야 한다. 왜냐하면 그 동안 다른 인터럽트가 무시되기 때문이다. 예를 들어 네트워크로부터 들어온 데이터를 처리해야 하는데, 인터럽트 핸들러를 너무 오랫동안 실행하느라 패킷이 손실된다면 매우 큰일일 것이다. 따라서 리눅스는 인터럽트의 처리를 Top HalfBottom Half로 나누었다. Top Half는 아까 말한, 인터럽트 핸들러에서 처리되는 작업을 말한다. Bottom Half는 인터럽트가 끝난 후에 처리되는 작업을 말한다. Bottom Half를 보통 후반부 처리라고 한다.

 

리눅스는 매우 빨리 처리될 필요가 있으며, 하드웨어와 연관이 있는 작업을 Top Half에서 처리하고, 비교적으로 좀 더 늦게 처리되어도 되는 일은 Bottom Half에서 처리한다. 한 가지 예를 들어보면, 네트워크 인터페이스 카드로부터 데이터가 들어왔을 때, 가장 급하게 처리해야하는 일은 데이터를 읽어오는 것이다. 최대한 빠르게 읽어오지 않는다면 저렴한 네트워크 인터페이스 카드의 경우 버퍼가 작아서, 이전에 들어온 데이터가 누락될 수 있다. 따라서 이 부분을 Top Half로 처리한다. 버퍼에서 읽어온 데이터를 사용자 프로그램으로 전달하는 것은 좀 더 늦게 처리되어도 상관이 없다. (적어도 커널 관점에서는) 따라서 이 부분을 Bottom Half로 처리한다.