NeXTSTEPDRIVERKIT:Chapter2 7
- Handling Interrupts
인터럽트 핸들링(Handling Interrupts)
Most kernel-level drivers don't handle interrupts directly. Instead, the kernel notifies the driver of an interrupt by sending a Mach message to the interrupt port. An interrupt port is allocated when a direct driver object is initialized by the attachInterruptPort method of IODirectDevice. Figure 2-2 shows how interrupts are handled by the kernel and the I/O thread of a direct device driver.
대부분의 커널 레벨 드라이버는 인터럽트를 직접 처리하지 않습니다. 커널은 인터럽트 포트에 Mach 메시지를 보냄으로써 인터럽트를 드라이버에 알립니다. 직접 드라이버 객체가 IODirectDevice 의 attachInterruptPort 메소드에 의해 초기화 될 때 인터럽트 포트가 할당됩니다. 그림 2-2 는 커널과 직접 장치 드라이버의 I/O 스레드가 인터럽트를 처리하는 방법을 보여줍니다.
As Figure 2-2 shows, when an interrupt occurs (1), the kernel masks off further occurrences of that particular interrupt (2) and sends a message to the appropriate interrupt port (3). It then returns from the interrupt. The interrupt message contains no information except for a message ID in its header that identifies this message as an interrupt message.
그림 2-2 에서 알 수 있듯이 인터럽트가 발생하면(1), 커널은 해당 인터럽트를 더 이상 발생하지 않게하고(2), 적절한 인터럽트 포트로 메시지를 전송(3)합니다. 전송을 마친 이후에 인터럽트에서 복귀합니다. 인터럽트 메시지에는 이 메시지를 인터럽트 메시지로 식별하는 메시지 ID를 제외한 정보는 없습니다.
When the driver receives an interrupt message (4), it should examine the hardware to determine the cause of the interrupt and perform whatever action is necessary for continuing the I/O transfer in progress (5). It should then request that the kernel reenable interrupt notification for the device (6).
드라이버가 인터럽트 메시지를 받으면(4), 하드웨어를 검사해서 인터럽트의 원인을 판별하고 진행중인 I/O 전송을 계속하기 위해 필요한 모든 작업을 수행해야합니다(5). 그런 다음 커널이 장치에 대한 인터럽트 알림을 다시 활성화하도록 요청해야합니다 (6).
No further interrupt messages are sent to the driver until the kernel enables interrupts (7). If interrupts are shared between devices, the kernel reenables interrupts. If interrupts are not shared, the kernel resumes sending interrupt messages. See the section "Shared Interrupts" in this chapter.
커널이 인터럽트를 활성화할 때까지 더 이상 인터럽트 메시지는 드라이버에 전송되지 않습니다 (7). 인터럽트가 장치간에 공유되면 커널이 인터럽트를 다시 활성화 시킵니다. 인터럽트가 공유되지 않으면 커널은 인터럽트 메시지 전송을 재개합니다. 이 장의 "Shared Interrupts" 절을 참조하십시오.
When a device interrupts while a message is queued on the corresponding interrupt port, the kernel returns immediately without sending an interrupt message. After msg_receive() returns (which dequeues the message), the kernel regains the ability to send interrupt messages (but not until the device interrupts again). The memory for messages is fixed since the kernel can't allocate more memory at the interrupt level. The message buffers accommodate only one interrupt message, so any interrupts that arrive while an interrupt message is already queued are lost.
메시지가 해당 인터럽트 포트에 대기하는 동안 장치가 인터럽트되면 커널은 인터럽트 메시지를 보내지 않고 즉시 복귀합니다. msg_receive() 가 반환하면(메시지를 대기열에서 제외) 커널은 인터럽트 메시지를 보내는 기능을 다시 얻습니다(장치가 다시 인터럽트 될 때까지는 안됨). 커널이 인터럽트 수준에서 더 많은 메모리를 할당할 수는 없기때문에 메시지 메모리는 고정되어 있습니다. 메시지 버퍼는 한번에 하나의 인터럽트 메시지만 수용하기 때문에, 인터럽트 메시지가 이미 대기중인 동안에 도착한 모든 인터럽트는 손실됩니다.
The I/O thread automatically calls msg_receive() to get messages on its interrupt port. The default I/O thread also invokes the interruptOccurred or interruptOccurredAt: method in response to interrupt messages. Most of the device-specific classes in the Driver Kit do this for you.
I/O 스레드는 자동으로 msg_receive() 를 호출하여 인터럽트 포트에서 메시지를 가져옵니다. 기본 I/O 스레드는 인터럽트 메시지에 대한 응답으로 interruptOccurred 또는 interruptOccurredAt: 메소드도 호출합니다. Driver Kit 에 있는 대부분의 장치별 클래스는 이 작업을 수행합니다.
하나의 장치에 하나의 스레드(One Device, One Thread)
A driver is responsible for maintaining and dealing with three kinds of resources--hardware, the driver's private data, and client I/O requests. In a multiprocessor system, or in a system in which driver code contains interrupt handlers, a great deal of care must be taken to protect access to all three of these resources. Almost every function must use locks and disable interrupts. Even in the most well-thought out design, the presence of locks and interrupt disabling makes code hard to read and tends to lead to bugs. The problem is most apparent in code that manipulates the hardware directly.
드라이버는 하드웨어, 드라이버의 프라이빗 데이터 및 클라이언트 I/O 요청의 세 가지 종류의 리소스를 유지 관리합니다. 다중 프로세서 시스템(multiprocessor system) 또는 드라이버 코드에 인터럽트 처리기가 포함된 시스템에서는 이 세 가지 리소스 모두에 대한 접근을 보호하기 위해 많은 주의를 기울여야 합니다. 거의 모든 기능이 잠금(lock)을 사용한 뒤에 인터럽트를 비활성화해야 합니다. 가장 잘 고안된 설계라고 해도, 잠금 장치(presence of locks) 및 인터럽트 비활성화 기능은 코드를 읽기가 어렵게 만들고 버그를 일으킬 가능성이 있습니다. 문제는 하드웨어를 직접 조작하는 코드에서 가장 두드러지게 됩니다.
The Driver Kit's solution to this problem is this:
이 문제에 대한 Driver Kit 의 해결책은 다음과 같습니다:
- Given any hardware resource, one and only one thread can deal with that resource at a time. Interrupt handlers have no direct access to the resource.
하드웨어 자원이 주어지면 단 하나의 스레드만 해당 자원을 한 번에 처리할 수 있습니다. 인터럽트 핸들러는 자원에 직접 액세스 할 수 없습니다.
Consider a SCSI controller chip, for example. If exactly one thread in the system has access to the chip, there's no need for locking or for disabling interrupts to protect the code that manipulates the chip.
예를 들어 SCSI 컨트롤러 칩을 생각해보십시오. 시스템의 정확히 하나의 스레드만 칩에 액세스 할 수 있다면, 칩을 조작하는 코드를 보호하기 위해서 인터럽트를 잠그거나(locking) 비활성화할 필요가 없습니다.
Another way of looking at this is that for a given piece of hardware, only one operation at a time can happen. At point A, a driver might be setting up a chip to start I/O. At point B, the driver might be waiting for an interrupt from the chip. At point C, the driver might be responding to an interrupt and interrogating registers to see what caused the interrupt. A driver is never setting up a chip to start an I/O at the same time it's interrogating registers to see what caused an interrupt. In UNIX drivers, a combination of locks, interrupt disabling, and an interrupt-driven state machine assure that the driver attempts only one hardware operation at a time. In the Driver Kit, the one-at-a-time sequence of operations is enforced by having a single thread (the I/O thread) perform all hardware operations.
이것을 보는 또다른 방법은 주어진 하드웨어에 대해 한 번에 하나의 작업만이 발생할 수 있다는 것입니다. 지점 A 에서 드라이버가 I/O 를 시작하도록 칩을 설정하고 있을 수 있습니다. B 지점에서는 드라이버가 칩에서 인터럽트를 기다리고 있을 수 있습니다.
C 지점에서는 드라이버가 인터럽트에 응답하고 인터럽트 원인을 확인하기 위해 레지스터를 조사 할 수 있습니다. 드라이버는 인터럽트의 원인을 살펴보기 위해서 레지스터를 조사하는 동시에 I/O 를 시작하도록 칩을 설정하지 않습니다[1]. UNIX 드라이버에서는 잠금, 인터럽트 비활성화 및 인터럽트 구동 상태 시스템(interrupt-driven state machine)을 조합해서 드라이버가 한 번에 하나의 하드웨어 작업만을 시도합니다. Driver Kit 에서는 단일 스레드(I/O 스레드)가 모든 하드웨어 작업을 수행함으로써 한 번에 하나씩 작업 순서가 적용됩니다.
Another reason for this model is the desire to have drivers run in user space. There's no practical way for user-level drivers to run interrupt handlers with interrupts disabled; only kernel software can do this.
이 모델이 존재하는 또 다른 이유는 사용자 공간에서 드라이버를 실행하려는 욕구 때문입니다. 사용자 레벨 드라이버가 인터럽트가 비활성화된 인터럽트 핸들러를 실행하는 실용적인 방법은 없으며, 커널 소프트웨어만이 할 수 있습니다.
Some drivers in exceptional cases may choose to have multiple threads with access to one piece of hardware. The "one device, one thread" model is not an absolute. It's merely a design goal that has proved to be a viable basis for writing Driver Kit drivers.
예외적인 경우 일부 드라이버는 하나의 하드웨어에 액세스 할 수 있는 다중 스레드를 선택할 수 있습니다. "one device, one thread" 모델이 절대적인 것은 아닙니다. 이러한 모델은, 단지 Driver Kit 드라이버를 작성하기 위한 실행 가능한 기반으로 판명된 디자인 목표 일뿐입니다.
예제: 플로피 디스크
Let's look at a simple piece of hardware, a floppy disk controller chip. Floppy disk I/O consists of a predictable sequence of operations--starting an I/O request, waiting for an interrupt, and manipulating some registers. A feasible template for a floppy disk I/O thread looks like this:
(구조가)간단한 하드웨어, 플로피 디스크 컨트롤러 칩을 살펴 보겠습니다. 플로피 디스크 I/O 는 I/O 요청 시작, 인터럽트 대기 및 일부 레지스터 조작과 같은 예측 가능한 작업 시퀀스로 구성됩니다. 플로피 디스크 I/O 스레드를 위한 가능한 템플릿은 다음과 같습니다:
floppyThread()
{
<< Initialize local data structures. >>
<< Initialize hardware. >>
while(1) {
<< Wait for an I/O request from a client. >>
<< Set up the controller chip to start the I/O. >>
<< Wait for interrupt. >>
<< Manipulate controller registers to finish the I/O. >>
<< Notify client of I/O completion. >>
}
}
Not all devices are this simple, but this illustrates how a single thread suffices to manipulate a hardware resource.
모든 장치가 이처럼 단순한것은 아니지만, 단일 스레드가 하드웨어 자원을 조작하는 경우에 대한 충분한 방법을 보여줍니다.
전통적인 UNIX 인터럽트 처리
Compare the Driver Kit's interrupt handling to the UNIX approach.
Driver Kit 의 인터럽트 처리를 UNIX 방식과 비교해 보시기 바랍니다.
The traditional UNIX driver design involves a conceptual top-half, which is code called from higher layers in the kernel to initiate an I/O, and a bottom-half, which consists of various interrupt handlers and I/O completion logic. A simple example follows:
전통적인 UNIX 드라이버 디자인은 I/O 를 시작하기 위해 커널의 상위 계층에서 호출되는 코드 인 상반부 개념과 다양한 인터럽트 처리기와 I / O 완료 논리로 구성된 하반부를 포함합니다. 간단한 예제는 다음과 같습니다:
- High-level kernel code calls the driver's strategy() or write() or read() routine (in the driver's top-half) to start an I/O.
고급 커널 코드는 I/O 를 시작하기 위해 드라이버의 strategy() 또는 write() 또는 read() 루틴(드라이버의 상단 부분)을 호출합니다. - The driver's top-half enqueues the I/O on a queue that is private to the driver, perhaps after translating the incoming data into a driver-specific format.
드라이버의 맨 위쪽 절반은 들어오는 데이터를 드라이버 관련 형식으로 변환한 후 드라이버 전용의 큐에 I/O 를 대기열에 넣습니다. - If the bottom half of the driver is idle, the top-half calls a start() routine to initiate a hardware operation.
드라이버의 아래 부분이 유휴 상태인 경우, 상단부는 start() 루틴을 호출해서 하드웨어 작동을 시작합니다. - The bottom-half takes over from here. When an interrupt occurs, the driver's interrupt handler runs and decides either that the hardware needs some more attention before completing the I/O (in which case a state machine is advanced and the driver awaits another interrupt) or that the I/O is complete (in which case higher-level code in the kernel is notified of this fact).
하반부는 여기에서 이어집니다. 인터럽트가 발생되면 드라이버의 인터럽트 처리기가 실행되어 I/O(이 경우 상태 머신(state machine)이 진행되며 드라이버는 다른 인터럽트를 대기)를 완료하기 전에 하드웨어가 더 주의를 기울여야 하는지 또는 I/O 가 완료되었는지(이 경우 커널의 상위 레벨 코드에 이 사실을 통보)를 결정합니다.
Things can actually get much more complicated than this. For instance, a certain section of code may sometimes run as the result of an interrupt and run the rest of the time for some other reason. Because an interrupt might occur while the code is already running, the code must protect itself during critical sections by disabling interrupts. One example of code that must be protected is a function that starts I/O. In the example given previously in this section, the start() function doesn't run as the result of an interrupt. However, if more work remains at I/O completion time, the start() function is called from the interrupt handler. The section of code that starts the I/O must be protected from interrupts so that it can complete its work correctly.
상황은 실제로 이것보다 훨씬 복잡해질 수 있습니다. 예를 들어, 코드의 특정 부분은 때때로 인터럽트의 결과로 실행되며 서로 다른 이유로 다른 시간에 실행됩니다. 코드가 이미 실행되는 동안 인터럽트가 발생할 수 있으므로 코드는 인터럽트를 사용하지 않도록 설정하여 중요 섹션에서 자체를 보호해야합니다. 보호되어야하는 코드의 예제라면 I/O 를 시작하는 함수입니다. 앞에서 설명한 예제에서 start() 함수는 인터럽트의 결과로 실행되지 않습니다. 그러나 I/O 완료 시간에 더 많은 작업이 남아 있으면 start() 함수가 인터럽트 처리기에서 호출됩니다. I/O 를 시작하는 코드 섹션은 인터럽트로부터 보호되어 작업을 올바르게 완료할 수 있어야 합니다.
Sometimes interrupts are disabled for hundreds of microseconds or more. Such long periods without interrupts seriously hamper system throughput and cripple the ability of the system to respond to real-time events such as the arrival of serial data.
간혹 인터럽트가 수백 마이크로초 또는 그 이상 동안 비활성화되는 경우가 있습니다. 인터럽트가 없는 오랜 대기시간은 시스템 처리량을 심각하게 저해하며 직렬 데이터의 도착과 같은 실시간 이벤트에 응답하는 시스템의 기능을 손상시킵니다.
Another problem with running some subset of a driver's code at interrupt level is that locking shared data structures (even if they are shared only between the files constituting one driver) is difficult on a multiprocessor system. To access a critical data structure on a multiprocessor system--when the data can be accessed at interrupt level by all processors--noninterrupt code must first disable interrupts on all processors and then acquire a lock.
인터럽트 수준에서 드라이버 코드의 일부 하위 집합을 실행하는 또 다른 문제는 다중 프로세서 시스템에서 공유 데이터 구조(하나의 드라이버를 구성하는 파일들 사이에서만 공유 되더라도)를 잠그는(lock)것이 어렵다는 것입니다. 다중 프로세서 시스템에서 중요한 데이터 구조에 접근하려면 - 모든 프로세서에서 인터럽트 수준으로 데이터에 접근할 수 있는 경우 비 인터럽트 코드는 먼저 모든 프로세서에서 인터럽트를 비활성화한 다음 잠금(lock)을 가져와야 합니다.
사용자 정의 인터럽트 핸들러(Custom Interrupt Handlers)
You may need to write your own interrupt handler in some cases. A driver for a device with high data rates that depends on programmed I/O would be a good candidate for a custom interrupt handler, for instance. The IODirectDevice getHandler:level:argument:forInterrupt: method has been provided to support such handlers. It specifies an interrupt handler function for the driver.
어떤 경우에는 독자적인 인터럽트 처리기를 작성해야할 수도 있습니다. 예를 들어, 프로그래밍된 I/O 에 의존하는 높은 데이터 속도를 갖는 디바이스용 드라이버는 사용자 정의 인터럽트 핸들러를 위한 좋은 후보가 될 것입니다. 이러한 핸들러를 지원하기 위해 IODirectDevice 'getHandler:level:argument:forInterrupt:' 메소드가 제공되었습니다. 드라이버에 대한 인터럽트 처리기 기능을 지정합니다.
Warning: Use interrupt level IPLDEVICE (defined in /NextDeveloper/Headers/kernserv/i386/spl.h) unless a higher interrupt level is absolutely necessary and you're fully aware of the possible consequences of using it.
Warning: 더 높은 인터럽트 레벨이 절대적으로 필요하며, 그것을 사용할 때 발생할 수 있는 결과를 완전히 인식하지 않는한 인터럽트 레벨 IPLDEVICE(/NextDeveloper/Headers/kernserv/i386/spl.h 에서정의된)를 사용하십시오.
If you want the I/O thread to take some action, the interrupt handler can call the IOSendInterrupt() function, which sends a Mach message to the I/O thread with the specified message ID.
I/O 스레드가 어떤 동작을 수행하게 하려면, 인터럽트 핸들러가 Mach 메시지를 지정된 메시지 ID 를 사용해서 I/O 스레드로 보내는 IOSendInterrupt() 함수를 호출할 수 있습니다.
Warning: Your driver must not send Objective C messages in an interrupt handler, since sending a message can result in memory allocation. Allocating memory can lead to sleeping, and interrupt handlers must not sleep, as described in NEXTSTEP Operating System Software.
Warning: 메시지를 보내면 메모리가 할당될 수 있기 때문에 드라이버는 Objective C 메시지를 인터럽트 처리기에서 보내지 않아야 합니다. NEXTSTEP Operating System Software 에 설명되어 있는대로 메모리 할당은 절전 모드로 전환될 수 있으며, 인터럽트 핸들러는 이를 방해할 수 있다.
Read "Designing a Loadable Kernel Server" in NEXTSTEP Operating System Software for more information on executing as the result of an interrupt.
인터럽트의 결과로 실행하는 방법에 대한 자세한 내용은 NEXTSTEP Operating System Software 에서 "Designing a Loadable Kernel Server" 를 읽으십시오.
Devices may share the same interrupt. Since there are only 15 IRQs available on Intel-based computers, sharing interrupts may be necessary for some configurations.
장치는 동일한 인터럽트를 공유할 수 있습니다. Intel 기반 컴퓨터에서는 15 개의 IRQ 만 사용할 수 있기 때문에 일부 설정(configurations)에서는 공유 인터럽트가 필요할 수 있습니다.
Each time an interrupt occurs for a shared IRQ number, every driver that shares the interrupt gets an interrupt message. If the driver has its own interrupt handler, it is called.
공유 IRQ 번호에 대해서 인터럽트가 발생할 때마다 인터럽트를 공유하는 모든 드라이버는 인터럽트 메시지를 받습니다. 드라이버에 자체 인터럽트 처리기가 있으면 호출됩니다.
At the end of your interrupt handling method or function, you must reenable the interrupt--whether or not the interrupt was intended for your device. You accomplish this by invoking enableAllInterrupts:
인터럽트 처리 방법이나 기능이 끝나면 인터럽트가 장치를 대상으로 했는지의 여부에 관계없이 인터럽트를 다시 활성화해야 합니다. enableAllInterrupts: 를 호출해서 이를 수행합니다.
[self enableAllInterrupts];
If you are using a special interrupt handler, reenable interrupts by calling IOEnableInterrupt() in the handler. You should only reenable the interrupt after removing the source of the interrupt--by clearing the interrupt status register on the device, for example, or by using whatever mechanism is necessary for the hardware your driver controls.
특별한 인터럽트 핸들러를 사용하고 있다면 핸들러에서 IOEnableInterrupt() 를 호출해서 인터럽트를 다시 활성화 하십시오. 인터럽트 소스를 제거한 뒤에는 장치의 인터럽트 상태 레지스터를 지우거나 드라이버가 제어하는 하드웨어에 필요한 메커니즘을 사용해서 인터럽트를 다시 활성화해야합니다.
The shared interrupt is masked each time an interrupt occurs. It is only unmasked after all drivers that are sharing the interrupt reenable their own interrupts.
공유 인터럽트는 인터럽트가 발생할 때마다 보호(masked)됩니다. 인터럽트를 공유하는 모든 드라이버가 자신의 인터럽트를 다시 활성화한 뒤에만 보호가 해제(unmasked)됩니다.
IODisableInterrupt() allows handlers of non-shared interrupts to indicate that the interrupt should be left disabled on return from the interrupt handler.
IODisableInterrupt() 는 공유되지 않는 인터럽트 핸들러가 인터럽트 핸들러에서 돌아왔을때 인터럽트를 사용하지 않도록 설정해야 함을 나타냅니다.
Note: IOEnableInterrupt() and IODisableInterrupt() must be called only inside a special interrupt handler function, that is, at interrupt level. (The special interrupt handler is the one you specified in getHandler:level:argument:forInterrupt:.) These functions can't be called from any other context. You shouldn't call them from interruptOccurred, for example.
Note: IOEnableInterrupt() 와 IODisableInterrupt() 는 특별한 인터럽트 핸들러 함수 안에서만, 즉 인터럽트 레벨에서 호출되어야 합니다. (특별 인터럽트 처리기는 getHandler:level:argument:forInterrupt: 에서 지정한 핸들러) 이러한 함수는 다른 컨텍스트에서 호출할 수 없습니다. 예를 들어 interruptOccurred 에서 호출해서는 안됩니다.
Enable shared interrupts for your system by setting the "Share IRQ Levels" key in your driver's Default.table:
드라이버의 Default.table 에 "Share IRQ Levels" 키를 설정(setting)해서 시스템에 공유 인터럽트를 활성화 하십시오:
"Share IRQ Levels" = "Yes";
Note: Currently, shared interrupts imply level-triggered interrupts on EISA and PCI bus machines. Shared interrupts are not supported on ISA bus machines.
Note: 현재 공유 인터럽트는 EISA 및 PCI 버스 시스템에서 level-triggered interrupts 를 의미합니다. ISA 버스 컴퓨터에서는 공유 인터럽트가 지원되지 않습니다.
Notes
- ↑ 칩을 설정하지 않는다는 의미를 정확하게 다시 번역체크 해야함