[Reading notes] Linux kernel design and implementation-interrupts and interrupt handling

As we all know, the speed of the processor and the speed of peripheral hardware devices are often not on the same order of magnitude. Therefore, if the kernel takes the processor to send a request to the hardware and then specifically waits for a response, it is obviously unsatisfactory.
Since the response of the hardware is so slow, the kernel should process other transactions during this period , wait for the hardware to actually complete the requested operation, and then go back and process it.

Q: How to do it?
A: Polling may be a solution. However, this method is likely to cause the kernel to do a lot of useless work (polling will always be repeated periodically).
Therefore, it is necessary to provide a mechanism for the hardware to send a signal to the kernel when it is needed (change the kernel initiative to the hardware initiative)-the interrupt mechanism.

1. Interrupt

The interrupt allows the hardware to issue a notification to the processor, causing the kernel's attention.
An interrupt is essentially a special electrical signal sent from the hardware device to the processor.
Interrupts can be generated at any time, so the interrupt handler can be executed at any time.
Different devices have different interrupts, and each interrupt has a unique number.
These unique digital flags are called interrupt values ​​(interrupt request IRQ line)

Exceptions (also known as synchronous interrupts):
In the operating system, when discussing interrupts, you must mention exceptions.
An exception is different from an interrupt. It must be synchronized with the processor clock when it is generated. They work similarly, the only difference is that the interrupt is caused by hardware rather than software.

ps: The system call is an exception (the system call handler is abnormal), and the system call is implemented through a soft interrupt.

2. Interrupt handler

In response to a specific interrupt, the kernel will execute a function-interrupt handler (interrupt handler) or interrupt service routine (interrupt service routine, ISR).

A device's interrupt handler is part of its device driver-the device driver is the kernel code used to manage the device.

Q: What is the difference between an interrupt handler and other kernel functions?
A: The interrupt handler is an ordinary C function, but it must be declared according to a specific type, so that the kernel can pass the information of the handler in a standard way. The real difference is that interrupt handlers are called by the kernel to respond to interrupts, and they run in a special context called an interrupt context (occasionally also called an atomic context – the execution code of the context cannot be blocked).

ps: Interrupt handlers are usually not associated with specific devices, but with specific interrupts, that is, if a device can generate multiple different interrupts, then the device can correspond to multiple interrupt handlers. Correspondingly, the device driver also needs to prepare multiple such functions.

3. Comparison of the upper half and the lower half

I also want the interrupt handler to run fast, and I want the interrupt handler to do a lot of work. These two goals are obviously in conflict. In view of the contradictory relationship between the two purposes, the interrupt processing is generally cut into two parts or halves .

The interrupt handler is the top half (top half)-when an interrupt is received, it immediately starts execution and is completed with all interrupts disabled (strict time limit).
Be allowed later to complete the work will be postponed until the bottom half (bottom half), and then when it's time to break open the bottom half is executed (Linux provides various mechanisms to achieve lower half).

4. Register interrupt handler-request_irq

The interrupt handler is an integral part of the driver that manages the hardware. Each device has an associated driver. If the device uses interrupts, the corresponding driver registers an interrupt handler.

The driver can register an interrupt handler (declared in <linux / interrupt.h>) through the request_irq () function and activate the given interrupt line to handle the interrupt:

/* request_irq:分配一条给定的中断线 */
int request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev);

irq: interrupt number to be assigned. (For some devices, the value is usually predetermined, such as the system clock or keyboard on the PC device. For other devices, it can usually be obtained by detection, or dynamically determined by programming)
handler: is a pointer to the actual interrupt handling this interrupt Handler (callback). This function is called as soon as the operating system receives an interrupt.

typedef irqreturn_t (*irq_handler_t)(int, void *);

flags: It can be 0 or a bit mask of one or more flags, defined in <linux / interrupt.h>, the more important ones are IRQF_DISABLED, IRQF_SAMPLE_RANDOM, IRQF_TIMER, IRQF_SHARED, etc.
name: is the ASCII text representation of the device associated with the interrupt. These names will be used in the / proc / irq and / proc / interrupts files.
dev: Used to share interrupt lines. When an interrupt handler needs to be released, dev will provide unique flag information (cookie) to delete the specified one from the many interrupt handlers that share the interrupt line. Without this parameter, the kernel cannot know which handler to delete on the given interrupt line. If there is no need to share the interrupt line, then this parameter can be assigned a null value (NULL). However, if the interrupt line is shared, then you must pass unique information. In addition, every time the kernel calls the interrupt handler, it will pass this pointer to it.
ps: Interrupt handlers are callback functions registered in the kernel in advance, and different functions are located in different drivers, so when these functions share the same interrupt line, the kernel must accurately create an execution environment for them . You can use this pointer to pass useful environmental information to them.

The successful execution of request_irq () will return 0. If a non-zero value is returned, it indicates that an error has occurred. You can determine the cause of the error based on the error code.

ps:

  1. The request_irq () function may sleep, so it cannot be called in an interrupt context or other code that does not allow blocking.
  2. The sequence of initializing the hardware and registering the interrupt handler must be correct to prevent the interrupt handler from starting execution before the device initialization is completed.

Q: Why might the request_irq () function sleep?
A: During the registration process, the kernel needs to create an entry corresponding to the interrupt in the / proc / irq file. The function proc_mkdir () is used to create this new procfs item. proc_mkdir () sets the new profs item by calling the function proc_create (), and proc_create () calls the function kmalloc () to request memory allocation-the function kmalloc () can sleep

Q: How to release the interrupt handler?
A: When
uninstalling the driver, you need to cancel the corresponding interrupt handler and release the interrupt line . The following methods can be called:

void free_irq(unsigned int irq, void *dev);

ps: If the specified interrupt line is not shared, then this function will disable the interrupt line while deleting the handler.
If the interrupt line is shared, only the handler corresponding to dev is deleted, then the interrupt line itself will only be disabled after the last handler is deleted.

Therefore, for the shared interrupt line, the only dev is very important.

For the shared interrupt line, a unique information is needed to distinguish multiple handlers above it, and let free_irq () only delete the specified handler.

Regardless of whether the interrupt line is shared or not, if dev is not empty, it must match the handler to be deleted, and free_irq () must be called from the process context.
The interrupt registration method table is as follows:

function description
request_irq() Register a given interrupt handler on a given interrupt line
free_irq() If there is no interrupt handler on the given interrupt line, then cancel the corresponding handler and disable its interrupt line

5. Write interrupt handler

The declaration of the interrupt handler is as follows:

static irqreturn_t intr_handler(int irq, void *dev);

The type of this function matches the parameter type required by the handler in the request_irq () parameter.
The first parameter, irq, causes the interrupt number of the interrupt to be handled by this handler.
ps: The irq parameter is more important when there is no dev parameter. In versions after 2.0, the irq parameter is not very useful. Generally, the log information is printed with the irq number.
The second parameter dev is a general pointer, which must be consistent with the parameter dev passed to request_irq () when the interrupt handler is registered. If the value is uniquely deterministic (in order to support sharing), then it is equivalent to a cookie that can be used to distinguish multiple devices that share the same interrupt handler.

ps: For each device, the device structure is unique and may be used in the interrupt handler.

The return value is a special type: irqreturn_t.
ps: Interrupt handlers are usually marked as static, it will never be directly called by code in another file.
The interrupt handler 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 processing function, I return IRQ_NONE;
when the interrupt handler is correctly called, and it is indeed the device that locked it When interrupted, return to IRQ_HANDLED.

ps:
You can use the macro IRQ_RETVLA (val) to return the return value.
That is, if val is not non-zero, IRQ_HANDLED is returned; otherwise, IRQ_NONE is returned.

Reentry and interrupt handlers: Interrupt handlers in
Linux do not need to be reentrant.
When a given interrupt handler is being executed, the corresponding interrupt line will be shielded on all processors to prevent receiving another new interrupt on the same interrupt.
Normally, all other interrupts are turned on, so other interrupts on these different interrupt lines can be processed, when the current interrupt line is always disabled.

5.1 Shared interrupt handler

Whether the interrupt handler is shared is similar in registration and operation, but the differences are as follows, and all drivers that share interrupt lines must also meet the following requirements:

  1. The parameter flags of request_irq () must set the IRQF_SHARED flag ;
  2. For each registered interrupt handler, the dev parameter must be unique . A pointer to any device structure can meet this requirement: the device structure is usually selected because it is unique and the interrupt handler may be used. NULL values ​​cannot be passed to shared handlers;
  3. The interrupt handler must be able to distinguish whether its device has actually generated an interrupt , which requires that the interrupt handler must know whether it has issued an interrupt with the device it is facing, or whether another device sharing this interrupt line has issued this interrupt. It requires both hardware support and related logic processing in the processing program.

When the IRQF_SHARED flag is specified to call request_irq (), it can succeed only in the following two cases: the interrupt line is not currently registered, or IRQF_SHARED is specified for all registered handlers on the line.

ps: After the
kernel receives an interrupt, it will call each handler registered on the interrupt line in turn. Therefore, a handler must know whether it should be responsible for this interrupt. If the device associated with it does not generate an interrupt, then the handler should exit immediately. This requires the hardware device to provide a status register (or similar mechanism) for the interrupt handler to detect.

5.2 Example of interrupt handler-rtc driver

After reading this example-the example is in the interrupt function of the rtc driver code, or chapter 7.5.2 of the book, there are the following questions:
shared interrupt line does not share the interrupt handler, shared interrupt handler does not share the interrupt line. Can this be understood?
Hope that the comments in the comment area of ​​the big brother.

6. Interrupt context

When executing an interrupt handler, the kernel is in an interrupt context.
The process context is an operation mode in which the kernel is located, and the kernel executes on behalf of the process at this time. In the process context, you can associate the current process (java's Context class) with the current macro.
Because the process is connected to the kernel in the form of a process context connected to the context, the process context can sleep or the scheduler can be called (interrupt context can not sleep, please refer to this blog post why interrupt cannot sleep ).

Because the interrupt handler interrupts other code (and may even interrupt another interrupt handler on other interrupt lines), all interrupt handlers must be as quick and brief as possible. Try to separate the work from the interrupt handler and put it in the lower half to execute, because the lower half can run at a more appropriate time.

In kernel versions after 2.6, the kernel stack and the interrupt stack are independent.
Q: What is an interrupt stack?
A: In order to deal with the reduction in stack size, the interrupt handler has its own stack (previously shared kernel stack), one for each processor, and the size is one page. This stack is called the interrupt stack .

7. Implementation of interrupt handling mechanism

The implementation of the interrupt processing system in Linux is very dependent on the architecture, because the implementation depends on the processor, the type of interrupt controller used, the design of the architecture, and the machine itself.

The following figure shows the routing of interrupts from the hardware to the core: the
Insert picture description here
device generates an interrupt and sends an electrical signal to the interrupt controller via the bus. If the interrupt lines are active ( they are allowed to be masked ), then the interrupt controller will send the interrupt to the processor.
In most architectures, this job is to send a signal to a specific pin of the processor through an electrical signal. Unless the interrupt is disabled on the processor, the processor will immediately stop what it is doing, shut down the interrupt system, and then jump to a predefined location in memory to start executing the code there. This predefined location is set by the kernel and is the entry point of the interrupt handler.

Q: The hardware interrupt begins when the hardware generates an interrupt to the interrupt line to the processor, so what is the interrupt and processing of the processor (core)?
A: In the kernel, the interrupted journey begins at a predefined entry point, similar to a system call entering the kernel through a predefined exception handle. For each interrupt line, the processor will jump to a unique position. In this way, the kernel can know the IRQ number of the received interrupt.
The initial entry point is simply to save this number on the stack and store the current register value (these values ​​belong to the interrupted task); then, the engagement begins to call the function do_IRQ ().
From here, most of the interrupt handling code is written in C-but they are still related to the architecture.
The do_IRQ () statement is as follows:

unsigned int do_IRQ(struct pt_regs regs);

The calling convention of C is to put the function parameters on the top of the stack . Therefore, the pt_regs structure contains the values ​​of the original registers, which were previously saved on the stack in the assembly entry routine. The interrupted value is also saved, and do_IRQ () can extract it.

After getting the interrupt number, do_IRQ () responds to the received interrupt and prohibits the transfer of interrupts on this line. Then, do_IRQ () needs to ensure that there is a valid handler (interrupt handler) on this interrupt line, and this procedure has been started, but it is not currently executed. If so, do_IRQ () calls handle_IRQ_event () to run the interrupt handler installed for this interrupt line. The handle_IRQ_event () method is defined in the file kernel / irq / handler.c.
From handle_IRQ_event () back to do_IRQ (), the function does the cleaning work and returns to the initial entry point, and then jumps from this entry point to the function ret_from_intr ().
The ret_from_intr () method is similar to the initial entry code and is written in assembly language. This method checks whether rescheduling is suspending (doing something about process recovery or scheduling after coming out of the interrupt context)

ps: handle_IRQ_event The specific implementation and processing of this method can refer to chapter 7.7 in the book.

8./proc/interrupts

procfs is a virtual file system, which only exists with kernel memory and is generally installed in the / proc directory.
In reading and writing files in procfs, you must call kernel functions. These functions simulate reading and writing from real files.

The / proc / interrupts file stores statistics related to interrupts in the system. The following figure is the output information on the multi-processor (SMP): the
Insert picture description here
first column is the interrupt line (interrupt number) and the
second column is a counter of CPU0 (processor 1) receiving the number of interrupts.
The penultimate column is the interrupt controller that handles this interrupt.
The last column is the device name associated with this interrupt. This name is provided to the function request_irq () through the parameter devname.
If the interrupt is shared, all devices registered on this interrupt line will be listed.

ps:
procfs code is located in fs / proc. The function that provides / proc / interrupts is architecture related and is called show_interrupts ().

9. Interrupt control

The Linux kernel provides a set of interfaces for operating the interrupt status on the machine. These interfaces provide the ability to disable the interrupt system of the current processor, or shield an interrupt line of the entire machine. These routines are related to the architecture and can be found in <asm / system.h> and <asm / Found in irq.h>.

In general, the reason for controlling the interrupt system is ultimately the need to provide synchronization . By disabling interrupts, you can ensure that an interrupt handler does not preempt the current code. In addition, disabling interrupts also prohibits kernel preemption.

ps:
Disable interrupts to provide a protection mechanism to prevent concurrent access from other interrupt handlers, but not to prevent concurrent access from other processors. Therefore, some kind of lock needs to be acquired, and the lock provides a protection mechanism to protect the concurrency of multiple processors.

9.1 Disabling and activating interrupts

Used to disable the local interrupt on the current processor , and the statement that is subsequently activated is:

local_irq_disable();
local_irq_enable();

These two functions are usually implemented as a single assembly instruction (depending on the architecture).

ps: It
is safer to save the state of the interrupted system before interrupts are disabled. In contrast, when preparing to activate interrupts, simply restore the interrupts to their original state.
eg:

unsigned long flags;

local irq_save(flags);	/* 禁止中断 */
/* ...... */
local_irq_restore(flags);	/* 中断被恢复到它们原来的状态 */

The calls to local_irq_save () and local_irq_restore () must be made in one function.
why?
A: These methods are at least partially implemented in the form of macros, so on the surface flags parameters (these parameters must be defined as unsigned long type) are passed by value . This parameter contains the data of the specific architecture, that is, the state of the interrupted system. At least one architecture combines stack information with values ​​(SPARC), so flags cannot be passed to another function (especially it must reside in the same stack frame) .

9.2 Prohibition of designated interrupt line

Linux provides four interfaces for shielding an interrupt line .

void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
void enable_irq(unsigned int irq);
void synchronize_irq(unsigned int irq);

The first two functions prohibit the interrupt line specified on the interrupt controller, that is, the transfer of a given interrupt to all processors in the system.
The function can only return disable_irq () after all the currently executing handlers are completed. Therefore, the caller must not only ensure that no new interrupts are passed on the specified line, but also ensure that all processing programs that have already started execution have been exited.
The function disable_irq_nosync () does not wait for the execution of the current interrupt handler.

The function synchronize_riq () waits for the exit of a specific interrupt handler. If the handler is being executed, the function must exit before returning.

ps:
1. The above functions can be nested and called;
2. The calls of disable_irq and disable_irq_nosync must be paired with enable to ensure the real activation of the midline.
3. It is inappropriate to prohibit interrupt lines shared by multiple interrupt handlers. Prohibiting the interrupt line prohibits the interrupt transfer of all devices on this line.

9.3 Interrupt system status

Q: How do I know the status of the interrupt system, or is it in the execution state of the interrupt context?
A: The macro irqs_disable () is defined in <asm / system.h>. If the interrupt system is disabled on the local processor, it returns non-zero, otherwise it returns zero.
The two macros defined in <linux / hardirq.h> provide an interface for checking the current context of the kernel, as follows:

in_interrupt()
in_irq()

in_interrupt macro: If the kernel is in any type of interrupt processing, it returns 0. It means that the kernel is currently executing the interrupt handler, or the second half of the handler.
The in_irq macro only returns non-zero when the kernel is actually executing an interrupt handler.

The list of interrupt control methods is as follows:

function Explanation
local_irq_disable() Disable local interrupt delivery
local_irq_enable() Activate local interrupt delivery
local_irq_save() Save the current state of local interrupt delivery, and then disable local interrupt delivery
locar_irq_restore() Resume local interrupt delivery to a given state
disable_irq() Disable the given interrupt line and ensure that no handler is running on the interrupt line before the function returns
disable_irq_nosync() Disable the given interrupt line
enable_irq() Activate the given interrupt line
irqs_disabled() If the local interrupt transfer is disabled, it returns non-zero; otherwise it returns 0
in_interrupt() If in the interrupt context, it returns 0; if in the process context, it returns 0
in_irq() If the interrupt handler is currently being executed, it returns non-zero, otherwise it returns 0
Published 91 original articles · praised 17 · 50,000+ views

Guess you like

Origin blog.csdn.net/qq_23327993/article/details/105434469