Hardware events can cause an interrupt. An interrupt triggers an implicit call using an address supplied in the IDT. This directs control to an interrupt handler. The IDT is a system structure consisting of 256 entries, each of which can point to a different handler. The first 32 entries are reserved for special use by the CPU for events such as breakpoints and hardware faults. The remaining entries may be used to handle external interrupts. The index of a particular handle is called the interrupt vector.
If handling the interrupt takes little time and requires no system calls other than certain interrupt-related system calls, the interrupt handler can process the interrupt itself; the interrupt handler executes in the context of the thread running when the interrupt occurred. Otherwise, the handler should invoke an interrupt thread, using the low-level kn__RtSemaphore or kn__RtMailbox objects, to finish processing the interrupt. Interrupt threads have their own context and are not dependent on the context of the thread that was interrupted.
After the interrupt is serviced by either the interrupt handler or the interrupt thread, control returns to the interrupted thread.
Most interrupts of interest to INtime programmers are generated by a device interface which is being programmed by an INtime application. Such devices generate two general classes of interrupt. When an interrupt is delivered to the CPU, the CPU must discover which interrupt have been delivered then look up its vector and invoke the appropriate interrupt handler. Over the years the method by which the CPU is informed of the interrupt vector has evolved, resulting in some programming complexity.
The original means of delivering an interrupt to the CPU was by means of a dedicated signal, called an interrupt request line, or IRQ. In order that multiple different devices can generate interrupts the IRQs are connected to an interrupt controller. Each input line on the controller is programmed to generate a different vector. The original Programmable Interrupt Controller (PIC) would assert the CPU's single interrupt signal, then the CPU would interrogate the PIC to discover the vector used by the particular IRQ. The PIC had to prioritize the IRQs to determine which one would be forwarded first in the event that two interrupts occurred simultaneously, and maintain the state of the other interrupts while one was being handled by the CPU. The handshake between the CPU and the PIC contributed to interrupt latency, which is the time between the signal being asserted, and the time at which the CPU starts to execute its interrupt handler. Each device had its own input on the controller, and the number of interrupts could be extended by cascading multiple controllers.
The next stage was to allow multiple devices to share an IRQ. Multiple devices could be connected to a single input pin on the PIC. Each device might have its own handler but they are all associated with the same vector. This was efficient in terms of hardware design but it means that every time that particular IRQ was asserted, all of the handlers associated with it had to be invoked to be sure that the interrupt was handled. It also added complexity to the interrupt handler because the code has to determine whether or not the interrupt came from its device before proceeding. This method of delivery first came about with the arrival of the PCI bus, where a greater number of devices could be installed in a system compared to the original ISA architecture.
With the arrival of multi-core CPUs a new interrupt controller was needed. Not only was it necessary to provide a vector when a given interrupt was asserted but a decision had to be made about which CPU core or cores were to be interrupted. The Advanced Programmable Interrupt Controller, or APIC, allowed for this, and it also made the delivery of the vector to the CPU core or cores more efficient by eliminating the handshake and just simply writes the vector to the CPU via a bus write cycle. Multiple APICs could be installed side by side without cascading, eliminating that source of additional latency also.
The current method of delivering interrupts from a device to the CPU cuts out the interrupt controller altogether. The device itself writes the vector directly to the CPU core or cores via a mechanism called a Message-Signaled Interrupt (MSI), which is a bus write cycle directly from the device to the CPU. This results in still less latency. MSI is the preferred method of delivering interrupts in the INtime kernel because of this minimal latency and because the delivery is independent of all other interrupt sources. Another extension of MSI is that it is possible for a device to deliver different vectors for different device statuses, allowing for simpler and more efficient programming of the interrupt handlers for the device.
The processor uses the IDT entry as a pointer to the interrupt handler to execute for the specific interrupt. Each IDT entry contains the physical address of the interrupt handler.
The hardware assigns a number to the cause of each interrupt and gives it an entry in the IDT. The INtime kernel does not use entries IDT 128-255. Because of the constraints of inter-operating with Windows, the entries assigned for external interrupts are different between the INtime for Windows and INtime Distributed RTOS kernels. This is one reason why interrupt levels are assigned to these interrupts, so that code is portable from one environment to the other. The entries are allocated as shown below.
See also: the user's guide for your microprocessor.
IDT Entry | Description |
---|---|
0-31 | Reserved for system events (faults, exceptions, breakpoints) |
32-223 | Reserved for internal kernel use |
224–255 | INtime interrupt entries |
In the PC architecture, interrupts from hardware interfaces are commonly signaled to a PIC. This is configured in one of two ways. In legacy systems the controller resembles a pair of 8259A PIC devices cascade so that the slave PIC is connected to input #2 on the master PIC. In this mode there are 15 inputs available to connect interrupt lines (IRQs) to the controller. Of these up to 4 may be used for PCI devices. All PCI devices in the system must share these four lines. Certain inputs are generally assigned to specific devices in the PC architecture as in the following illustration:
Modern systems use the controller in APIC mode. In this mode the first 16 inputs are reserved for legacy device (ISA) interrupts leaving the rest available for handling PCI interrupts. The interrupt lines are connected to the I/O APIC (part of the chipset), which delivers the interrupts to the CPU via the Local APIC (part of the CPU).
The interrupt lines of the master and slave PICs are associated with interrupt levels. An interrupt level is a value defined by INtime which encodes both the interrupt input (IRQ) and its associated priority into a single value. All system calls which deal with interrupts use the interrupt level value to refer to an interrupt source.
In legacy mode the interrupt lines are assigned priority in order of the input on the 8259 PIC. IRQ0 (the system timer) has higher priority than IRQ5 on the mast PIC for example. All of the slave PIC interrupts connected to IRQ2 also have higher priority than IRQ5. If two interrupts occur simultaneously, the PIC informs the CPU of the higher-priority interrupt first.
Whether an interrupt handler services an interrupt level by itself or invokes an interrupt thread to service the interrupt depends on the system calls (these are limited) and the amount of time needed. An interrupt signal disables all interrupts; they remain disabled until the interrupt handler either services the interrupt and exits, or invokes an interrupt thread. Invoking an interrupt thread enables higher-priority interrupts (and in some cases, the same priority interrupts) to be accepted.
The following sections describe:
Interrupt handlers are generally written as C procedures, but they can be written in assembly language. You must save and restore all register values on entry and exit from the handler.
An interrupt handler uses the interrupted thread's stack.
If an interrupt handler services interrupts for a given level without invoking an interrupt thread, it must do these things:
When writing an interrupt handler, follow these guidelines:
This table lists common operations for interrupts and the system calls that perform the operations:
To . . . | Use this system call . . . |
---|---|
Assign handler | SetRtInterruptHandler SetRtInterruptHandlerEx |
Remove interrupt level | ResetRtInterruptHandler |
Send EOI | SignalEndOfRtInterrupt |
Invoke thread | SignalRtInterruptThread |
Put thread to sleep | WaitForRtInterrupt |
Enable level | EnableRtInterrupt |
Disable level | DisableRtInterrupt |
Get current level | GetRtInterruptLevel |
Set up segment | EnterRtInterrupt |
Get information about level | GetRtInterruptInfo |
This shows the order to make interrupt system calls and lists calls that interrupts frequently use:
|
Before an interrupt handler can service an interrupt level, a thread must invoke SetRtInterruptHandler to bind the handler to the interrupt level. SetRtInterruptHandler places a pointer to the first instruction of the handler in the appropriate entry in the IDT.
These are the parameters you supply in SetRtInterruptHandler:
pHandler
to specify the starting address of the interrupt handler. When an interrupt of that level occurs, control automatically transfers through the IDT to the handler.
byMaxInt
to 0, to specify that there is no interrupt thread for the level. Use ResetRtInterruptHandler to cancel the assignment of a handler by clearing out the appropriate entry in the IDT. The call also disables the specified level.
If an interrupt handler invokes an interrupt thread, it must do these things:
An interrupt thread must call these functions in the indicated order:
The interrupt thread has its own resources and runs in its own environment. The interrupt thread can use exception handlers, whereas the interrupt handler always handles exceptions inline.
The INtime kernel assigns priorities to INtime interrupt threads based on the handler's interrupt level. Lower-priority interrupts are disabled when an interrupt thread is running and will run later. If this is a problem for your application, use an ordinary thread instead of an interrupt thread. This enables you to control the thread's priority. You can use low-level INtime RT kernel calls to signal an ordinary or non-interrupt thread. When you use low-level INtime kernel calls in an INtime interrupt handler, you need to create the service thread, cause the service thread to perform specific functions, and cause the handler to perform specific functions.
The PCI bus architecture as used in the PC allows less flexibility to the system designer regarding how interrupts are routed and separated from other sources. While it may be possible on a system consisting of a PCI Local Bus architecture to separate PCI device interrupts from each other, as soon as a bridge is added, and more buses connected, then it is almost impossible to avoid the possibility that different devices may be sharing the same interrupt line.
INtime provides support to allow different devices to share the same interrupt line by providing generic interrupt handlers internally, which call out to user-supplied, device-specific handlers. When an interrupt occurs on such a line, all the handlers installed for that hardware interrupt level are called in sequence, starting with the first installed. This results in a little extra overhead over standard handlers, so use of this feature should be restricted to situations where you cannot guarantee exclusive access to an interrupt line.
Another consequence of interrupt sharing is that the interrupt signals are level-triggered. That is, the PIC is programmed to recognize an interrupt condition whenever the interrupt line is asserted. Non-PCI (non-shared) interrupts are edge-triggered; the PIC recognizes when the interrupt line changes state to active. The practical consequence of this level triggering is that if the device is not made to de-assert its interrupt line before the CPU exits from the handler, then the interrupt condition is still asserted, and the handler is immediately re-entered. Against that, remember that while the CPU is executing an interrupt handler, no other interrupt will occur until the CPU interrupt mask is cleared. Thus we want to minimize the time spent in each shared interrupt handler, but do sufficient work to remove the interrupt condition.
To install a shared interrupt handler, use SetRtInterruptHandlerEx, and specify that this is to be a shared interrupt handler by OR-ing the SHARED_LEVEL macro with the desired interrupt level. SetRtInterruptHandlerEx returns a modified interrupt level value which all subsequent interrupt system calls must use. Here is an example of a handler installed on the level associated with IRQ11:
wLevel = SetRtInterruptHandlerEx( SHARED_LEVEL | IRQ11_LEVEL, 255, SharedIntHandler, Param );
An INtime shared interrupt handler has a different interface when compared to a standard interrupt handler. The standard handler is invoked directly from the IDT when the interrupt occurs, whereas the shared interrupt handler is called from a common handler in INtime's RT kernel. The kernel thus can perform tasks normally required of the handler, such as setting the process context and finding the interrupt level value. An example of a shared handler looks like this:
__INTERRUPT void SharedIntHandler(WORD wRACS, WORD wLevel, LPVOID Param) { // TODO: declare any local variables here __SHARED_INTERRUPT_PROLOG(); // Interrupt-processing statements go here SignalRtInterruptThread(wLevel); __SHARED_INTERRUPT_RETURN(); }
The parameter lpParamPtr passed to SetRtInterruptHandlerEx is passed unmodified to the interrupt handler. The first parameter of the handler is not for use by the developer.
Note that the PROLOG and RETURN macros differ from those used in the standard interrupt handler. The return statement should not be used in the handler; use the __SHARED_INTERRUPT_RETURN() macro instead.
Other than the differences illustrated above, and taking into account the restrictions imposed on a shared interrupt handler, the same techniques can be applied as to a standard interrupt handler. Some interrupt system calls will have no effect. For example, SignalEndOfRtInterrupt is ignored for shared handlers, since this action is taken automatically after all handlers have been called.
Shared interrupt handlers should not call GetRtInterruptLevel since the return value is meaningless in shared handler context. The encoded interrupt level is supplied as a parameter to the handler. Shared handlers should not call EnterRtInterrupt since the process context is already set up on shared handler entry.
If your device supports MSI then you can tell the RT kernel to use this feature to invoke your interrupt handler. MSI handlers are never shared, since each MSI event generates a different interrupt vector. This has two advantages; the performance is slightly better than a shared handler because only one is called, and the device is isolated from any other interrupt source.
Generally the same rules should be followed when writing an MSI handler as when writing a shared handler. Keep the handler short but perform sufficient operations to remove the interrupt condition at the device and signal a thread to perform the rest of the work of handling the interrupt condition.
To install an MSI handler, first set up a structure identifying the PCI device and any user parameter, then use SetRtInterruptHandlerEx and specify that this is to be an MSI handler by specifying the predefined value MSI_LEVEL
for the level parameter. SetRtInterruptHandlerEx returns a modified interrupt level value which all subsequent interrupt system calls must use. Here is an example of a handler installed for a PCI device described in a PCIDEV structure:
MSI_PARAM msi; msi.PciAddress = MKPCIADDR(&pci); msi.ReservedZero = 0; msi.Param = Param; wLevel = SetRtInterruptHandlerEx(MSI_LEVEL, 255, MsiHandler, &msi);
Note that the PCI structure is of type PCIDEV and is usually populated by a call to PciFindDevice. The MKPCIADDR macros encodes an identifier for the device which the kernel uses to program the device to generate an MSI. The Param
value is the user parameter which is passed unmodified to the interrupt handler.
An INtime MSI handler has a similar interface to a shared interrupt handler. The MSI handler is called from a common handler in INtime software's RT kernel. The kernel thus can perform tasks normally required of the handler, such as setting the process context and finding the interrupt level value. An example of an MSI handler looks like this:
__INTERRUPT void MsiHandler(WORD wRACS, WORD wLevel, MSI_PARAM *msi) { LPVOID *Param = msi->Param; // user parameter // TODO: declare any local variables here __SHARED_INTERRUPT_PROLOG(); // Interrupt-processing statements go here SignalRtInterruptThread(wLevel); __SHARED_INTERRUPT_RETURN(); }
The parameter of type MSI_PARAM or MSI_PARAM_EX passed to SetRtInterruptHandlerEx is passed unmodified to the interrupt handler. The first parameter of the handler is not for use by the developer.
Other than the differences illustrated above, and taking into account the restrictions imposed on a shared interrupt handler, the same techniques can be applied as to standard interrupt handler. It is not required to call SignalEndOfRtInterrupt for MSI handlers, since this action is taken automatically by the kernel after the handler returns.
Like shared handlers, MSI handlers should not call GetRtInterruptLevel since the return value is meaningless in MSI handler context. The encoded interrupt level is supplied as a parameter to the handler. Calling EnterRtInterrupt is also superfluous since the process context is already set up on MSI handler entry.
The MSI specification allows a device to generate interrupts on multiple vectors. Each might be used to signal a different condition or else to signal a different CPU in the system. Multiple MSI support is present in the INtime kernel and requires special programming. Each interrupt vector generated by the device has its own handler and thread.
Multiple MSI support is not available in INtime for Windows Shared Mode, due to the limited number of interrupt vectors available to INtime in this mode.
To find out if a device is capable of generating multiple MSIs, call PciGetMsiCount to determine the number of vectors supported by the device. If the number is greater than 1, then the device supports multiple MSIs. In this case, create an interrupt object for the number of vectors reported, using AllocateRtInterrupts. The interrupt object reserves and encapsulates a range of interrupt vectors which meets the alignment requirements of the MSI specification.
Using this interrupt object, an interrupt handler may be installed for each of the available vectors using SetRtInterruptHandlerEx and a MSI_PARAM_EX structure:
MSI_PARAM_EX msi;
msi.PciAddress = MKPCIADDR(&pci);
msi.MsiIndex = 0; // which MSI source is to be programmed?
msi.MsiVectors = AllocateRtInterrupts(nVectors);
msi.ReservedZero = 0;
msi.Param = Param;
wLevel = SetRtInterruptHandlerEx(MSI_LEVEL, 1, MsiHandler, &msi);
Further instances of the MSI_PARAM_EX structure may be created with different values of MsiIndex in order to install further handlers for the device.
The Visual Studio INtime Application Wizard generates generic code which detects if multiple MSIs are available and will create a handler for the first vector (MsiIndex = 0) for the device.
While the interrupt thread is processing, the INtime kernel disables all lower interrupt levels. The associated interrupt level is either disabled or enabled, depending on the byMaxInt
parameter passed to SetRtInterruptHandler.
If the number of pending interrupts is less than the interrupt limit specified, the associated interrupt level is enabled. All SignalRtInterruptThread calls that the handler makes (up to the limit specified) are counted.
If the associated interrupt level is disabled (the number of pending interrupts equals the pending interrupt limit) while the interrupt thread is running, the call to WaitForRtInterrupt enables that level.
You should call WaitForRtInterrupt from interrupt threads immediately after initializing and immediately after servicing interrupts. This call suspends the interrupt thread until the interrupt handler for the same level resumes it by invoking SignalRtInterruptThread.
If the number of pending interrupts is greater than 0 when the interrupt thread calls WaitForRtInterrupt the thread is not suspended. Instead, it continues processing the next WaitForRtInterrupt request.
When a thread becomes an interrupt thread by calling SetRtInterruptHandler or SetRtInterruptHandlerEx, the INtime kernel assigns a priority to it according to the interrupt level to be serviced. The following table shows the relationship between the encoded level (the value used for the wLevel parameter of SetRtInterruptHandler), the Master and Slave interrupt levels, the IDT slot and the priorities of threads that service those levels.
Note: If an interrupt thread's priority exceeds the maximum priority attribute of its process, the interrupt thread fails to set up and the INtime kernel returns an exceptional condition code. Prevent this by increasing the process's maximum thread priority using SetRtProcessMaxPriority.
IRQ | INtime Encoding | Interrupt Thread Priority |
---|---|---|
0 | 0x0008 | 18 |
1 | 0x0018 | 34 |
3 | 0x0038 | 67 |
4 | 0x0048 | 83 |
5 | 0x0058 | 98 |
6 | 0x0068 | 114 |
7 | 0x0078 | 130 |
8 | 0x0020 | 36 |
9 | 0x0021 | 38 |
10 | 0x0022 | 40 |
11 | 0x0023 | 42 |
12 | 0x0024 | 44 |
13 | 0x0025 | 46 |
14 | 0x0026 | 48 |
15 | 0x0027 | 50 |
16 | 0x0030 | 52 |
17 | 0x0031 | 54 |
18 | 0x0032 | 56 |
19 | 0x0033 | 58 |
20 | 0x0034 | 60 |
21 | 0x0035 | 62 |
22 | 0x0036 | 64 |
23 | 0x0037 | 66 |
24 | 0x0040 | 68 |
25 | 0x0041 | 70 |
26 | 0x0042 | 72 |
27 | 0x0043 | 74 |
28 | 0x0044 | 76 |
29 | 0x0045 | 78 |
30 | 0x0046 | 80 |
31 | 0x0047 | 82 |
When a handler for an MSI or other non-legacy interrupt is installed, an unused entry starting at entry 16 is found and the thread priority for the entry used for the interrupt thread.
You need to create a thread to handle the interrupt. When creating the thread, set the thread priority so that it does not disable lower-level interrupts.
byMaxInt
set to 0. This indicates there is no associated RT interrupt thread.
The following code shows how you can use INtime kernel calls in RT interrupt handlers:
__INTERRUPT void IntHdlr(void) { WORD IntLevel; __INTERRUPT_PROLOG(); IntLevel = GetRtInterruptLevel(IntLevel); EnterRtInterrupt (IntLevel); */ * perform any handler level interrupt processing here */ /* * The handler will now signal an ordinary INtime thread which is waiting at * a Kernel semaphore. * Get scheduling lock prior to making the signaling call. */ knStopRtScheduler(); /* * The knStopRtScheduler call prevents a thread switch from immediately * occurring as a result of knReleaseRtSemaphore. * If a thread is waiting at knsemaphore, it will be made ready but will * not run immediately. */ knReleaseRtSemaphore(knsemaphore); /* signal ordinary thread */ /* * The SignalEndOfRtInterrupt call sends an End of Interrupt (EOI) to the * interrupt controller. */ SignalEndOfRtInterrupt(IntLevel); /* * Release the scheduling lock and resume normal scheduling. * At this point the highest priority ready thread will run, possibly * even before the return from knStartRtScheduler. * * If knStartRtScheduler causes an immediate thread switch, the rest * of the handler code will not be executed until the *interrupted* * thread gets to run again. For this reason, the knStartRtScheduler * call should be the very last call in the handler, just prior to the * return. */ knStartRtScheduler(); __INTERRUPT_RETURN(); }
The INtime kernel may mask less important interrupts automatically while the interrupt thread is running. Occasionally you may want to prevent interrupt signals from causing an immediate interrupt at the thread's own level. For example, in a device driver finish procedure, you may want to disable interrupts from the device before deleting resources an interrupt handler or thread would require. You can disable each interrupt level except the system clock. You disable a level by using the DisableRtInterrupt system call.
If the level is disabled, the interrupt signal is blocked until the level is enabled, at which time the signal is recognized by the CPU. However, if the signal is no longer emanating from its source, it is not recognized and the interrupt is not handled.
An interrupt level can be disabled in these ways:
Sometimes, an interrupt thread may finish with a buffer of data before it finishes its processing. An example of this is a thread that processes a buffer and then waits at a mailbox, possibly for a message from a user terminal, before calling WaitForRtInterrupt. If other buffers of data are available to the handler (the number of pending interrupts has not reached the limit), this does not present a problem. The handler can continue accepting interrupts and filling empty buffers. However, if the interrupt thread is processing the last available buffer (i.e., the limit has been reached), the interrupt handler will not receive further interrupts because the interrupt level is disabled. This may be an undesirable situation if the interrupt thread takes a long time before calling WaitForRtInterrupt.
To prevent this situation, the interrupt thread can call EnableRtInterrupt immediately after it processes the buffer, enabling its associated interrupt level. This means that while the thread engages in its time-consuming activities, the interrupt handler can accept further interrupts and place the data into the buffer just released by the thread. You can use this technique whenever the limit is 1, whether or not you use a buffer.
However, if the interrupt handler fills the buffer and calls SignalRtInterruptThread before the thread calls WaitForRtInterrupt, these events occur:
If the interrupt thread calls EnableRtInterrupt when the count is below the limit, the level is enabled and no exception code is returned. However, if the interrupt thread tries to enable the interrupt level when the count is greater than the limit, the EnableRtInterrupt system call returns the E_CONTEXT condition code.
If a thread other than an interrupt thread tries to enable the level, one of these events may occur:
The next figure illustrates the relationships between the servicing patterns of interrupt handlers and interrupt threads.
The handler performs the simple, less time-consuming functions; it signals the interrupt thread to perform more complicated functions. The handler sends information to the thread in data buffers. The number of pending interrupts influences when and how interrupts are disabled.
An interrupt handler might signal an interrupt thread sometimes, but not every time. For example, an interrupt handler may put characters entered at a terminal into a buffer. If the character is an end-of-line character, or if the character count maintained by the interrupt handler indicates the buffer is full, the interrupt handler calls SignalRtInterruptThread to activate the interrupt thread to process the contents of the buffer. Otherwise, the interrupt handler calls SignalEndOfRtInterrupt and then returns control to the application thread.
An interrupt handler that reads data from an external device, character by character, and places the characters into a buffer is an example of a single-buffer interrupt handler. When the buffer fills, the handler calls SignalRtInterruptThread to signal an interrupt thread to further process the data. There is only one buffer for the data, so the interrupt level associated with the interrupt thread must be disabled while the thread is processing.
Because the thread called SetRtInterruptHandler with byMaxInt
equal to 1, the INtime kernel automatically disables the interrupt level when the handler invokes SignalRtInterruptThread.
This prevents the interrupt handler from destroying the contents of the buffer by continuing to place data into an already full buffer. This figure illustrates single buffering:
If you require only single buffering in interrupt servicing, specify 1 for the byMaxInt
parameter in SetRtInterruptHandler.
In this example, the interrupt handler and the interrupt thread provide the same functions as in the previous example, but they use multiple buffers. In this case, the interrupt level associated with the thread need not always be disabled while the thread runs. Instead, the thread can process a full buffer while the handler continues to accept interrupts. When the handler fills a buffer, it calls SignalRtInterruptThread to start the interrupt thread, as in the first example. However, because the byMaxInt
is greater than 1, the interrupt level is not disabled. Instead, the handler continues to accept interrupts, placing the data into the next empty buffer.
While this occurs, the interrupt thread processes the full buffer. When the thread completes the processing, it calls WaitForRtInterrupt to indicate that it is ready to accept another SignalRtInterruptThread request (another full buffer),and to indicate that the buffer it just finished processing is available for re-use by the handler.
Because the handler and the thread run somewhat independently, the handler may fill a buffer and call SignalRtInterruptThread before the thread finishes processing the previous buffer. To prevent the SignalRtInterruptThread request from becoming lost, the INtime kernel maintains a count of pending interrupt requests. Each time the handler calls SignalRtInterruptThread , the count of pending interrupts is incremented by one. Each time the thread calls WaitForRtInterrupt, the count of pending interrupts decrements by one. You can use the SDM vt command to view an interrupt thread and the count of pending interrupts.
If the count of pending interrupts is still greater than 0 after the interrupt thread calls WaitForRtInterrupt, the thread does not wait for the next SignalRtInterruptThread to occur before resuming execution. Instead, it immediately starts processing the next full buffer. Neither the interrupt thread nor the interrupt handler has to wait for the other. The interrupt handler can continually respond to interrupts without having the thread disable the interrupt level. The interrupt thread can continually process full buffers of data without waiting for the handler to call SignalRtInterruptThread.
This illustrates the multiple buffering handler:
The following table describes the actions of the handler and the thread. The table is divided into three parts:
The byMaxInt
parameter of SetRtInterruptHandler is set to three. The table shows the actions of both the handler and the thread through time, and the change in value of the count.
Time | Interrupt handler | Interrupt thread | Count |
---|---|---|---|
Thread A calls SetRtInterruptHandler to set handler and thread for level, setting byMaxInt to 3. |
0 | ||
A calls WaitForRtInterrupt to wait for first request from handler. | 0 | ||
Intrpt | Handler processes interrupt, starts filling first buffer. | ||
Intrpt | Process interrupt. Buffer is full. Call SignalRtInterruptThread. | ||
A starts processing 1st full buffer. | 1 | ||
Intrpt | Process interrupt. Start filling next buffer. | ||
Intrpt | Process interrupt. Buffer is full. Call SignalRtInterruptThread. | 2 | |
Intrpt | Process interrupt.Start filling next buffer. | 2 | |
Intrpt | Process interrupt. Buffer is full. Call SignalRtInterruptThread. Count is 3. Interrupt level is disabled. | 3 | |
Call WaitForRtInterrupt. Start processing next buffer. | 2 | ||
Intrpt | Process interrupt. Buffer is full. Call SignalRtInterruptThread. | 3 | |
Call WaitForRtInterrupt. Start processing next full buffer. | 2 |
The interrupt thread, when it initially calls SetRtInterruptHandler, specifies the maximum number of pending interrupt requests in byMaxInt
. When the interrupt handler calls SignalRtInterruptThread, causing the number of pending interrupts to be incremented to the maximum:
byMaxInt
in SetRtInterruptHandler. The interrupt level is enabled, allowing the handler to resume accepting interrupts. Always set byMaxInt
equal to the number of buffers that the thread and handler use. If the thread sets byMaxInt
larger than the number of buffers, the handler will accept interrupts when no buffers are available and data will be lost. If the thread sets byMaxInt
smaller than the number of buffers, there will always be empty buffers and space will be wasted.
For example, if you need one buffer, set byMaxInt
to 1. In this case, the INtime kernel disables the interrupt level while the thread is processing the buffer. If you need two buffers, set byMaxInt
to 2. Then, the handler can fill one buffer while the thread is processing the other. Additional buffers require correspondingly higher limits. However, if the thread sets the limit to 0, the interrupt handler operates without an interrupt thread.