I have talked about the interruptions many times. Today I want to talk about the interruptions. Why? In the operating system, interruptions must be mentioned ..
So what is interrupt? interrupt or interrupt. You don't understand it. Alas, interruption is a bit like interruption. We know that Linux manages all the hardware devices. The first thing we need to do is communication. Then, we say a word every day: the processor speed and the speed of peripheral hardware devices are usually not an order of magnitude or even several orders of magnitude different, you can't let the processor wait until your hardware is ready. Let me know. It is easy to relate to daily life. This is too inefficient. It is better to do other things with my processor. If your hardware is ready, just let me know. This tells us that it's easy to say, and it's hard to do it! What's more, simply put, round training (polling) may be a solution. The disadvantage is that the operating system has to do too much useless work, where silly jobs do less important and need to be repeated, there is a better way here-interruption. It doesn't matter. The key is that from the perspective of hardware devices, we have achieved a historic breakthrough from passive to active.
I will not talk about the interrupted example. This is obviously. An analysis interrupt is essentially a special electrical signal sent to the processor by a hardware device. After the processor receives the interrupt, it immediately returns the signal to the operating system, then, the OS is responsible for processing the new data. interruption can occur at any time, so you don't have to worry about time synchronization with the processor. Different devices have different interruptions. From the operating system level, the difference lies in a digital Identifier-the interrupt number. Specifically, it is called the IRQ line. Generally, IRQ is a numerical value. In some architectures, interruptions are fixed, and some are distributed dynamically. This is not a problem. The problem is that a specific interruption is always associated with a specific device, and the kernel needs to know this information, which is the most critical, isn't it? Haha.
One sentence in the book: We have to mention exceptions when talking about interruptions. Exceptions are different from interruptions. They must be synchronized with the CPU clock when being generated. In fact, exceptions are often called synchronization interruptions, when the processor executes wrong commands due to programming errors, or when special circumstances occur during execution, it must rely on the kernel for processing, the processor will generate an exception. Because many Processor Architecture processes exceptions and Interrupts in a similar way, the kernel processes them similarly. Most of the discussions here are suitable for exceptions, which can be seen as the interruption of the processor itself.
Interrupt generation tells the interrupt controller, and continues to tell the operating system kernel that the kernel always needs to be processed, right? Here, the kernel will execute a command called interrupt processing.ProgramOr a function that interrupts the processing routine. In particular, the interrupt handler is associated with a specific medium disconnection, rather than a device. If a device can cause many interruptions, at this time, the driver of the device also needs to prepare multiple such functions. An interrupt handler is part of the device driver. As we have already said in the Linux Device Driver, I will mention it later. The front mentioned a problem: interruptions may occur at any time. Therefore, it is necessary to ensure that the interrupt processing program can be executed at any time, and the interrupt processing program should be executed as quickly as possible, only in this way can the interruption be restored as quickly as possible.Code.
However, I don't want to say it, but the first day of university skipping course is still fresh in my memory: I want to run the horse and not eat grass. How can this happen! But the real problems may not be as pessimistic as we think, and our interruptions may be a miracle. The miracle is to cut the interrupt processing into two or two parts. The upper half of the interrupt handler (top half) --- receives an interrupt and starts execution immediately, but only works within a strict time limit, these tasks are completed when all interruptions are disabled. At the same time, the work that can be completed later can be postponed to the lower half (bottom half). After that, the lower half will be executed. Generally, the lower half will be executed immediately when the interrupt handler returns. I will talk about the various mechanisms provided by Linux to implement the lower half.
Now the first question is how to register an interrupt handler. In the Linux driver theory, we can use the following functions to register an interrupt handler:
Int request_irq (unsigned int IRQ, irqreturn_t (* Handler) (INT, void *, struct pt_regs *), unsigned long irqflags, const char * devname, void * dev_id)
I will not talk about some parameter descriptions about this interrupt. Once an interrupt handler is registered, it will certainly release the interrupt processing. This is to call the following functions:
Void free_irq (unsigned int IRQ, void * dev_id)
It must be noted that free_irq () must be called from the process context ().Now, let's give an example to illustrate this process. First, declare an interrupt handler:
Static irqreturn_t intr_handler (int irq, void * dev_id, struct pt_regs * regs)
Note: The type here matches the parameter type required by request_irq () mentioned in the front. For the return value, the return value of the interrupt handler is a special type. irqrequest_t may return two special values: irq_none and irq_handled. when the interrupt handler detects an interrupt, but the device corresponding to the interrupt is not the source specified during the registration of the handler, irq_none is returned. When the interrupt handler is called correctly, in addition, the macro irq_retval (x) can be used to return irq_handled.c when the corresponding device is interrupted. If X is not 0, the macro returns irq_handled. Otherwise, irq_none. with this special value, the kernel can know whether the device is sending a false (unrequested) interrupt. If irq_none is returned for all interrupt handlers on the given interrupt line, the kernel can detect a problem. Finally, it should be noted that it is static. The interrupt handler is usually marked as static because it is never directly called by code in other files. In addition, the interrupt handler does not need to be reentrant. When a given interrupt handler is being executed, the corresponding Disconnection will be blocked on all processors, to prevent receiving another interruption on the same interrupt. Generally, all other interruptions are opened, so other interruptions on different interruptions can be handled, but the current interruption is always disabled. It can be seen that the same interrupt handler will never be called at the same time to handle nested interruptions.
The following is a problem related to the shared interrupt handler. Sharing and non-sharing are similar in the registration and running modes. The main differences are as follows:
1. The flags parameter of request_irq () must be set to the sa_shirq flag. 2. For each registered interrupt handling, the dev_id parameter must be unique. The pointer pointing to any device structure can meet this requirement. The device structure is usually selected because it is unique and The disconnected handler may use it and cannot pass a null value to the shared handler. 3. the interrupt handler must be able to identify whether the device actually has an interruption. This requires both hardware support and related processing logic of the processing program. If the hardware does not support this function The disconnection handler will be helpless, and it cannot know whether there is an interruption with its corresponding device or whether other devices that share the disconnection have been interrupted. |
When you specify the sa_shirq flag to call request_irq (), the operation is successful only in the following two cases: the operation is interrupted and sa_shirq.a is specified for all registered handlers on the line. Note that 2.6 is different from the previous kernel at this point. Shared processors can be mixed with sa_interrupt. once the kernel receives an interrupt, it calls each handler registered online for the interrupt in sequence. Therefore, a handler must know whether it should be responsible for this interrupt. If the device associated with the device does not interrupt, the interrupt handler should exit immediately, which requires the hardware device to provide status registers (or similar mechanisms) for the interrupt handler to check. There is no doubt that most devices provide this feature.
When an interrupt handler or lower half is executed, the kernel is in the interrupt context. Compared with the process context, the process context is an operation mode in which the kernel is located. At this time, the kernel represents the process execution and can be associated with the current process through the current macro. In addition, because a process is connected to the kernel in the form of a process context, the process context can sleep at any time or schedule programs. But the interrupt context is completely different. It can sleep, because we cannot call a function from the interrupt context. If a function is sleep, it cannot be used in the interrupt handler, which is also a limitation on what functions can be used in the interrupt handler. It should also be noted that the interrupt handler does not have its own stack. On the contrary, it shares the kernel stack of the interrupted process. If there is no running process, it uses the stack of the idle process. Because interrupt programs share others' stacks, they must save a lot of effort in obtaining space from stacks. The kernel stack is 8 kb in the 32-bit architecture and 16 kb in the 64-bit architecture. The context of the executed process and all the interruptions generated share the kernel stack.
The following describes the process of interrupting the routing from hardware to kernel (selected from the liuux Kernel Analysis and Design p61), and then summarizes the process:
Figure 1 route interruption from hardware to Kernel
The internal description of the above figure is clear and I will not discuss it in detail here. In the kernel, the interrupted journey begins with a predefined entry point, which is similar to a system call. For each disconnection, the processor jumps to a unique position. In this way, the kernel can know the IRQ Number of the received interrupt. The initial entry point only saves this number in the stack and stores the value of the current Register (these values are interrupted tasks). Then, the kernel calls the function do_irq (). from here on, most of the interrupt processing code is written in C. The do_irq () statement is as follows:
Unsigned int do_irq (struct pt_regs regs)
Because C's call convention is to place function parameters on the top of the stack, the pt_regs structure contains the values of the original register, which were previously stored on the stack in the Assembly entry routine. The interrupted value is also saved. Therefore, do_irq () can extract it. The x86 code is:
Int IRQ = regs. orig_eax & 0xff
after the interrupt number is calculated, do_irq () responds to the received interrupt, disable the interruption transmission of this line. On normal PC machines, these operations are completed by mask_and_ack_8259a (), which is called by do_irq. Next, do_irq () needs to ensure that there is a valid processing program on the interrupt line, and this program has been started but is not currently executed. In this case, do_irq () calls handle_irq_event () to run the interrupt processing program installed in the disconnection center. for processing examples, refer to the Linux kernel design analysis book. I will not elaborate on it here. In handle_irq_event (), the first step is to enable the processor interrupt, because we have already said that all interrupts on the processor are prohibited (because we have said that sa_interrupt is specified ). Next, each potential processing program is executed in sequence in the loop. If this line is not shared, the loop will exit after the first execution. Otherwise, all the processing programs will be executed. If the sa_sample_random flag is specified during registration, the add_interrupt_randomness () function is also called. This function uses the interrupt interval to generate entropy for random numbers. Finally, the interruption is disabled (do_irq () is always expected to be disabled), and the function returns. This function cleans up and returns to the initial entry point, and then jumps from this entry point to the function ret_from_intr (). this function is similar to the initial entry code and compiled in assembler. It checks whether rescheduling is pending. If rescheduling is pending, and the kernel is returning the user space (that is, if the user process is interrupted, schedule () is called. If the kernel is returning the kernel space (that is, the kernel itself is interrupted), Schedule () will be called only when preempt_count is 0 (otherwise, it is not safe to seize the kernel ). Before schedule () returns, or if there is no pending operation, the original register is restored and the kernel is restored to the previously interrupted point. On x86, the initialization Assembly routine is located at ARCH/i386/kernel/entry. S, and the C method is located at ARCH/i386/kernel/IRQ. C. Other supported structures are similar.
The output result of the/proc/interrupts file on the PC is shown below. This file stores the interrupt-related statistics in the system. Here we will explain this table:
The above is the input of this file. The first column is the disconnection (Interrupt number), the second column is a counter that receives the number of interruptions, and the third column is the interrupt controller that handles the interruption, the last column is the device name related to the interrupt, which is provided to the request_irq () function through the devname parameter. Finally, if the interruption is shared, all the devices that interrupt online registration will be listed, for example, 4.
The Linux Kernel provides us with a set of interfaces that allow us to control the interrupt status on the machine. These interfaces can be found in <ASM/system. h> and <ASM/IRQ. h>. In general, the reason for controlling the interrupt system is that synchronization is required. By disabling interruption, an interrupt handler can ensure that the current code is not preemptible. In addition, you can also disable kernel preemption by disabling interruption. However, no protection mechanism is provided to prevent concurrent access from other processors, whether it is to prohibit interruption or kernel preemption. Linux supports multi-processor. Therefore, kernel code generally needs to obtain a lock to prevent concurrent access to shared data from other processors. Obtaining these locks is also accompanied by disabling local interruption. The lock provides a protection mechanism to prevent concurrent access from other processors, while the interruption protection mechanism to prevent concurrent access from other interrupt handlers.
I have introduced the interrupt operation interface of Linux in detail in the theoretical post on Linux device drivers. Here I will discuss about how to disable/enable local interruptions (only the current processor:
Local_irq_disable (); local_irq_enable ();
If the interrupt is disabled before local_irq_disable () is called, this function may lead to potential risks. The same local_irq_enable () may also cause potential risks, it causes unconditional activation interruption, although the interruption may be disabled at the beginning. Therefore, we need a mechanism to restore the interrupt to the previous state rather than simply disabling or activating it. The kernel is generally concerned about this, the reason is that a given code path in the kernel can be reached in case of interruption activation or ELE. Me, depending on the call chain. In this case, it is safer to save the state of the interrupted system before the interruption is disabled. Instead, you only need to restore the interrupt to its original state when preparing to activate the interrupt:
Unsigned long flags; local_irq_save (flags); local_irq_restore (flags );
The parameter contains the data of the specific architecture, that is, the status of the interrupted system. There must be at least one architecture that combines stack information with values (iSCSI), so flags cannot be passed to another function (in other words, it must reside in the same stack frame ), for this reason, the call to local_irq_save () and local_irq_restore () must be performed in the same function. All the preceding functions can be called either in the interrupt or in the process context.
Previously, I mentioned the function to disable all interruptions on the entire CPU. But sometimes, I want to be curious, but I don't want to disable all the interruptions. Sometimes, I only need to disable a specific interruption in the system (Block A single disconnection ), the following interface is provided:
Void disable_irq (unsigned int IRQ); void disable_irq_nosync (unsigned int IRQ); void enable_irq (unsigned int IRQ); void synchronise_irq (unsigned int IRQ );
I have already made it very clear about the description and attention of functions. In addition, it is inappropriate to prohibit multiple interrupt handlers from sharing the broken line. Disabling a moderate disconnection prevents the interruption of all devices on this line. Therefore, drivers for new devices should prefer not to use these interfaces. In addition, we can also define Macros in <ASM/system. h> by using macrosIrqs_disable () to obtain the interrupt status. If the interrupt system is disabled, it returns non-0; otherwise, it returns 0. It is defined in <ASM/hardirq. h> the two macros in_interrupt () and in_irq () in to check the current context interface of the kernel. Because the code sometimes needs to do something like sleep that can only be done from the context of the process, the value of these two functions is reflected.
Finally, as a summary of this blog, we will provide a list of methods I mentioned earlier to control interruptions: