IRQ 和 iret 指令语义在 32 位内核(保护模式)上的工作
Working of the IRQs and the iret instruction semantics on a 32 bit kernel (protected mode)
我一直在写一个业余爱好OS,我正在尝试在内核中进行interrupt/exception 处理。我在 ring 0 中,所以没有内部特权堆栈切换等。这些是我的例程:
#include <stdint.h>
#include "dadio.h"
#define MAX_INTERRUPTS 256
#define IDT_DESC_BIT16 0x06 //00000110
#define IDT_DESC_BIT32 0x0E //00001110
#define IDT_DESC_RING1 0x40 //01000000
#define IDT_DESC_RING2 0x20 //00100000
#define IDT_DESC_RING3 0x60 //01100000
#define IDT_DESC_PRESENT 0x80//10000000
//Structs used in this routine
typedef struct __attribute__ ((__packed__)) idtr {
uint16_t limit;
uint32_t base;
}idtr_t;
typedef struct __attribute__ ((__packed__)) gdtr {
uint16_t limit;
uint32_t base;
}gdtr_t;
typedef struct __attribute__ ((__packed__)) idt_descriptor {
uint16_t baseLo;
uint16_t sel;
uint8_t reserved;
uint8_t flags;
uint16_t baseHi;
}idt_descriptor_t;
typedef struct __attribute__((__packed__)) gdt_descriptor {
uint16_t limit;
uint16_t baseLo;
uint8_t baseMid;
uint16_t flags;
uint8_t baseHi;
} gdt_descriptor_t;
//External assembly functions
void init_pic();
void install_idt(idtr_t* address);
void enable_interrupts();
//Global variables in this routine
static idt_descriptor_t _idt[MAX_INTERRUPTS];
static idtr_t _idtr; //This will be the 6 byte base + limit
//Helper functions
static void install_ir(uint32_t index,uint16_t flags, uint16_t sel, uint32_t* handler_address);
static void default_handler();
void idt_init()
{
_idtr.base = (uint32_t)_idt;
_idtr.limit = (sizeof (idt_descriptor_t) * MAX_INTERRUPTS) -1 ;
for (int i=0;i<MAX_INTERRUPTS;i++)
{
_idt[i].baseLo = 0;
_idt[i].sel = 0;
_idt[i].reserved = 0;
_idt[i].flags = 0;
_idt[i].baseHi = 0;
}
for (int i=0;i<MAX_INTERRUPTS;i++)
install_ir(i,IDT_DESC_BIT32 | IDT_DESC_PRESENT, 0x08, (uint32_t*) default_handler);
init_pic();
install_idt(& _idtr);
enable_interrupts();
}
static void install_ir(uint32_t index,uint16_t flags, uint16_t sel, uint32_t* handler_address)
{
if (index >=MAX_INTERRUPTS) return;
_idt[index].baseLo = (uint32_t)handler_address & 0xffff;
_idt[index].baseHi = ((uint32_t)handler_address >> 16) & 0xffff;
_idt[index].reserved = 0;
_idt[index].flags = flags;
_idt[index].sel = sel;
}
static void default_handler()
{
monitor-puts("This is the default exception handler"); //This is a routine that prints messages on the screen... The gist is that it writes to 0xb8000 and so on...
for (;;);
}
汇编例程
init_pic:
mov al, 0x11 ;ICW 1 ;Expect IC4|single?|0|level?|init?|000
out 0x20,al
out 0xA0,al
mov al,0x20 ;Remapping the IRQs
out 0x21,al
mov al,0x28
out 0xA1,al
; Send ICW 3 to primary PIC
mov al, 0x4 ; 0x4 = 0100 Second bit (IR Line 2)
out 0x21, al ; write to data register of primary PIC
; Send ICW 3 to secondary PIC
mov al, 0x2 ; 010=> IR line 2
out 0xA1, al ; write to data register of secondary PIC
; Send ICW 4 - Set x86 mode --------------------------------
mov al, 1 ; bit 0 enables 80x86 mode
out 0x21, al
out 0xA1, al
; Zeroing out the data registers
mov al, 0
out 0x21, al
out 0xA1, al
ret
enable_interrupts:
sti
ret
最小内核为:
void kmain()
{
idt_init();
return;
}
如果我在 idt_init 函数 中注释掉 init_pic(),我会收到消息:This is the default异常处理程序,后跟 for(;;)。
我认为这是意料之中的,因为一旦我启用中断,定时器之类的东西就会发送一个 IRQ,并且由于它默认映射到(除以零?)异常,我得到了我定义的处理程序消息。
但是如果我取消对 init_pic() 的注释,我就不会收到消息。我知道 IRQ (0-15) 已被重新映射到中断向量 (32 - 47)。但是定时器中断仍然会触发,我应该得到消息。 (我认为,在我的案例中,所有 256 个可能的 interrupts/exceptions 都映射到相同的例程)。
我哪里做错了?
另外,还有一个小的后续问题。我知道有些异常会推送错误代码,而有些则不会。但是 iret 指令不能知道,对吧?那么程序员有责任手动添加到esp(弹出异常的错误代码确实会引发错误)然后进行iret吗?
我已经阅读了 80386 开发人员手册,据我了解。我哪里错了吗?
PS:尽管我的项目有更多代码,但我已尝试提供最少的代码。
一个关键位可能是 kmain returns?
时发生的情况
后续问题很重要:你必须在 iret 之前清理堆栈。仅出于这个原因,将门指向 C 函数确实很麻烦。大多数人制作两个汇编存根:
#define SAVE() pusha; push %ds; push %es; push %fs; push %gs
#define RESTORE() pop %gs; pop %fs; pop %es; pop %ds; popa
#define TRAP0(n) .global vec#n; vec#n: \
pushl $(n<<8); \
pushl [=10=]; \
SAVE(); \
call generic(); \
RESTORE(); \
add , %esp; \
iret
#define TRAP1(n) .global vec#n; vec#n: \
movw $n, 2(%esp); \
pushl [=10=]; \
SAVE();\
call generic(); \
RESTORE(); \
add , %esp; \
iret
#define PTRAP(n) .global vec#n; vec#n: \
movw $n, 2(%esp);\
pushl [=10=]; SAVE();\
mov %cr3, %eax; \
mov %eax, (12*4)(%esp); \
call generic(); \
RESTORE(); \
add , %esp; \
iret
TRAP0(0); TRAP0(1); ... TRAP0(7);
TRAP1(8); TRAP0(9);
TRAP1(10); ...TRAP1(13);
PTRAP(14); TRAP1(15); ...; TRAP1(31);
/* interrupts: */
TRAP0(32); TRAP0(33); .... TRAP0(47)
所以你把vec0..vec47安装到idt[0]..[47];
那么您的通用处理程序看起来已经传递了一个结构:
struct kstk {
uint32_t seg[4];
uint32_t reg[8];
uint32_t cr3;
uint16_t errc, trap;
uint32_t ip, cs, fl, sp, ss;
};
void handler(struct kstk x);
ss,sp 是可选的。
(标准免责声明,我只是在消息中输入了这个,所以它可能需要一些调整)。
我一直在写一个业余爱好OS,我正在尝试在内核中进行interrupt/exception 处理。我在 ring 0 中,所以没有内部特权堆栈切换等。这些是我的例程:
#include <stdint.h>
#include "dadio.h"
#define MAX_INTERRUPTS 256
#define IDT_DESC_BIT16 0x06 //00000110
#define IDT_DESC_BIT32 0x0E //00001110
#define IDT_DESC_RING1 0x40 //01000000
#define IDT_DESC_RING2 0x20 //00100000
#define IDT_DESC_RING3 0x60 //01100000
#define IDT_DESC_PRESENT 0x80//10000000
//Structs used in this routine
typedef struct __attribute__ ((__packed__)) idtr {
uint16_t limit;
uint32_t base;
}idtr_t;
typedef struct __attribute__ ((__packed__)) gdtr {
uint16_t limit;
uint32_t base;
}gdtr_t;
typedef struct __attribute__ ((__packed__)) idt_descriptor {
uint16_t baseLo;
uint16_t sel;
uint8_t reserved;
uint8_t flags;
uint16_t baseHi;
}idt_descriptor_t;
typedef struct __attribute__((__packed__)) gdt_descriptor {
uint16_t limit;
uint16_t baseLo;
uint8_t baseMid;
uint16_t flags;
uint8_t baseHi;
} gdt_descriptor_t;
//External assembly functions
void init_pic();
void install_idt(idtr_t* address);
void enable_interrupts();
//Global variables in this routine
static idt_descriptor_t _idt[MAX_INTERRUPTS];
static idtr_t _idtr; //This will be the 6 byte base + limit
//Helper functions
static void install_ir(uint32_t index,uint16_t flags, uint16_t sel, uint32_t* handler_address);
static void default_handler();
void idt_init()
{
_idtr.base = (uint32_t)_idt;
_idtr.limit = (sizeof (idt_descriptor_t) * MAX_INTERRUPTS) -1 ;
for (int i=0;i<MAX_INTERRUPTS;i++)
{
_idt[i].baseLo = 0;
_idt[i].sel = 0;
_idt[i].reserved = 0;
_idt[i].flags = 0;
_idt[i].baseHi = 0;
}
for (int i=0;i<MAX_INTERRUPTS;i++)
install_ir(i,IDT_DESC_BIT32 | IDT_DESC_PRESENT, 0x08, (uint32_t*) default_handler);
init_pic();
install_idt(& _idtr);
enable_interrupts();
}
static void install_ir(uint32_t index,uint16_t flags, uint16_t sel, uint32_t* handler_address)
{
if (index >=MAX_INTERRUPTS) return;
_idt[index].baseLo = (uint32_t)handler_address & 0xffff;
_idt[index].baseHi = ((uint32_t)handler_address >> 16) & 0xffff;
_idt[index].reserved = 0;
_idt[index].flags = flags;
_idt[index].sel = sel;
}
static void default_handler()
{
monitor-puts("This is the default exception handler"); //This is a routine that prints messages on the screen... The gist is that it writes to 0xb8000 and so on...
for (;;);
}
汇编例程
init_pic:
mov al, 0x11 ;ICW 1 ;Expect IC4|single?|0|level?|init?|000
out 0x20,al
out 0xA0,al
mov al,0x20 ;Remapping the IRQs
out 0x21,al
mov al,0x28
out 0xA1,al
; Send ICW 3 to primary PIC
mov al, 0x4 ; 0x4 = 0100 Second bit (IR Line 2)
out 0x21, al ; write to data register of primary PIC
; Send ICW 3 to secondary PIC
mov al, 0x2 ; 010=> IR line 2
out 0xA1, al ; write to data register of secondary PIC
; Send ICW 4 - Set x86 mode --------------------------------
mov al, 1 ; bit 0 enables 80x86 mode
out 0x21, al
out 0xA1, al
; Zeroing out the data registers
mov al, 0
out 0x21, al
out 0xA1, al
ret
enable_interrupts:
sti
ret
最小内核为:
void kmain()
{
idt_init();
return;
}
如果我在 idt_init 函数 中注释掉 init_pic(),我会收到消息:This is the default异常处理程序,后跟 for(;;)。 我认为这是意料之中的,因为一旦我启用中断,定时器之类的东西就会发送一个 IRQ,并且由于它默认映射到(除以零?)异常,我得到了我定义的处理程序消息。
但是如果我取消对 init_pic() 的注释,我就不会收到消息。我知道 IRQ (0-15) 已被重新映射到中断向量 (32 - 47)。但是定时器中断仍然会触发,我应该得到消息。 (我认为,在我的案例中,所有 256 个可能的 interrupts/exceptions 都映射到相同的例程)。 我哪里做错了?
另外,还有一个小的后续问题。我知道有些异常会推送错误代码,而有些则不会。但是 iret 指令不能知道,对吧?那么程序员有责任手动添加到esp(弹出异常的错误代码确实会引发错误)然后进行iret吗?
我已经阅读了 80386 开发人员手册,据我了解。我哪里错了吗?
PS:尽管我的项目有更多代码,但我已尝试提供最少的代码。
一个关键位可能是 kmain returns?
时发生的情况后续问题很重要:你必须在 iret 之前清理堆栈。仅出于这个原因,将门指向 C 函数确实很麻烦。大多数人制作两个汇编存根:
#define SAVE() pusha; push %ds; push %es; push %fs; push %gs
#define RESTORE() pop %gs; pop %fs; pop %es; pop %ds; popa
#define TRAP0(n) .global vec#n; vec#n: \
pushl $(n<<8); \
pushl [=10=]; \
SAVE(); \
call generic(); \
RESTORE(); \
add , %esp; \
iret
#define TRAP1(n) .global vec#n; vec#n: \
movw $n, 2(%esp); \
pushl [=10=]; \
SAVE();\
call generic(); \
RESTORE(); \
add , %esp; \
iret
#define PTRAP(n) .global vec#n; vec#n: \
movw $n, 2(%esp);\
pushl [=10=]; SAVE();\
mov %cr3, %eax; \
mov %eax, (12*4)(%esp); \
call generic(); \
RESTORE(); \
add , %esp; \
iret
TRAP0(0); TRAP0(1); ... TRAP0(7);
TRAP1(8); TRAP0(9);
TRAP1(10); ...TRAP1(13);
PTRAP(14); TRAP1(15); ...; TRAP1(31);
/* interrupts: */
TRAP0(32); TRAP0(33); .... TRAP0(47)
所以你把vec0..vec47安装到idt[0]..[47]; 那么您的通用处理程序看起来已经传递了一个结构:
struct kstk {
uint32_t seg[4];
uint32_t reg[8];
uint32_t cr3;
uint16_t errc, trap;
uint32_t ip, cs, fl, sp, ss;
};
void handler(struct kstk x); ss,sp 是可选的。 (标准免责声明,我只是在消息中输入了这个,所以它可能需要一些调整)。