Linux source reading notes - hardware interrupt

xiaoxiao2021-03-05  31

Linux hard interruption

The Linux interrupts and other operating systems are interrupted, requiring hardware and software support. The advantage of Linux is to see the core processing interrupt, and the following is a detailed analysis of the Linux interrupt mechanism.

First, interrupt classifications that can be handled in Linux:

1. Interrupts generated by physical hardware devices, these devices are connected to the I8259A interrupt controller on the motherboard, and the specific connection can be found to see the "computer composition principle". 16 interrupt numbers can be handled in Linux, but this does not mean that Linux can only handle 16 peripheral interrupt requests. In fact, many peripherals can share interrupt numbers, which requires software support of the operating system, can be seen later To Linux is if it is processed.

2. Abnormal, an abnormality is unpredictable. If it is 0 divided, missing,

Set_trap_gate (0, & Divide_ERROR);

Set_trap_gate (1, & debug);

Set_intr_gate (2, & nmi);

Set_system_gate (3, & INT3); / * INT3-5 Can Be Called from ALL * /

Set_system_gate (4, & overflow);

Set_system_gate (5, & bounds);

SET_TRAP_GATE (6, & invalid_op);

SET_TRAP_GATE (7, & device_not_available);

Set_trap_gate (8, & double_fault);

SET_TRAP_GATE (9, & Coprocessor_SEGMENT_OVERRUN);

SET_TRAP_GATE (10, & invalid_tss);

Set_trap_gate (11, & segment_not_present);

SET_TRAP_GATE (12, & stack_segment);

Set_trap_gate (13, & general_protection);

Set_trap_gate (14, & Page_Fault);

SET_TRAP_GATE (15, & spurious_interrupt_bug);

SET_TRAP_GATE (16, & COPROCESSOR_ERROR);

Set_trap_gate (17, & alignment_check);

SET_TRAP_GATE (18, & Machine_Check);

Set_trap_gate (19, & simd_coprocessor_error);

Set_system_gate (syscall_vector, & system_call);

/ *

* DEFAULT LDT IS A SINGLE-Entry Callgate to LCall7 for IBCS

* And a callgate to lcall27 for Solaris / x86 binaries

* /

SET_CALL_GATE (& Default_LDT [0], LCALL7);

Set_call_gate (& Default_LDT [4], LCALL27);

From the I386 / KERNEL / TRAPS.C-> trap_init () function clip, the trap_init () function is also called the initialization of the operating system. The start_kernel () function call, which can see the exception processed by Linux from the list. From the comments above, you can see the abnormality 3-5, which is interrupt command INT3, INT4, and INT5 can be called by all process, which is actually used by the debugger, so it can certainly be called by your program. Other exceptions can only be called in the process of privileged grade 0, that is, these exceptions can only be handled by the kernel, the truth is very obvious, but it is not so straightforward. 3. Trap (Trap)

Trap is also called active exception, which is the int N directive directly in your program, and there is not much such instructions that can appear in your program unless your program is a module. .

4. System call INT80

The reason why the reason is proposed is to understand the system call, and the system call is the functional interface provided by the operating system to provide the user. Many people will have a mystery, which loaded into memory low-ends when the system is initialized, and is mapped into the inner core space (3G), and the address is the same for all call processes.

Many of the above classifications are not necessarily agreed, but it is worthless.

Linux interrupt processing

In the previous CPU before 80386, the interrupt is processed by the interrupt vector table IDT. This handles you can look at the relevant books. It is where the operating system puts the population address of the interrupt processing in memory 0, each interrupt vector accounts for 4 bytes, 2 for the segment base address, and 2 for offset. A total of 256 vector, just 1K. In 80386, the CPU allows the IDT to be stored in any address, so that the CPU has more register IDTR, where the IDT is stored in memory. In the 80386, a new term "door" is introduced in the protection mode. For this term, you can refer to Intel's information, which is understood as a check door that must be passed when the process authority changes. Intel sets 4 doors: Task door, interrupt door, trap door, call door. The three are basically almost the same. The task door does not seem to use, in the Linux annotation, in order to run IBCS and Solaris / X86 on Linux, set two task doors. The interrupt vector description of 80386 is also different from 8086, and the interrupt vector has 64-bit long, which gives the segment selector (16 bits) of the interrupt handler, the offset address of the program (32 bits), and the type code. Therefore, it is possible to describe the scene of the 80386 processing interrupt: (1) The CPU finds the corresponding interrupt item in the IDT according to the interrupt vector; (2) Find the segment in the GDT according to the segment selector in the interrupt vector. In combination with the offset, the linear address of the interrupt service is obtained, and this address is definitely 3G or more, that is, it is mapped to the core area.

80386 sets the door (interrupt vector) in the interrupt, which has strict requirements for the permissions of the process, specific:

1. Before entering the door, the process has a higher authority than the permissions of the door (this permission is in the type description of the interrupt vector),

2. After entering the door, the process has a higher authority than the runtime (GDT) of the segment where the interrupt service program is located.

3. The hardware interrupt can bypass the above process.

4. Other types of interruptions, exceptions, if not satisfied, will trigger an exception. This permission setting is completed when it is initialized, we look at the function trap_init (), there are four set functions, respectively,

Set_trap_gate

Set_system_gate

set_intr_gate

set_call_gate

Find their definitions in source traps.c

Void set_intr_gate (unsigned int N, void * addr)

{

_SET_GATE (IDT_TABLE N, 14, 0, ADDR);

}

Static void __init set_trap_gate (unsigned int N, void * addr)

{

_SET_GATE (IDT_TABLE N, 15, 0, ADDR);

}

Static void __init set_system_gate (unsigned int N, void * addr)

{

_SET_GATE (IDT_TABLE N, 15, 3, ADDR);

}

Static void __init set_call_gate (void * a, void * addr)

{

_SET_GATE (A, 12, 3, ADDR);

}

In fact, they are all completed by an internal function, _SET_GATE (), is different, here is the third parameter, this parameter is the permission of the door. If this parameter is equal to 3, it is to say that the user-state process can also be called. This gives the system_gate and call_gate user processes to call, INTR_GATE and TRAP_GATE only have a core state process. Here to add that INTR_GATE and TRAP_GATE system processing are basically consistent, the only difference is that INTR_GATE processing is turned off, and trap_gate is not interrupted when processing.

The processing of abnormal processing can be explained in detail herein. Linux has been unified for hardware interrupts. Here you have to look at the specific code. In the source file Arch / I386 / KERNEL / I8259.C, first look at the function init_irq, this function is the initialization function of the hardware interrupt, and is also called by START_kernel. Remove the code compiled by the SMP macro, simplify it, get the following code

Void __init init_irq (void)

{

........

INIT_ISA_IRQS ();

........

For (i = 0; i

INT vector = first_EXTERNAL_VECTOR I;

IF (Vector! = SYSCALL_VECTOR)

SET_INTR_GATE (Vector, Interrupt [I]);

}

...... ..

}

The core is two functions init_isa_irqs, set_intr_gate, and a global array variable interrupt. Macro first_external_vector = 20, that is, the hardware interrupt vector> = 20, here illustrates the difference between the interrupt number and the interrupt vector, the interrupt number is actually the hardware connection I8259A is the line number, physical; interrupt vector is logic, representative interrupt Service subscript in IDT. The macro syscall_vector is 0x80, which is reserved for the system.

See the definition of init_isa_irqs first

Void __init init_isa_irqs (void)

{

INT I;

INIT_8259A (0);

For (i = 0; i

Irq_desc [i] .status = IRQ_DISABED; IRQ_DESC [I] .Artion = 0;

Irq_desc [i] .depth = 1;

IF (i <16) {

/ *

* 16 Old-Style INTA-CYCLE Interrupts:

* /

Irq_desc [i] .handler = & i8259A_IRQ_TYPE;

} else {

/ *

* 'High' PCI IRQS Filled in Demand

* /

Irq_desc [i] .handler = & no_irq_type;

}

}

}

The first thing to explain is a global IRQ_DESC array.

/ *

* This is the "Irq Descriptor", Which Contains Various Information

* About The Irq, Including What Kind of Hardware Handling It Has,

* WHETHER IT IS DISABED ETC ETC.

*

* Pad this out to 32 bytes for cache and indexing.

* /

Typedef struct {

Unsigned int stats; / * irq status * /

HW_IRQ_CONTROLLER * HANDLER;

Struct Irqaction * action; / * irq action list * /

Unsigned int Depth; / * Nested IRQ Disables * /

Spinlock_t lock;

} ____cacheline_aligned Irq_Desc_t;

EXTERN IRQ_DESC_T IRQ_DESC [NR_IRQS];

As seen from the above statement, IRQ_DESC is "IRQ Descriptor". I don't understand that the type Struct Irqaction and HW_IRQ_CONTROLLER in the structure are found to find out their statements:

Struct hw_interrupt_type {

Const char * typename;

Unsigned int (* start IRQ);

Void (* ShutDown) (UNSIGNED IRQ);

VOID (* enable) (UNSIGNED IRQ);

Void (* disable) (UNSIGNed INT IRQ);

Void (* ACK) (unsigned int IRQ);

Void (* end) (unsigned IRQ);

Void (* set_affinity) (unsigned Irq, unsigned long mask);

}

Typedef struct hw_interrupt_type hw_irq_controller;

Struct Irqaction {

Void (* Handler) (int, void *, struct pt_regs *);

Unsigned long flag;

UNSIGNED Long Mask;

Const char * name;

Void * dev_id;

Struct Irqaction * Next;

}

HW_IRQ_Controller is the control of the interrupt control chip, and its function can be seen from the data type. So when i <16, IRQ_DESC [I] .Handler = & I8259A_IRQ_TYPE; here I correspond to the interrupt number. Struct Irqaction is a list, before the hardware can share the interrupt number, so after the interrupt number is coming, it is looking for a handler in a linked list. This handler is IRQAction-> Handler, this list is IRQ_DESC [I] -> Irqaction. It can be seen that init_isa_irqs is to initialize the IRQ_DESC array, and the IRQ_DESC array records a list of servers of each interrupt number, which will also see it in the later system unified interrupt handler. The second function set_intr_gate has been seen in front, here for convenience and then paste it

Void set_intr_gate (unsigned int N, void * addr)

{

_SET_GATE (IDT_TABLE N, 14, 0, ADDR);

}

It is written in the IDT table interrupt vector, the key is the global array interrupt.

Find in the same document

Void (* interrupt [nr_irqs]) (void) = {

Irqlist_16 (0x0),

.......

}

Is a function pointer array, IrqList_16 (0x0) is a macro definition

#define IRQ (X, Y) /

IRQ ## x ## y ## _ interrupt

#define Irqlist_16 (x) /

IRQ (X, 0), IRQ (X, 1), IRQ (X, 2), IRQ (X, 3), /

IRQ (X, 4), IRQ (X, 5), IRQ (X, 6), IRQ (X, 7), /

IRQ (X, 8), IRQ (X, 9), IRQ (X, A), IRQ (X, B), /

IRQ (X, C), IRQ (X, D), IRQ (X, E), IRQ (X, F)

An IrqList_16 (x) defines 16 functions, named IRQ0X0N_Interrupt, such as the function of the No. 3 interrupt is Void IRQ0X03_Interrupt (void). The definition of this function is in the source file I8259a.c of Linux, it is concealed by a string of macro, look carefully

#define bi (x, y) /

Build_irq (x ## y)

#define build_16_irqs (x) /

Bi (x, 0) Bi (x, 1) Bi (x, 2) Bi (x, 3) /

BI (x, 4) Bi (x, 5) Bi (x, 6) Bi (x, 7) /

Bi (x, 8) Bi (x, 9) Bi (x, a) Bi (x, b) /

Bi (x, c) Bi (x, d) Bi (x, e) Bi (x, f)

/ *

* ISA PIC or Low IO-APIC TRIGGERED (INTA-CYCLE OR APIC) Interrupts:

* (THESE ARE USUALLY MAPPED to VECTORS 0X20-0X2F)

* /

Build_16_irqs (0x0),

The macro build_16_irqs defines 16 BIs, and each BI is expanded through the macro build_irq (x ## y).

Find the macro definition of Build_irq (X ## Y):

#define build_irq (nr) /

ASMLINKAGE VOID IRQ_NAME (NR); /

__ASM __ (/

"/ n" __ align_str "/ n" /

Symbol_name_str (IRQ) #nr "_interrupt: / n / t" /

"Pushl $" # nr "-256 / n / t" /

"JMP Common_Interrupt"); find the macro definition of IrQ_name

#define Irq_name2 (NR) NR ## _ Interrupt (Void)

#define IrQ_name (NR) IRQ_NAME2 (IRQ ## NR)

Finally, IRQ0X03_Interrupt definition is to

__ASM __ (/

"/ n" __ align_str "/ n" /

Symbol_name_str (IRQ) #nr "_interrupt: / n / t" /

"Pushl $" # nr "-256 / n / t" /

"JMP Common_INTERRUPT");

Translation as a C language is

{

IRQ # NR_INTERRUPT:

Pushl NR-256

JMP Common_INTERRUPT

}

This series of macro definitions are for short-write source code, otherwise write 16 compiled code. If someone has seen "in-depth shallow MFC", it should be more familiar with this, and there is a lot of macros in the definition in the MFC, and you also make you look back. These functions must jump to the code under the COMMON_INTERRUPT, then where is this? in

Inside a header file, this file is HW_IRQ.H, defined as follows:

#define build_common_irq () /

ASMLINKAGE VOID CALL_DO_IRQ (VOID); /

__ASM __ (/

"/ n" __align_str "/ n" /

"Common_Interrupt: / N / T" /

SAVE_ALL /

"Pushl $ RET_FROM_INTR / N / T" /

Symbol_name_str (call_do_irq) ": / n / t" /

"JMP" Symbol_name_str (do_irq));

Skip to a function call_do_irq, this function is replicated with C.

Void call_do_irq (void)

{

Common_interrupt:

Save_all

Pushl $ RET_FROM_INTR

CALL_DO_IRQ:

JMP DO_IRQ

}

Save_all is a macro,

#define save_all /

"CLD / N / T" /

"Pushl% ES / N / T" /

"Pushl% DS / N / T" /

"pushl% EAX / N / T" /

"pushl% EBP / N / T" /

"Pushl% EDI / N / T" /

"pushl% ESI / N / T" /

"Pushl% EDX / N / T" /

"Pushl% ECX / N / T" /

"pushl% EBX / N / T" /

"Movl $" Str (__ kernel_ds) ",% EDX / N / T" /

"MOVL% EDX,% DS / N / T" /

"MOVL% EDX,% ES / N / T"

As soon as I see, save the registers of the current process, saved in the system stack in the current process. RET_FROM_INTR estimation can guess, it is to process the function of the interrupt returned, but why should I enter the stack? This is related to the calling method of the Do_IRQ function, the Do_irq function is executed through JMP, that is, the command number after the function is not entered, so that the function returns to the next instruction, Pushl $ RET_FROM_INTR Solved this problem, the function DO_IRQ returns, the command RET puts the RET_FROM_INTR out of the stack. When it is loaded into the IP, it is executed to RET_FROM_INTR. This is a skill of assembler design. It is also unfortunate to take people. I have to catch crazy. Wrap the old half a day, understand the function DO_IRQ for handling hardware interrupt service. Look at this function, in Irq.c

Repair some error handling and SMP processing, simply look at it.

/ *

* DO_IRQ HANDLES All Normal Device Irq's (The Special

* SMP Cross-CPU Interrupts Have Their Own Specific

* Handlers).

* /

ASMLINKAGE UNSIGNED INT DO_IRQ (Struct Pt_Regs Regs)

{

INT IRQ = regs.orig_eax & 0xff; / * high bits used in return in RET_FROM_ CODE * /

INT CPU = SMP_PROCESSOR_ID ();

IRQ_DESC_T * DESC = IRQ_DESC IRQ;

Struct Irqaction * action;

Unsigned int stat;

KSTAT.IRQS [CPU] [IRQ] ;

Spin_lock (& ​​desc-> lock);

DESC-> Handler-> Ack (IRQ);

.......

Action = NULL;

IF (! (iRQ_DISABED | IRQ_INPROGRESS))) {

Action = desc-> action;

Status & = ~ Irq_pend; / * We commit to handling * /

Status | = IRQ_INPROGRESS; / * WE Are Handling It * /

}

DESC-> status = status;

For (;;) {

Spin_unlock (& ​​Desc-> Lock);

Handle_irq_event (IRQ, & Regs, Action);

Spin_lock (& ​​desc-> lock);

IF (! (desc-> status & id_pending))

Break;

DESC-> Status & = ~ Irq_pend;

}

.......

IF (Softirq_Active (CPU) & Softirq_Mask (CPU))

DO_SOFTIRQ ();

Return 1;

}

The key is in the function NDLE_IRQ_EVENT (IRQ, & Regs, Action),

INT HANDLE_IRQ_EVENT (unsigned int IRQ, Struct Pt_regs * regs, struct Irqaction * action)

{

Int status;

INT CPU = SMP_PROCESSOR_ID ();

Irq_enter (CPU, IRQ); status = 1; / * force the "do bottom halves" bit * /

IF (! (Action-> Flags & Sa_Interrupt))

__sti ();

Do {

Status | = Action-> Flags;

Action-> Handler (IRQ, Action-> dev_id, regs);

Action = action-> next;

WHILE (Action);

IF (status & sa_sample_random)

Add_INTERRUPT_RANDOMNESS (IRQ);

__cli ();

Irq_exit (CPU, IRQ);

Return status;

}

In the do-while loop of the ANDle_IRQ_EVENT function, I finally saw an action = action-> next, and the system is consistent with the original idea. See here, return to the front, understand the role of the Interrupt array.

Now review the entire process of Linux processing interrupts, the hardware generates an interrupt, the CPU gets the interrupt number, the system finds the interrupt vector corresponding to this interrupt number in the IDT, from the interrupt vector to get the function address of IRQ0X0N_Interupt,

Execute this function, jump to common_interrupt, go to call_do_irq, to ​​do_irq, last to Handle_IRQ_EVENT

From the process of processing throughout the interrupt, the above is to involve the initial, processing of the interrupt, and how the user sets the interrupt processing function, which is processed in the Request_IRQ function. Hardware interruptions should be written so much, as well as soft interrupts and system calls, and then say it after reading it.

转载请注明原文地址:https://www.9cbs.com/read-37195.html

New Post(0)