主要内容
扩充内核
一
为了方便控制,需要将esp,gdt等内容也放进内存。
将位于loader中的gdt复制给新的gdt,并更新gdt的信息
#include "type.h"
#include "const.h"
#include "protect.h"
PUBLIC void* memcpy(void* pDst, void* pSrc, int iSize);
PUBLIC void disp_str(char * pszInfo);
PUBLIC u8 gdt_ptr[6]; /* 0~15:Limit 16~47:Base */
PUBLIC DESCRIPTOR gdt[GDT_SIZE];
PUBLIC void cstart()
{
disp_str("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
"-----\"cstart\" begins-----\n");
/* 将 LOADER 中的 GDT 复制到新的 GDT 中 */
memcpy(&gdt, /* New GDT */
(void*)(*((u32*)(&gdt_ptr[2]))), /* Base of Old GDT */
*((u16*)(&gdt_ptr[0])) + 1 /* Limit of Old GDT */
);
/* gdt_ptr[6] 共 6 个字节:0~15:Limit 16~47:Base。用作 sgdt/lgdt 的参数。*/
u16* p_gdt_limit = (u16*)(&gdt_ptr[0]);
u32* p_gdt_base = (u32*)(&gdt_ptr[2]);
*p_gdt_limit = GDT_SIZE * sizeof(DESCRIPTOR) - 1;
*p_gdt_base = (u32)&gdt;
}
切换堆栈和gdt
; 把 esp 从 LOADER 挪到 KERNEL
mov esp, StackTop ; 堆栈在 bss 段中
sgdt [gdt_ptr] ; cstart() 中将会用到 gdt_ptr
call cstart ; 在此函数中改变了gdt_ptr,让它指向新的GDT
lgdt [gdt_ptr] ; 使用新的GDT
;lidt [idt_ptr]
jmp SELECTOR_KERNEL_CS:csinit
csinit: ; “这个跳转指令强制使用刚刚初始化的结构”——<<OS:D&I 2nd>> P90.
push 0
popfd ; Pop top of stack into EFLAGS
hlt
二
整理文件,完成归纳。
利用Makefile编译
简单的Makefile
# Entry point of Orange'S
# It must have the same value with 'KernelEntryPointPhyAddr' in load.inc!
ENTRYPOINT = 0x30400
# Offset of entry point in kernel file
# It depends on ENTRYPOINT
ENTRYOFFSET = 0x400
# Programs, flags, etc.
ASM = nasm
DASM = ndisasm
CC = gcc
LD = ld
ASMBFLAGS = -I boot/include/
ASMKFLAGS = -I include/ -f elf
CFLAGS = -I include/ -c -fno-builtin
LDFLAGS = -s -Ttext $(ENTRYPOINT)
DASMFLAGS = -u -o $(ENTRYPOINT) -e $(ENTRYOFFSET)
# This Program
ORANGESBOOT = boot/boot.bin boot/loader.bin
ORANGESKERNEL = kernel.bin
OBJS = kernel/kernel.o kernel/start.o lib/kliba.o lib/string.o
DASMOUTPUT = kernel.bin.asm
# All Phony Targets
.PHONY : everything final image clean realclean disasm all buildimg
# Default starting position
everything : $(ORANGESBOOT) $(ORANGESKERNEL)
all : realclean everything
final : all clean
image : final buildimg
clean :
rm -f $(OBJS)
realclean :
rm -f $(OBJS) $(ORANGESBOOT) $(ORANGESKERNEL)
disasm :
$(DASM) $(DASMFLAGS) $(ORANGESKERNEL) > $(DASMOUTPUT)
# We assume that "a.img" exists in current folder
buildimg :
dd if=boot/boot.bin of=a.img bs=512 count=1 conv=notrunc
sudo mount -o loop a.img /mnt/floppy/
sudo cp -fv boot/loader.bin /mnt/floppy/
sudo cp -fv kernel.bin /mnt/floppy
sudo umount /mnt/floppy
boot/boot.bin : boot/boot.asm boot/include/load.inc boot/include/fat12hdr.inc
$(ASM) $(ASMBFLAGS) -o $@ $<
boot/loader.bin : boot/loader.asm boot/include/load.inc \
boot/include/fat12hdr.inc boot/include/pm.inc
$(ASM) $(ASMBFLAGS) -o $@ $<
$(ORANGESKERNEL) : $(OBJS)
$(LD) $(LDFLAGS) -o $(ORANGESKERNEL) $(OBJS)
kernel/kernel.o : kernel/kernel.asm
$(ASM) $(ASMKFLAGS) -o $@ $<
kernel/start.o : kernel/start.c include/type.h include/const.h include/protect.h
$(CC) $(CFLAGS) -o $@ $<
lib/kliba.o : lib/kliba.asm
$(ASM) $(ASMKFLAGS) -o $@ $<
lib/string.o : lib/string.asm
$(ASM) $(ASMKFLAGS) -o $@ $<
Makefile的最重要语法就是
target : prerequistites
command
1.要想得到target,需要执行command
2.当prerequisites中有任何文件比target文件新时,command被执行
3.执行make,就会对有更新的文件进行编译,而不会对所有源文件都编译一遍
最后执行相应的make命令,就能将所需要的文件编译完成。
三
添加中断处理
设置8259A和建立IDT
初始化8259A
PUBLIC void init_8259A()
{
/* Master 8259, ICW1. */
out_byte(INT_M_CTL, 0x11);
/* Slave 8259, ICW1. */
out_byte(INT_S_CTL, 0x11);
/* Master 8259, ICW2. 设置 '主8259' 的中断入口地址为 0x20. */
out_byte(INT_M_CTLMASK, INT_VECTOR_IRQ0);
/* Slave 8259, ICW2. 设置 '从8259' 的中断入口地址为 0x28 */
out_byte(INT_S_CTLMASK, INT_VECTOR_IRQ8);
/* Master 8259, ICW3. IR2 对应 '从8259'. */
out_byte(INT_M_CTLMASK, 0x4);
/* Slave 8259, ICW3. 对应 '主8259' 的 IR2. */
out_byte(INT_S_CTLMASK, 0x2);
/* Master 8259, ICW4. */
out_byte(INT_M_CTLMASK, 0x1);
/* Slave 8259, ICW4. */
out_byte(INT_S_CTLMASK, 0x1);
/* Master 8259, OCW1. */
out_byte(INT_M_CTLMASK, 0xFF);
/* Slave 8259, OCW1. */
out_byte(INT_S_CTLMASK, 0xFF);
}
对端口的写操作
out_byte:
mov edx, [esp + 4] ; port
mov al, [esp + 4 + 4] ; value
out dx, al
nop ; 一点延迟
nop
ret
初始化IDT
u16* p_idt_limit = (u16*)(&idt_ptr[0]);
u32* p_idt_base = (u32*)(&idt_ptr[2]);
*p_idt_limit = IDT_SIZE * sizeof(GATE) - 1;
*p_idt_base = (u32)&idt;
GATE门定义
typedef struct s_gate
{
u16 offset_low; /* Offset Low */
u16 selector; /* Selector */
u8 dcount; /* 该字段只在调用门描述符中有效。如果在利用
调用门调用子程序时引起特权级的转换和堆栈
的改变,需要将外层堆栈中的参数复制到内层
堆栈。该双字计数字段就是用于说明这种情况
发生时,要复制的双字参数的数量。*/
u8 attr; /* P(1) DPL(2) DT(1) TYPE(4) */
u16 offset_high; /* Offset High */
}GATE;
在这里我们对异常处理的思想是:如果有错误码,直接把向量号压栈,再执行一个异常处理函数,如果没有错误码,先往栈中压入0xFFFFFFFF,再把向量号压栈并执行一个异常处理函数
; 中断和异常 -- 异常
divide_error:
push 0xFFFFFFFF ; no err code
push 0 ; vector_no = 0
jmp exception
single_step_exception:
push 0xFFFFFFFF ; no err code
push 1 ; vector_no = 1
jmp exception
nmi:
push 0xFFFFFFFF ; no err code
push 2 ; vector_no = 2
jmp exception
breakpoint_exception:
push 0xFFFFFFFF ; no err code
push 3 ; vector_no = 3
jmp exception
overflow:
push 0xFFFFFFFF ; no err code
push 4 ; vector_no = 4
jmp exception
bounds_check:
push 0xFFFFFFFF ; no err code
push 5 ; vector_no = 5
jmp exception
inval_opcode:
push 0xFFFFFFFF ; no err code
push 6 ; vector_no = 6
jmp exception
copr_not_available:
push 0xFFFFFFFF ; no err code
push 7 ; vector_no = 7
jmp exception
double_fault:
push 8 ; vector_no = 8
jmp exception
copr_seg_overrun:
push 0xFFFFFFFF ; no err code
push 9 ; vector_no = 9
jmp exception
inval_tss:
push 10 ; vector_no = A
jmp exception
segment_not_present:
push 11 ; vector_no = B
jmp exception
stack_exception:
push 12 ; vector_no = C
jmp exception
general_protection:
push 13 ; vector_no = D
jmp exception
page_fault:
push 14 ; vector_no = E
jmp exception
copr_error:
push 0xFFFFFFFF ; no err code
push 16 ; vector_no = 10h
jmp exception
exception:
call exception_handler
add esp, 4*2 ; 让栈顶指向 EIP,堆栈中从顶向下依次是:EIP、CS、EFLAGS
hlt
PUBLIC void exception_handler(int vec_no,int err_code,int eip,int cs,int eflags)
{
int i;
int text_color = 0x74; /* 灰底红字 */
char * err_msg[] = {"#DE Divide Error",
"#DB RESERVED",
"-- NMI Interrupt",
"#BP Breakpoint",
"#OF Overflow",
"#BR BOUND Range Exceeded",
"#UD Invalid Opcode (Undefined Opcode)",
"#NM Device Not Available (No Math Coprocessor)",
"#DF Double Fault",
" Coprocessor Segment Overrun (reserved)",
"#TS Invalid TSS",
"#NP Segment Not Present",
"#SS Stack-Segment Fault",
"#GP General Protection",
"#PF Page Fault",
"-- (Intel reserved. Do not use.)",
"#MF x87 FPU Floating-Point Error (Math Fault)",
"#AC Alignment Check",
"#MC Machine Check",
"#XF SIMD Floating-Point Exception"
};
/* 通过打印空格的方式清空屏幕的前五行,并把 disp_pos 清零 */
disp_pos = 0;
for(i=0;i<80*5;i++){
disp_str(" ");
}
disp_pos = 0;
disp_color_str("Exception! --> ", text_color);
disp_color_str(err_msg[vec_no], text_color);
disp_color_str("\n\n", text_color);
disp_color_str("EFLAGS:", text_color);
disp_int(eflags);
disp_color_str("CS:", text_color);
disp_int(cs);
disp_color_str("EIP:", text_color);
disp_int(eip);
if(err_code != 0xFFFFFFFF){
disp_color_str("Error code:", text_color);
disp_int(err_code);
}
}
设置IDT
PUBLIC void init_prot()
{
init_8259A();
// 全部初始化成中断门(没有陷阱门)
init_idt_desc(INT_VECTOR_DIVIDE, DA_386IGate,
divide_error, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_DEBUG, DA_386IGate,
single_step_exception, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_NMI, DA_386IGate,
nmi, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_BREAKPOINT, DA_386IGate,
breakpoint_exception, PRIVILEGE_USER);
init_idt_desc(INT_VECTOR_OVERFLOW, DA_386IGate,
overflow, PRIVILEGE_USER);
init_idt_desc(INT_VECTOR_BOUNDS, DA_386IGate,
bounds_check, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_INVAL_OP, DA_386IGate,
inval_opcode, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_COPROC_NOT, DA_386IGate,
copr_not_available, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_DOUBLE_FAULT, DA_386IGate,
double_fault, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_COPROC_SEG, DA_386IGate,
copr_seg_overrun, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_INVAL_TSS, DA_386IGate,
inval_tss, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_SEG_NOT, DA_386IGate,
segment_not_present, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_STACK_FAULT, DA_386IGate,
stack_exception, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_PROTECTION, DA_386IGate,
general_protection, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_PAGE_FAULT, DA_386IGate,
page_fault, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_COPROC_ERR, DA_386IGate,
copr_error, PRIVILEGE_KRNL);
}
四
开始使用8259A
因为8259A可以挂接15个外部设备,所以理应有15个中断处理程序。
导出
extern spurious_irq
global hwint00
global hwint01
global hwint02
global hwint03
global hwint04
global hwint05
global hwint06
global hwint07
global hwint08
global hwint09
global hwint10
global hwint11
global hwint12
global hwint13
global hwint14
global hwint15
对于每个中断都设置相同函数,打印出中断号
PUBLIC void spurious_irq(int irq)
{
disp_str("spurious_irq: ");
disp_int(irq);
disp_str("\n");
}
添加在IDT中
init_idt_desc(INT_VECTOR_IRQ0 + 0, DA_386IGate,
hwint00, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ0 + 1, DA_386IGate,
hwint01, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ0 + 2, DA_386IGate,
hwint02, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ0 + 3, DA_386IGate,
hwint03, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ0 + 4, DA_386IGate,
hwint04, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ0 + 5, DA_386IGate,
hwint05, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ0 + 6, DA_386IGate,
hwint06, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ0 + 7, DA_386IGate,
hwint07, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ8 + 0, DA_386IGate,
hwint08, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ8 + 1, DA_386IGate,
hwint09, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ8 + 2, DA_386IGate,
hwint10, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ8 + 3, DA_386IGate,
hwint11, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ8 + 4, DA_386IGate,
hwint12, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ8 + 5, DA_386IGate,
hwint13, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ8 + 6, DA_386IGate,
hwint14, PRIVILEGE_KRNL);
init_idt_desc(INT_VECTOR_IRQ8 + 7, DA_386IGate,
hwint15, PRIVILEGE_KRNL);
}
打开屏蔽的中断
/* Master 8259, OCW1. */
out_byte(INT_M_CTLMASK, 0xFD);
/* Slave 8259, OCW1. */
out_byte(INT_S_CTLMASK, 0xFF);
改变IF位
csinit:
sti
hlt
完成,能出现键盘中断。