"Operating System Truth Restoration" Study Notes: Chapter 7 Interruption

  • Because the CPU knows something happened in the computer, the CPU suspends the program that is being executed and turns to execute the program that handles the event. When this program is executed, the CPU continues to execute the program just now. The whole process is called interrupt handling, also known as interrupt.

The interrupts are classified according to the source of the event, the interrupts from outside the CPU are called external interrupts, and the interrupts from inside the CPU are called internal interrupts.

  • External interrupts are classified according to whether they cause downtime, and can be divided into maskable interrupts and non-maskable interrupts.
  • Internal interrupts are divided according to whether the interrupt is normal, and can be divided into soft interrupts and exceptions.

external interruption

External interrupts can be divided into: maskable interrupts and non-maskable interrupts

  • External interrupts refer to interrupts from outside the CPU, and the external interrupt source must be a certain hardware, so external interrupts are also called hardware interrupts. The CPU provides two signal lines. External hardware interrupts are notified to the CPU through two signal lines. These two signal lines are INTR (INTeRrupt) and NMI (Non Maskable Interrupt)
    insert image description here
  1. Maskable interrupts : Maskable interrupts enter the CPU through the INTR pin, and interrupts from external devices such as hard disks and network cards are all maskable interrupts. Maskable means that the interrupts issued by this external device can be ignored by the CPU, because it will not cause the system to crash, so the interrupts of all these external devices can be masked through the IF bit of the eflags register. In addition, these devices are all connected to a certain interrupt proxy device, and the interrupt of a certain device can also be shielded separately through the interrupt proxy. For this type of maskable interrupt, the CPU can choose to ignore it, or even after it is ignored, it can divide the interrupt into the upper half and the lower half for separate processing like Linux.
  2. Non-maskable interrupt : The non-maskable interrupt enters the CPU through the NMI pin, which means that a fatal error has occurred in the system, which is equivalent to announcing that the operation of the computer is over. The IF bit in the eflags register has no effect on it

internal interrupt

Internal interrupts can be divided into: soft interrupts and exceptions

  1. Soft interrupt : A soft interrupt is an interrupt initiated by software. Since the interrupt is actively initiated during software operation, it is subjective, not an objective internal error

The following instructions can initiate an interrupt:

  • "int 8-bit immediate". This is a commonly used instruction in the future. We need to use it to make system calls. The 8-bit immediate value can represent 256 kinds of interrupts, which is consistent with the number of interrupts supported by the processor.

  • "int3". This is not an int space 3, there is no gap between them. int3 is a debug breakpoint instruction, the interrupt vector number triggered by it is 3, and the machine code of int3 is 0xcc.

  • into. This is an interrupt overflow instruction, and the interrupt vector number it triggers is 4. However, whether the No. 4 interrupt can be triggered depends on whether the OF bit in the eflags flag register is 1. If it is 1, an interrupt will be triggered, otherwise the instruction will quietly do nothing and be very low-key.

  • bound. This is the instruction to check the array index out of bounds, it can trigger the No. 5 interrupt to check whether the index subscript of the array is within the upper and lower boundaries. The command format is bound 16/32 位寄存器, 16/32 位内存. The destination operand is stored in a register, and its content is the subscript value of the array to be detected. . The source operand is memory whose contents are the lower and upper bounds of the array subscript. When the bound instruction is executed, if the subscript is outside the range of the array index, No. 5 interrupt will be triggered.

  • ud2. Undefined instruction, which triggers interrupt number 6. This instruction indicates that the instruction is invalid and cannot be recognized by the CPU. Actively use it to initiate an interrupt, which is often used in software testing and has no practical use.

Except for the first kind of "int 8-bit immediate value", the other kinds of soft interrupt instructions above can be called exceptions.


  1. Abnormal : The exception is caused by an error generated inside the CPU during the execution of the instruction. Because it is a runtime error, it is not affected by the IF bit in the flag register eflags and cannot be hidden from the user (because the operation cannot continue, and the error cannot be caught. ).
  • Not all abnormalities are fatal. According to the degree of severity, they can be divided into the following three types. :
    (1) Fault, also known as fault . This kind of error is a type that can be repaired. It belongs to the mildest kind of abnormality, and it gives the software a chance to "reform". When such an exception occurs, the CPU restores the machine state to the state before the exception, and then calls the interrupt handler, the CPU returns the address to still point to the instruction that caused the fault exception. Usually this problem is fixed in the interrupt handler and can be retried after the interrupt handler returns. The most typical example is the page fault mentioned in the operating system course. It is said that the virtual memory of Linux is based on the page fault, which fully shows that this kind of exception is extremely easy to be repaired, and even beneficial.
    (2) Trap, also known as a trap , the name vividly shows that the software has fallen into a trap set by the CPU, causing it to stop. This exception is usually used in debugging. For example, the int3 instruction causes this type of exception. In order to allow the interrupt handler to continue to execute after returning, the CPU points the return address of the interrupt handler to the address of the next instruction that caused the exception.
    (3) Abort, also known as termination . From the name, this is the most serious type of exception. Once it occurs, the program will not continue to run because the error cannot be repaired. In order to protect itself, the operating system can only disable this program from the process. removed from the table. Errors that cause this exception are usually hardware errors, or errors in some system data structures.
vector number mnemonic illustrate type error number source
0 #OF In addition to errors Fault none DIV or IDIV directive
1 #DB debugging Fault/Trap none Any code or data reference, or INT 1 instruction
2 - - NMI interrupt to interrupt none non-maskable external interrupt
3 #BP breakpoint trap none INT 3 instruction
4 #OF overflow trap none INTO instruction
5 #BR Boundary range exceeded Fault none BOUND command
6 #UD invalid opcode (undefined opcode) Fault none UD2 instruction or reserved opcode (new instruction added in Pentium Pro)
7 #NM Device does not exist (no math coprocessor) Fault none Floating point or WAIT/FWAIT instructions
8 #DF double error abnormal termination Yes (0) Any instruction that generates an exception, NMI, or INTR
9 - - Coprocessor segment override (reserved) Fault none Floating-point instructions (CPUs after 386 do not generate this exception)
10 #TS Invalid task state segment TSS Fault have Task exchange or visit TSS
11 #NP segment does not exist Fault have Load segment registers or access system segments
12 #SS stack error Fault have Stack operations and SS register loading
13 #GP general protection fault Fault have Any memory references and other protection checks
14 #PF page protection Fault have any memory reference
15 (Reserved by Intel, do not use) none
16 #MF x87FPU floating point error (math error) Fault none x87FPU floating point or WAIT/FWAIT instructions
17 #AC alignment check Fault Yes (0) A reference to any data in memory
18 #MC machine inspection abnormal termination none The error code (if any) and the source are related to the CPU type (introduced by the Pentium processor)
19 #XM SIMD floating point exception Fault none SSE and SSE2 floating point instructions (introduced by PIII processors)
20~31 - - (Reserved by Intel, do not use)
32~255 - - User-Defined (Non-Reserved) Interrupts to interrupt External interrupt or INT n instruction



Interrupt Descriptor Table

  • The Interrupt Descriptor Table (Interrupt Descriptor Table, IDT) is a table used to store the entry of the interrupt handler in protected mode. When the CPU receives an interrupt, it needs to use the interrupt vector to retrieve the corresponding descriptor in this table. In this description Find the start address of the interrupt handler in the symbol, and then execute the interrupt routine
  • There are not only interrupt descriptors in the interrupt descriptor table, but also task gate descriptors and trap gate descriptors. Since all descriptors in the table record the starting address of a program, which is equivalent to the "gate" of a program, the descriptors in the interrupt descriptor table have their own name ------- gate

insert image description here

insert image description here

insert image description here

insert image description here

task gate

  • Task gate and task status segment (Task Status Segment, TSS) is the task switching mechanism provided by the Intel processor at the hardware level, so the task gate needs to be used in conjunction with TSS, and the TSS selector is recorded in the task gate. Shift is not used. Task gates can exist in the Global Descriptor Table GDT, Local Descriptor Table LDT, and Interrupt Descriptor Table IDT. The type value of the task gate in the descriptor is binary 0101. Most operating systems (including Linux) do not use TSS for task switching.

interrupt gate

  • The interrupt gate includes the segment selector of the segment where the interrupt handler is located and the offset address within the segment. After entering the interrupt in this way, the IF bit in the flag register eflags is automatically set to 0, that is, after entering the interrupt, the interrupt is automatically turned off to avoid nesting of interrupts. Linux is a system call implemented by using the interrupt gate, which is the famous int 0x80. Interrupt gates are only allowed in the IDT. The type value of the interrupt gate in the descriptor is binary 1110.

trap door

  • The trap gate is very similar to the interrupt gate, the difference is that after the trap gate enters the interrupt, the IF bit in the flag register eflags will not be automatically set to 0. Trap doors are only allowed in IDTs. The type value of the trap door in the descriptor is binary 1111.

call gate

  • A call gate is a way for a user process to enter privilege level 0. Its DPL is 3. The address of the routine is recorded in the call gate, which cannot be called by the int instruction, but only by the call and jmp instructions. Call gates can be installed in GDT and LDT. The type value of the call gate in the descriptor is binary 1100.



insert image description here

There is an interrupt descriptor table register (Interrupt Descriptor Table Register, IDTR) inside the CPU, which is divided into two parts: bits 0~15 are table boundaries, IDT size minus 1, and bits 16~47 are the base address of IDT. The 16-bit table limit means that the maximum range is 0xffff, which is 64KB. The number of descriptors that can be accommodated is 64KB/8=8K=8192. Special attention is that the 0th segment descriptor in GDT is not available, but IDT has no such limitation, the 0th gate descriptor is also available, and the interrupt with interrupt vector number 0 is a division error. But the processor only supports 256 interrupts, that is, 0-254, and the rest of the interrupt descriptors are not available. There is a P bit in the gate descriptor, so when we build the IDT in the future, remember to set the P bit to 0, which means that the interrupt handler in the gate descriptor is not in memory.

Like loading GDTR, loading IDTR also has a special instruction—lidt, and its usage is:lidt 48 位内存数据




Interrupt handling and protection

The complete interrupt process is divided into two parts outside the CPU and inside the CPU:

  • Outside the CPU: The interrupt of the external device is received by the interrupt proxy chip, and the interrupt vector number of the interrupt is sent to the CPU after processing.
  • Inside the CPU: The CPU executes the interrupt handler corresponding to the interrupt vector number.

Processes inside the CPU:

  1. The processor locates the interrupt gate descriptor according to the interrupt vector number.
    The interrupt vector number is the index of the interrupt descriptor. When the processor receives an external interrupt vector number, it uses this vector number to query the corresponding interrupt descriptor in the interrupt descriptor table, and then executes the interrupt descriptor in the interrupt descriptor. Interrupt handler. Since the interrupt descriptor is 8 bytes, the processor multiplies the interrupt vector number by 8, and then adds it to the interrupt descriptor table address in IDTR. The sum of the requested addresses is the interrupt description corresponding to the interrupt vector number. symbol
  2. The processor checks the privilege level.
    Since the interrupt is notified to the processor through the interrupt vector number, the interrupt vector number is only an integer, and there is no RPL. Therefore, the RPL is not involved in the privilege level check of the privilege level transfer caused by the interrupt. The privilege check of the interrupt gate is similar to the call gate. For the soft interrupt initiated by the software, the current privilege level CPL must be between the gate descriptor DPL and the target code segment DPL in the gate. This is to prevent user programs at the 3 privilege level from actively calling certain routines that only serve the kernel.
    (a) If the interrupts are caused by soft interrupts int n, int3, and into, these are interrupts initiated actively in the user process. Due to user code control, the processor needs to check the current privilege level CPL and the gate descriptor DPL. This is the check The lower limit of the privilege of entering the door. If the CPL authority is greater than or equal to the DPL, the "threshold" check of the privilege level passes, and the next step of the "door frame" check is entered. Otherwise, the processor throws an exception. (b) This step checks the upper limit of the privilege level (door frame)
    : The processor needs to check the current privilege level CPL and the target code segment DPL corresponding to the selector recorded in the gate descriptor. If the CPL authority is less than the target code segment DPL, the check passes; otherwise, if the CPL is greater than or equal to the target code segment DPL, the processor will An exception is thrown, that is, except for returning from a high privilege level with a return instruction, a privilege transfer can only occur from low to high.
    If the interrupt is caused by an external device and an exception, only the CPL and the DPL of the target code segment are directly checked, which is the same as step b) above, and the CPL authority is required to be smaller than the DPL of the target code segment, otherwise the processor throws an exception.
  3. Execute the interrupt handler.
    After the privilege check is passed, load the target code segment selector of the gate descriptor into the code segment register CS, load the offset address of the interrupt handler in the gate descriptor into EIP, and start executing the interrupt handler.
    insert image description here
  • After the interrupt occurs, the NT bit and TF bit in eflags will be set to 0. If the gate descriptor corresponding to the interrupt is an interrupt gate, the IF bit of the flag register eflags will be automatically set to 0 to avoid nesting of interrupts, that is, during interrupt processing A new interrupt came again, which is to prevent the same interrupt from coming again during the process of processing an interrupt. This causes a general protective (GP) exception. This means that by default, the processor executes the interrupt handling routine in the interrupt gate descriptor in an undisturbed mode.

  • If the corresponding interrupt is a task gate or a trap gate, the CPU will not clear the IF bit to 0. Interrupt nesting is allowed because trap gates are primarily used for debugging, allowing the CPU to respond to higher-level interrupts. For the task gate, this is to execute a new task, and the task should be performed under the condition of opening and interrupting, otherwise the CPU resources will be monopolized, and the operating system will degenerate from multi-tasking to single-tasking.

  • The instruction returning from the interrupt is iret, which pops data from the stack to registers cs, eip, eflags, etc., and judges whether to restore the old stack according to whether the privilege level has changed, that is to say, whether to restore the value at the position of SS_old and ESP_old in the stack Pop to register ss and esp. When the interrupt handler finishes executing and returns, restore the contents of eflags from the stack through the iret instruction.

Push stack when interrupt occurs

After the interrupt occurs, since CS loads a new target code segment selector, the processor does not care whether the new selector is the same as the current selector in any segment register, or whether the two selectors point to the same segment, as long as the segment The register is loaded, the segment descriptor buffer register will be refreshed, and the processor will think that a segment has been changed, which belongs to an inter-segment transfer, that is, a far transfer. Therefore, after the current process is interrupted by an interrupt, in order to continue running the process after returning from the interrupt, the processor automatically saves the current values ​​of CS and EIP to the stack used by the interrupt handler. Processors use different stacks at different privilege levels. As for which stack the interrupt handler uses, it depends on the privilege level it is in at the time, because interrupts can occur at any privilege level. In addition to saving CS and EIP, the flag register EFLAGS also needs to be saved. If privilege level conversion is involved, the SS and ESP registers must also be pushed.

Let's take a look at the stacking situation and order of the above registers, and we will not discuss the content of privilege checking here:

  1. After the processor finds the corresponding interrupt descriptor according to the interrupt vector number, it compares the CPL with the DPL of the target code segment corresponding to the selector in the interrupt gate descriptor. If the CPL authority is lower than the DPL, it means that it needs to transfer to a higher privilege level. Need to switch to a high-privileged stack. This means that after executing the interrupt handler, if you want to return to the currently interrupted process correctly, you also need to restore the stack to the old stack at this time. So the processor temporarily saves the values ​​of the old stack SS and ESP, denoted as SS_old and ESP_old, and then finds in TSS the same stack as the target code segment DPL level and loads it into the registers SS and ESP, denoted as SS_new and ESP_new, and then The previously temporarily saved SS_old and ESP_old are pushed into the new stack backup for reloading to the stack segment register SS and stack pointer ESP when returning. Since SS_old is 16-bit data, the stack operand in 32-bit mode is 32-bit, so SS_old is extended by 16 bits with 0 to become 32-bit data and then pushed into the stack. At this time, the content of the new stack is shown in Figure A.

  2. Push the EFLAGS register into the new stack, and the content of the new stack is shown in Figure B

  3. Since it is necessary to switch to the target code segment, for this inter-segment transfer, it is necessary to save CS and EIP to the current stack for backup, which are recorded as CS_old and EIP_old, so that the interrupted process can be restored after the execution of the interrupt program is completed. Similarly, CS_old is 16-bit data, and its upper 16 bits need to be filled with 0, expanded to 32-bit data and then pushed into the stack. At this time, the content of the new stack is shown in Figure C.

  4. Some exceptions will have an error code. This error code is used to report which segment the exception occurred on, that is, the location where the exception occurred. Therefore, the error code contains information such as the selector, and the error code will be pushed onto the stack immediately after EIP , recorded as ERROR_CODE. At this time, the contents of the new stack are shown in Figure D.
    insert image description here

  • If it is judged in step 1 that no privilege-level transfer is involved, it will not look for a new stack in the TSS, but will continue to use the current old stack, so it is impossible to restore the old stack. At this time, the data in the stack is not available when an interrupt occurs. Includes SS_old and ESP_old. For example, when the interrupt occurs, the kernel program is currently running, which is from 0 privilege level to 0 privilege level, and there is no privilege level change.

insert image description here

  • After the processor enters the interrupt and executes the interrupt handler, it must return to the interrupted process, which is the reverse process of entering the interrupt. Returning from an interrupt is accomplished with the iret instruction. Iret, that is, interrupt ret, this instruction is dedicated to returning from the interrupt handler. Assuming that in 32-bit mode, it pops 32-bit data from the top of the current stack to registers EIP, CS, and EFLAGS respectively. The iret instruction does not know the correctness of the data in the stack. It is only responsible for popping the data from the top of the stack, 4 bytes at a time, into the relevant registers. Therefore, before using iret, you must ensure that the data from the top of the stack The data is correct, and the order from the top of the stack is EIP, CS, EFLAGS, and ESP, SS according to whether the privilege level changes.
  • Since the segment register CS is 16 bits, the high 16 bits of the 32-bit data returned from the stack are discarded, and only the low 16 bits are loaded into CS. If the processor finds that the privilege level will change after returning, it will continue to return two double-word data to ESP and SS, where SS is also a 16-bit register, so after popping up 32-bit data, only the lower 16 bits will be loaded to SS. The iret instruction means return from interrupt, so it is the last instruction in the interrupt handler.
  • Similar instructions include iretw and iretd, iretw is used in 16-bit mode, and iretd is used in 32-bit mode. iret is the abbreviation of iretw and iretd. Whether it is encoded in 16-bit mode or 32-bit mode, only the iret instruction can be used. Whether it is compiled into iretw or iretd depends on the word length specified by the pseudo-instruction BITS .

Let's talk about the process of returning from the interrupt handler:
(1) When the processor executes the iret instruction, it knows that to perform a far return, it first needs to return the code segment selector CS_old and the instruction pointer of the interrupted process from the stack EIP_old. At this time, it needs to perform a privilege level check. First check the CS selector CS_old in the stack, and judge whether to change the privilege level during the return process according to its RPL bit, that is, the future CPL.
(2) The CS selector in the stack is CS_old. Check the privilege level according to the DPL of the code segment corresponding to CS_old and the RPL in CS_old. If the check is passed, the registers CS and EIP need to be updated immediately. Since CS_old has expanded the upper 16 bits to 0 when it was pushed into the stack, it is now 32-bit data, and the segment register CS is 16 bits, so the processor discards the upper 16 bits of CS_old, loads the lower 16 bits to CS, and loads EIP_old to EIP register, after which the stack pointer points to EFLAGS. If there is no privilege level transfer involved when entering the interrupt, the stack pointer is ESP_old at this time (indicating that after entering the interrupt before, the old stack is continued to be used). Otherwise, the stack pointer is ESP_new (indicating that the new stack recorded in TSS is used after entering the interrupt before).
(3) Pop the EFLAGS saved in the stack to the flag register EFLAGS. If the privilege level needs to be changed after judging in step 1, the stack pointer is ESP_new, which points to ESP_old in the stack. Otherwise, it is a horizontal transfer when entering an interrupt, and the old stack is used. At this time, the stack pointer is ESP_old, and there is no data pushed into the stack because of this interrupt, and the stack pointer points to the top of the stack before the interrupt occurred.
(4) If it is judged in step 1 that the privilege level needs to be changed when returning, that is to say, the old stack needs to be restored, then it is necessary to load ESP_old and SS_old into the registers ESP and SS respectively, and discard the original values ​​in the registers SS and ESP. SS_new and ESP_new perform privilege level checks at the same time. In addition, since SS_old has been filled with 0 by the processor when it is pushed into the stack, it is now 32-bit data, so before reloading to the stack segment register SS, the high 16 bits of SS_old need to be stripped and discarded, and only its low 16-bit load SS.

interrupt error code

Some interrupts will push an error code on the stack, which is a bit like "last words, provide clues", which is used to indicate which segment the interrupt occurred on. Therefore, the most important part of the error code is the selector, but this selector can retrieve descriptors in various tables. The error code consists of several parts, the format is shown in the figure
insert image description here

  • EXT means EXTernal event, that is, an external event, which is used to indicate whether the interrupt source is from outside the processor. If the interrupt source is a non-maskable interrupt NMI or an external device, EXT is 1, otherwise it is 0.

Programmable Interrupt Controller 8259A

  • 8259A is used to manage and control maskable interrupts. It is shown in shielding peripheral interrupts, implementing priority judgments on them, and providing functions such as interrupt vector numbers to the CPU. The reason why it is called programmable is that the above functions can be set by programming.

  • Intel processors support a total of 256 interrupts, but 8259A can only manage 8 interrupts, so in order to support more interrupt devices, another solution is provided, combining multiple 8259A, the official term is cascading. With the combination of cascading, each 8259A is called 1 chip. If the cascading method is used, that is, multiple 8259A chips are connected in series, and a maximum of 9 can be cascaded, that is, a maximum of 64 interrupts can be supported. N slices of 8259A can support 7n+1 interrupt sources through cascading. When cascading, only one slice of 8259A is the master slice, and the rest are slave slices. The interrupt from the slave chip can only be passed to the master chip, and then passed up to the CPU by the master chip, that is to say, only the master chip will send the INT interrupt signal to the CPU.

  • Each independently running external device is an interrupt source. The interrupts they send can only be known by the CPU master when they are connected to the interrupt request (IRQ: InterruptReQuest) signal line. This means that when you turn on the computer, there will be See IRQ1...IRQn, these are the interrupt numbers assigned to external devices.

insert image description here

8259A internal structure schematic diagram:
insert image description here

  • INT: After the 8259A selects the interrupt request with the highest priority, it sends a signal to the CPU.
  • INTA: INT Acknowledge, interrupt response signal. INTA located in 8259A receives interrupt response signal from CPU's INTA interface.
  • IMR: Interrupt Mask Register, interrupt mask register, the width is 8 bits, used to mask the interrupt of a certain peripheral.
  • IRR: Interrupt Request Register, interrupt request register, the width is 8 bits. Its function is to accept and latch the interrupt signal filtered by the IMR register. This register is full of interrupts waiting to be processed, which is "equivalent" to the unprocessed interrupt signal queue maintained by 5259A.
  • PR: Priority Resolver, priority arbiter. When multiple interrupts occur at the same time, or when a new interrupt request comes in, compare it with the interrupt currently being processed to find out the interrupt with higher priority.
  • ISR: In-Service Register, interrupt service register, the width is 8 bits. When an interrupt is being serviced, it is saved in this register.

8259A workflow: When a peripheral device sends an interrupt signal, the interrupt signal is finally sent to the 8259A because the motherboard has already pointed the signal path to an IRQ interface of the 8259A chip.

  1. 8259A first checks whether the interrupt signal from the IRQ interface has been masked in the IMR register. The bit in the IMR register is 1, which means interrupt masking, and 0 means interrupt release. If the bit corresponding to the IRQ has been set to 1, it means that the interrupt from the IRQ interface has been masked. Then discard the interrupt signal, otherwise, send it to the IRR register,
  2. After sending to the IRR register, set the corresponding BIT in the IRR register where the IRQ interface is located. The role of the IRR register is "equivalent" to a queue of pending interrupts. At an appropriate time, the priority arbiter PR will select an interrupt with the highest priority from the IRR register. The priority judgment here is very simple, that is, the lower the IRQ interface number, the higher the priority, so IRQ0 priority maximum.
  3. After that, the 8259A will send the INTR signal to the CPU through the INT interface in the control circuit. After the signal is sent to the INTR interface of the CPU, the CPU knows that the interrupt of the letter is coming, and there is another job to do, so after the CPU executes the instruction in hand, it immediately communicates to the INTA interface of the 8259A through its own INTA interface. Reply an interrupt response signal, indicating that I am ready for the CPU now, and you can continue to work on the 8259A.
  4. After receiving this signal, the 8259A immediately sets the BIT corresponding to the interrupt with the highest priority selected just now in the ISR register to 1. This register indicates the interrupt currently being processed, and at the same time, the interrupt must be removed from the "pending interrupt queue" Removed from IRR, that is, the BIT corresponding to 1 of the interrupt is set to 0 when the IRR wins the prize.
  5. After that, the CPU will send the INTA signal to the 8259A again, this time to obtain the interrupt vector number corresponding to the interrupt. Since the initial interrupt vector number of 8259A is not 0 in most cases (the initial interrupt vector number is modified, the reason will be mentioned later), so the initial interrupt vector number + IRQ interface number is the interrupt vector number of the device, It can be seen that although the external device will send an interrupt signal, it does not know that there is an interrupt vector number, and it does not know that it will be assigned such an integer by the interrupt agent (such as 8259A).
  6. Subsequently, 8259A sends this interrupt vector number to the CPU through the system data bus. After the CPU gets the interrupt vector number from the data bus, it uses it as an index in the interrupt vector table or interrupt descriptor table to find the corresponding interrupt handler and execute it.

If the "EOI Notification (End Of Interrupt)" of 8259A is set to non-automatic mode (manual mode), there must be a code to send EOI to 8259A at the end of the interrupt handler. After 8259A receives EOI, it will The corresponding BIT in the ISR register is set to 0 for the interrupt. If "EOI notification" is set to automatic mode, after the 8259A receives the second INTA signal just now, that is, the INTA that the CPU asks the 8259A for the interrupt vector number, the 8259A will automatically set the corresponding BIT of this interrupt in the ISR to 0 .




Programming the 8259A

The programming of 8259A is to initialize it, set the cascading mode of the main slice and the slave slice, specify the starting interrupt vector number and set various tasks.

The interrupt vector number is a logical thing, it is physically the IRQ interface number on 8259A. The order of the IRQ numbers on the 8259A is fixed, but the corresponding interrupt vector numbers are not fixed. This is actually a mapping from hardware to software. By setting the 8259A, the IRQ interface can be mapped to different interrupt vector numbers.

There are two sets of registers inside the 8259A, one set is the initialization command register set, which is used to save the initialization command words (InitializationCommand Words, ICW), and there are 4 ICWs in total, ICW1~ICW4. The other group of registers is the operation command register group, which is used to save the operation command word (Operation Command Word, OCW). There are 3 OCWs in total, OCW1~OCW3. Therefore, our programming of 8259A is also divided into two parts, initialization and operation.

  • Part of it is initialized with ICW, which is used to determine whether cascading is required, set the initial interrupt vector number, and set the interrupt end mode. Its programming is to send a series of ICWs to the port of 8259A. Since the working status of 8259A must be determined from the very beginning, many settings need to be written at one time. Some settings are related and dependent. Maybe a later setting will depend on the setting written by a previous ICW. Therefore, this part requires a strict sequence, and ICW1, ICW2, ICW3, and ICW4 must be written in sequence.
  • The other part is to use OCW to operate and control 8259A. The above-mentioned interrupt mask and interrupt end are realized by sending OCW to the 8259A port. The order in which OCWs are sent is not fixed, and it doesn't matter which of the three is sent first.
    insert image description here
    ICW1 is used to initialize the connection mode of 8259A and the trigger mode of interrupt signal. The connection mode refers to whether it works with a single chip or multi-chip cascaded work, and the trigger mode refers to whether the interrupt request signal is level-triggered or edge-triggered.
  • Note that ICW1 needs to be written to the 0x20 port of the master chip and the 0xA0 port of the slave chip
  • IC4 indicates whether to write ICW4, which means that not all ICW initialization control words need to be used. When IC4 is 1, it means that ICW4 needs to be written later, and if it is 0, it does not. Note that IC4 must be 1 for x86 systems.
  • SNGL means single, if SNGL is 1, it means single chip, if SNGL is 0, it means cascade. Let me talk about it here, if in cascading mode, this involves the issue of which IRQ interface is used for the master (1) and slave (multiple) to connect to each other, so when SNGL is 0, the master and slave are also ICW3 is required.
  • ADI stands for call address interval, which is used to set the call interval of 8085, and x86 does not need to be set.
  • LTIM means level/edge triggered mode, which is used to set the interrupt detection mode, LTIM is 0 means edge triggered, LTIM is 1 means level triggered.
  • 1 in the 4th bit is fixed, which is the flag of ICW1.
  • The 5th to 7th bits are dedicated to the 8085 processor, not needed for x86, just set to 0 directly.

insert image description here
ICW2 is used to set the initial interrupt vector number, which is the mapping from the hardware IRQ interface to the logical interrupt vector number mentioned above. Since the IRQ interfaces on each 8259A chip are arranged sequentially, our setting here is to specify the interrupt vector number to which IRQ0 is mapped, and the corresponding interrupt vector numbers of other IRQ interfaces will be automatically arranged in sequence.

  • Note that ICW2 needs to be written to the 0x21 port of the master chip and the 0xA1 port of the slave chip
  • Because we only need to set the interrupt vector number of IRQ0, the interrupt vector number of IRQ1~IRQ7 is the extension of IRQ0, so we are only responsible for filling in the upper 5 bits T3~T7, and the lower 3 bits of ID0~ID2 are not our responsibility. Because we only fill in the upper 5 bits, any number is a multiple of 8, and this number represents the set initial interrupt vector number. This is intentionally designed. The lower 3 bits can represent 8 interrupt vector numbers, which are automatically imported by 8259A according to the arrangement of the 8 IRQ interfaces. The value of IRQ0 is 000, the value of IRQ1 is 001, and the value of IRQ2 is 010... …and so on, so that the high 5 bits plus the low 3 bits represent the interrupt vector number actually allocated by any IRQ interface.

insert image description here

insert image description here
ICW3 is only required in the cascading mode (if SNGL in ICW1 is 0), and is used to set which IRQ interface is used for interconnection between the master and the slave.
Due to the different cascading methods of the main slice and the slave slice, for this ICW3, the master slice and the slave slice have their own different structures

  • ICW3 needs to write to the 0x21 port of the master chip and the 0xA1 port of the slave chip.
  • For the master chip, the IRQ interface corresponding to the bit set to 1 in ICW3 is used to connect to the slave chip, and if it is 0, it means to connect to an external device. For example, if the master chip IRQ2 and IRQ5 are connected to the slave chip, the ICW3 of the master chip is 00100100
  • For the slave chip, it is necessary to set the connection mode with the master chip 8259A, "no need" to specify which IRQ interface to use to connect with the master chip, the interface on the slave chip specially used for cascading the master chip is not the IRQ. The way to set up the slave chip to connect to the master chip is to specify the IRQ interface that the master chip uses to connect to itself on the slave chip.
  • When responding to an interrupt, the master chip will send the IRQ interface number that is cascaded with the slave chip, and all slave chips compare it with the lower 3 bits of their own ICW3, and if they are consistent, they are considered to be sent to themselves. For example, if the master uses IRQ2 interface to connect slave A, and uses IRQ5 interface to connect slave B, the value of ICW3 of slave A should be set to 00000010, and the value of ICW3 of slave B should be set to 00000101. Therefore, the lower 3 bits of ID0~ID2 in the slave chip ICW3 are enough, and the upper 5 bits are not needed, just 0

insert image description here

  • The 7th to 5th bits are undefined, just set to 0 directly.
  • SFNM means Special Fully Nested Mode. If SFNM is 0, it means fully nested mode. If SFNM is 1, it means special fully nested mode.
  • BUF indicates whether the 8259A chip is working in buffer mode. If BUF is 0, it works in non-buffer mode; if BUF is 1, it works in buffer mode.
  • When multiple 8259A are cascaded, if they work in the buffer mode, M/S is used to specify whether the 8259A is the main chip or the slave chip. If M/S is 1, it means it is the master chip, if M/S is 0, it means it is the slave chip. If it works in non-buffer mode (BUF is 0), M/S is invalid.
  • AEOI means Auto End Of Interrupt, 8259A can continue to process the next interrupt only when it receives the interrupt end signal, this item is used to set whether to let 8259A end the interrupt automatically. If AEOI is 0, it means that it is not automatic, that is, the interrupt is ended manually. We can manually send EOI signals to the master and slave chips of 8259A in the interrupt handler or in the main function. This kind of "operation" type command is carried out through the OCW to be introduced below. If AEOI is 1, it means automatic end of interrupt.
  • μPM represents the microprocessor type (microprocessor), this item is for compatibility with old processors. If μPM is 0, it means 8080 or 8085 processor, if μPM is 1, it means x86 processor.



insert image description here
OCW1 is used to shield the interrupt signal of the external device connected to 8259A, in fact, OCW1 is written into the IMR register. The shielding here means whether to forward the interrupt signal from the external device to the CPU. Since the interrupts of external devices are all maskable interrupts, they are ultimately subject to the control of the IF bit in the flag register eflags. If IF is 0, all maskable interrupts are masked. That is to say, when IF is 0, Even if the 8259A sends the interrupt vector number of the external device, the CPU ignores it.

  • Note that OCW1 should be written to port 0x21 of the master or port 0xA1 of the slave
  • M0~M7 correspond to IRQ0~IRQ7 of 8259A, if a certain bit is 1, the interrupt signal on the corresponding IRQ is masked. Otherwise, if a certain bit is 0, the corresponding IRQ interrupt signal will be released.

insert image description here

  • OCW2 is used to set the interrupt end mode and priority mode.
  • Note that OCW2 should be written to port 0x20 of the master chip and port 0xA0 of the slave chip.
  • The configuration of OCW2 is relatively complicated, and various attribute bits must be matched together to combine various working modes of 8259A. The high 3 bits R, SL, and EOI can define multiple interrupt end methods and priority cycle methods.
  • R, Rotation, indicates whether to set the interrupt priority in a circular manner. If R is 1, it means automatic cycle of priority, if R is 0, it means no automatic cycle, and fixed priority mode is adopted.
  • SL, Specific Level, indicates whether to specify a priority level. The level is specified with the lower 3 bits. The SL here is just to turn on the switch of the lower 3 bits, so SL also indicates whether the lower 3 bits L2~L0 are valid or not. SL is 1 means valid, SL is 0 means invalid.
  • EOI, End Of Interrupt, is the interrupt end command bit. If EOI is set to 1, the corresponding bit in the ISR register will be cleared to 0, that is, the currently processed interrupt will be cleared, indicating the end of processing. Actively sending EOI to 8259A is the method of manually ending the interrupt, so there is a prerequisite for using this command, that is, the AEOI bit in ICW4 is 0, and it is only used when the interrupt is not automatically ended.
  • 00 in the 4th to 3rd digits is the identification of OCW2.
  • L2~L0 are used to determine the coding of the priority. There are two types here. One is used for EOI, indicating the interrupted priority level, and the other is used for priority cycle, specifying the lowest initial priority level.
    insert image description here



insert image description here

  • OCW3 is used to set special shielding mode and query mode
  • Note that OCW3 should be written to the 0x20 port of the master chip or the 0xA0 port of the slave chip.
  • Bit 7 is not used.
  • The 6-bit ESMM (Enable Special Mask Mode) and the 5th-bit SMM (Special Mask Mode) are used together to enable or disable the special mask mode. ESMM is the special mask mode enable bit, which is a switch. SMM is the special mask mode bit. Special masking mode is only valid when special masking mode is enabled. That is, if ESMM is 0, SMM is invalid. If ESMM is 1 and SMM is 0, it means not working in special shielding mode. If both ESMM and SMM are 1, it will work in special shielding mode officially.
  • 01 in the 4th to 3rd digits is the identification of OCW3, and 8259A judges which control word it is through these two digits.
  • P, Poll command, query command, when P is 1, set 8259A to interrupt query mode, so that by reading the register,
  • RR, Read Register, read register command. It is used together with the RIS bit. Registers can only be read when RR is 1.
  • RIS, Read Interrupt register Select, read interrupt register select bit, as the name implies, is to use this bit to select the register to be read. It is a bit similar to the index in the graphics card register. If RIS is 1, it means select ISR register, if RIS is 0, it means select IRR register. These two registers can be read if the value of RR is 1.

The initialization of 8259A must be completed first, the steps are:

  • Regardless of whether 8259A is cascaded or not, ICW1 and ICW2 must be available and written in sequence.
  • Only when the SNGL bit in ICW1 is 0, it means cascading, and cascading needs to set the master slice and the slave slice, so it is necessary to write ICW3 in the master slice and the slave slice respectively. Note that the format of ICW3 is different in master and slave.
  • Writing to ICW4 is only required when IC4 in ICW1 is 1. However, IC4 must be 1 for x86 systems.
  • In the x86 system, for initializing the cascaded 8259A, all four ICWs are required, for initializing the single-chip 8259A, ICW3 is not required, and all others are required.

Only after the above initialization of 8259A can it be operated with OCW.



//interrupt.c
#include "interrupt.h"
#include "print.h"
#include "io.h"


/*中断门描述符结构体*/
struct gate_desc {
    
    
   unsigned short func_offset_low_word;
   unsigned short selector;
   unsigned char  dcount;   //此项为双字计数字段,是门描述符中的第4字节。此项固定值,不用考虑
   unsigned char  attribute;
   unsigned short func_offset_high_word;
};




static struct gate_desc idt[33];  // idt是中断描述符表,本质上就是个中断门描述符数组
extern int intr_entry_table[33];





static void idt_desc_init(void) 
{
    
    
   int i;
   for (i = 0; i < 33; i++) 
   {
    
    
		idt[i].func_offset_low_word = ((unsigned int)intr_entry_table[i]) & 0x0000FFFF;
		idt[i].selector=0b1000;		//代码段选择子
		idt[i].dcount = 0;			//未使用
		idt[i].attribute=0b10001110;
		idt[i].func_offset_high_word = ((unsigned int)intr_entry_table[i] & 0xFFFF0000) >>16;
   }

   put_str("   idt_desc_init done\n");
} 

/* 初始化可编程中断控制器8259A */
static void pic_init(void) {
    
    

   /* 初始化主片 */
   outb (0x20, 0x11);   // ICW1: 边沿触发,级联8259, 需要ICW4.
   outb (0x21, 0x20);   // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.
   outb (0x21, 0x04);   // ICW3: IR2接从片. 对于主片,ICW3 中置 1 的那一位对应的 IRQ 接口用于连接从片
   outb (0x21, 0x01);   // ICW4: 8086模式, 正常EOI

   /* 初始化从片 */
   outb (0xa0, 0x11);	// ICW1: 边沿触发,级联8259, 需要ICW4.
   outb (0xa1, 0x28);	// ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.
   outb (0xa1, 0x02);	// ICW3: 设置从片连接到主片的IR2引脚
   outb (0xa1, 0x01);	// ICW4: 8086模式, 正常EOI

   /* 打开主片上IR0,也就是目前只接受时钟产生的中断 */
   outb (0x21, 0xfe);	//OCW1主片
   outb (0xa1, 0xff);	//OCW1 从片

   put_str("   pic_init done\n");
}



void idt_init() 
{
    
    
	put_str("idt_init start\n");
	idt_desc_init();	   // 初始化中断描述符表
	
	pic_init();		   // 初始化8259A
	 
	 
	//第0~15位是表界限,即IDT减1,可容纳8192个中段描述符;第16~47位时IDT的基地址。 
	/* 加载idt */ 
   unsigned long long int idt_operand = ((sizeof(idt) - 1) | (( unsigned long long int)(unsigned int)idt << 16));
   asm volatile("lidt %0" : : "m" (idt_operand));
   put_str("idt_init done\n");
}


//kernel.c
#include "print.h"
#include "io.h"


void intr_entry_0() 
{
    
    
    put_str("intr_entry_0\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_1() 
{
    
    
    put_str("intr_entry_1\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_2() 
{
    
    
    put_str("intr_entry_2\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_3() 
{
    
    
    put_str("intr_entry_3\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_4() 
{
    
    
    put_str("intr_entry_4\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_5() 
{
    
    
    put_str("intr_entry_5\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_6() 
{
    
    
    put_str("intr_entry_6\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_7() 
{
    
    
    put_str("intr_entry_7\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_8() 
{
    
    
    put_str("intr_entry_8\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}

void intr_entry_9() 
{
    
    
    put_str("intr_entry_9\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_a() 
{
    
    
    put_str("intr_entry_A\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_b() 
{
    
    
    put_str("intr_entry_b\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_c() 
{
    
    
    put_str("intr_entry_c\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_d() 
{
    
    
    put_str("intr_entry_d\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_e() 
{
    
    
    put_str("intr_entry_e\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_f() 
{
    
    
    put_str("intr_entry_f\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_10() 
{
    
    
    put_str("intr_entry_10\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_11() 
{
    
    
    put_str("intr_entry_11\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_12() 
{
    
    
    put_str("intr_entry_12\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_13() 
{
    
    
    put_str("intr_entry_13\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_14() 
{
    
    
    put_str("intr_entry_14\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_15() 
{
    
    
    put_str("intr_entry_15\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_16() 
{
    
    
    put_str("intr_entry_16\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_17() 
{
    
    
    put_str("intr_entry_17\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_18() 
{
    
    
    put_str("intr_entry_18\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_19() 
{
    
    
    put_str("intr_entry_19\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_1a() 
{
    
    
    put_str("intr_entry_1a\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}



void intr_entry_1b() 
{
    
    
    put_str("intr_entry_1b\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_1c() 
{
    
    
    put_str("intr_entry_1c\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_1d() 
{
    
    
    put_str("intr_entry_1d\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_1e() 
{
    
    
    put_str("intr_entry_1e\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_1f() 
{
    
    
    put_str("intr_entry_1f\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_20() 
{
    
    
	static int i=0;
    put_str("intr_entry_20\n");
	put_int(i);
	 put_str("\n");
	i++;
    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


int intr_entry_table[33]={
    
    
(int)intr_entry_0,
(int)intr_entry_1,
(int)intr_entry_2,
(int)intr_entry_3,
(int)intr_entry_4,
(int)intr_entry_5,
(int)intr_entry_6,
(int)intr_entry_7,
(int)intr_entry_8,
(int)intr_entry_9,
(int)intr_entry_a,
(int)intr_entry_b,
(int)intr_entry_c,
(int)intr_entry_d,
(int)intr_entry_e,
(int)intr_entry_f,
(int)intr_entry_10,
(int)intr_entry_11,
(int)intr_entry_12,
(int)intr_entry_13,
(int)intr_entry_14,
(int)intr_entry_15,
(int)intr_entry_16,
(int)intr_entry_17,
(int)intr_entry_18,
(int)intr_entry_19,
(int)intr_entry_1a,
(int)intr_entry_1b,
(int)intr_entry_1c,
(int)intr_entry_1d,
(int)intr_entry_1e,
(int)intr_entry_1f,
(int)intr_entry_20};

Why add "leave"
insert image description here

objdump -d kernel.oViewing the disassembly code with the command shows that the compiler adds codes to save the old base pointer, create a new base pointer, and allocate space for local variables at the beginning of the function . So you need to use leavethe command equivalent mov esp, ebp pop ebpto make the stack balanced.

Guess you like

Origin blog.csdn.net/qq_17111397/article/details/132085675
Recommended