当前 Linux 0.11 代码树是 Linus 基于 Intel 的 386 兼容机编写的。其 CPU 为80386,想一下,假如你是 Linus,你要为你的操作系统来适配 386 兼容机,现在你要完成异常处理部分,你应该做什么?
答案毋庸置疑吧,所以具体关于中断和异常的处理我们来看看 CPU 手册中是如何描述的。那么,我们先按照一个正常开发流程去模拟当前 Linus 是如何开发异常处理模块的。
80386
在 i386 的数据手册中,有一章节描述了中断和异常。
原文是这么描述的:
The 80386 has two mechanisms for interrupting program execution:
Exceptions are synchronous events that are the responses of the CPU to certain conditions detected during the execution of an instruction.
Interrupts are asynchronous events.
Interrupts and exceptions are alike in that both cause the processor to temporarily suspend its present program execution in order to execute a program of higher priority.
The major distinction between these two kinds of interrupts is their origin. An exception is always reproducible by re-executing with the program and data that caused the exception, whereas an interrupt is generally independent of the currently executing program.
Application programmers are not normally concerned with servicing interrupts. More information on interrupts for systems programmers may be found in Chapter 9. Certain exceptions, however,are of interest to applications programmers,and many operating systems give applications programs the opportunity to service these exceptions. However,the operating system itself defines the interface between the applications programs and the exception mechanism of the 80386.
有了访问地址,异常向量偏移,那么我们就可以设计代码框架了。我想当时 Linus 应该是这么想的:我一直在研究 Unix 操作系统设计,所以我是否也可以将信号用到 Linux 操作系统中呢,可以把每个异常对应一个信号,用来做全局通知链,这样一些无需 CPU reset 解决的,可以做一下告警,告知开发应用程序或者驱动的程序员该如何规范使用当前芯片。好吧,我要开干了!!!
Linus 编写的异常代码
/*
* linux/kernel/traps.c
*
* (C) 1991 Linus Torvalds
*/
/*
* 'Traps.c' handles hardware traps and faults after we have saved some
* state in 'asm.s'. Currently mostly a debugging-aid, will be extended
* to mainly kill the offending process (probably by giving it a signal,
* but possibly by killing it outright if necessary).
*/
/* 我们保存了一些硬件状态在'asm.s'中,然后我们使用'Traps.c'用来处理陷进和故障。当前主要用于调试,
* 后续我们将扩展使其可以杀死一些令人厌恶的进程(或许可以给它一个信号,但是尽量还是将其杀死)。
*/
#include <string.h>
#include <linux/head.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <asm/io.h>
/* 以下语句定义了三个嵌入式汇编宏语句函数,有关嵌入式汇编的基本语法见本程序列表后的说明。
* 用圆括号括住的组合语句(花括号中的语句)可以作为表达式使用,其中最后的__res是其输出值。
* 第23行定义了一个寄存器变量__res,该变量将被保存在一个寄存器中,以便于快速访问和操作。
* 如果想指定寄存器(例如eax),那么我们可以把该句写成register char __res asm("ax");
* 取段seg中地址addr处的一个字节。
* 参数:seg - 段选择符;addr - 段内指定地址。
* 输出:%0 - eax (__res);输入:%1 - eax (seg);%2 - 内存地址 (*(addr))
*/
#define get_seg_byte(seg,addr) ({ \
register char __res; \
__asm__("push %%fs;mov %%ax,%%fs;movb %%fs:%2,%%al;pop %%fs" \
:"=a" (__res):"0" (seg),"m" (*(addr))); \
__res;})
/* 取段seg中地址addr处的一个长字(4字节)。
* 参数:seg - 段选择符;addr - 段内指定地址。
* 输出:%0 - eax (__res);输入:%1 - eax (seg);%2 - 内存地址 (*(addr))
*/
#define get_seg_long(seg,addr) ({ \
register unsigned long __res; \
__asm__("push %%fs;mov %%ax,%%fs;movl %%fs:%2,%%eax;pop %%fs" \
:"=a" (__res):"0" (seg),"m" (*(addr))); \
__res;})
/* 取fs段寄存器的值(选择符)
* 输出:%0 - eax (__res
*/
#define _fs() ({ \
register unsigned short __res; \
__asm__("mov %%fs,%%ax":"=a" (__res):); \
__res;})
/* 下面是异常向量表对应的函数原型 */
int do_exit(long code);
void page_exception(void);
void divide_error(void);
void debug(void);
void nmi(void);
void int3(void);
void overflow(void);
void bounds(void);
void invalid_op(void);
void device_not_available(void);
void double_fault(void);
void coprocessor_segment_overrun(void);
void invalid_TSS(void);
void segment_not_present(void);
void stack_segment(void);
void general_protection(void);
void page_fault(void);
void coprocessor_error(void);
void reserved(void);
void parallel_interrupt(void);
void irq13(void);
/* die 函数主要用于打印出错的中断的名称、出错号、调用程序的EIP、EFLAGS、ESP、fs
* 段寄存器值以及段的基址、段的长度、进程号pid、任务号、10字节指令码。
* 如果堆栈在用户数据段,则还打印16字节的堆栈内容。
*/
static void die(char * str,long esp_ptr,long nr)
{
long * esp = (long *) esp_ptr;
int i;
printk("%s: %04x\n\r",str,nr&0xffff);
printk("EIP:\t%04x:%p\nEFLAGS:\t%p\nESP:\t%04x:%p\n",
esp[1],esp[0],esp[2],esp[4],esp[3]);
printk("fs: %04x\n",_fs());
printk("base: %p, limit: %p\n",get_base(current->ldt[1]),get_limit(0x17));
if (esp[4] == 0x17) {
printk("Stack: ");
for (i=0;i<4;i++)
printk("%p ",get_seg_long(0x17,i+(long *)esp[3]));
printk("\n");
}
str(i);
printk("Pid: %d, process nr: %d\n\r",current->pid,0xffff & i);
for(i=0;i<10;i++)
printk("%02x ",0xff & get_seg_byte(esp[1],(i+(char *)esp[0])));
printk("\n\r");
do_exit(11); /* play segment exception */
}
/* 以下这些以 do_ 开头的函数是 asm.s 中对应中断处理程序调用的C函数。
void do_double_fault(long esp, long error_code)
{
die("double fault",esp,error_code);
}
void do_general_protection(long esp, long error_code)
{
die("general protection",esp,error_code);
}
void do_divide_error(long esp, long error_code)
{
die("divide error",esp,error_code);
}
void do_int3(long * esp, long error_code,
long fs,long es,long ds,
long ebp,long esi,long edi,
long edx,long ecx,long ebx,long eax)
{
int tr;
__asm__("str %%ax":"=a" (tr):"0" (0));
printk("eax\t\tebx\t\tecx\t\tedx\n\r%8x\t%8x\t%8x\t%8x\n\r",
eax,ebx,ecx,edx);
printk("esi\t\tedi\t\tebp\t\tesp\n\r%8x\t%8x\t%8x\t%8x\n\r",
esi,edi,ebp,(long) esp);
printk("\n\rds\tes\tfs\ttr\n\r%4x\t%4x\t%4x\t%4x\n\r",
ds,es,fs,tr);
printk("EIP: %8x CS: %4x EFLAGS: %8x\n\r",esp[0],esp[1],esp[2]);
}
void do_nmi(long esp, long error_code)
{
die("nmi",esp,error_code);
}
void do_debug(long esp, long error_code)
{
die("debug",esp,error_code);
}
void do_overflow(long esp, long error_code)
{
die("overflow",esp,error_code);
}
void do_bounds(long esp, long error_code)
{
die("bounds",esp,error_code);
}
void do_invalid_op(long esp, long error_code)
{
die("invalid operand",esp,error_code);
}
void do_device_not_available(long esp, long error_code)
{
die("device not available",esp,error_code);
}
void do_coprocessor_segment_overrun(long esp, long error_code)
{
die("coprocessor segment overrun",esp,error_code);
}
void do_invalid_TSS(long esp,long error_code)
{
die("invalid TSS",esp,error_code);
}
void do_segment_not_present(long esp,long error_code)
{
die("segment not present",esp,error_code);
}
void do_stack_segment(long esp,long error_code)
{
die("stack segment",esp,error_code);
}
void do_coprocessor_error(long esp, long error_code)
{
if (last_task_used_math != current)
return;
die("coprocessor error",esp,error_code);
}
void do_reserved(long esp, long error_code)
{
die("reserved (15,17-47) error",esp,error_code);
}
/* trap_init 函数是异常(陷阱)中断程序初始化子程序,主要设置它们的中断向量表。
* set_trap_gate() 与 set_system_gate() 都使用了中断描述符表IDT中的陷阱门(Trap Gate)
* 它们之间的主要区别在于前者设置的特权级为0,后者是3。因此断点陷阱中断int3、溢出中断
* overflow 和边界出错中断 bounds 可以由任何程序调用。这两个函数均是嵌入式汇编宏程序。
* 更多可以查看 include/asm/system.h;第36行以及39行。
*/
void trap_init(void)
{
int i;
set_trap_gate(0,÷_error);
set_trap_gate(1,&debug);
set_trap_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,&reserved);
set_trap_gate(16,&coprocessor_error);
for (i=17;i<48;i++)
set_trap_gate(i,&reserved);
set_trap_gate(45,&irq13);
outb_p(inb_p(0x21)&0xfb,0x21);
outb(inb_p(0xA1)&0xdf,0xA1);
set_trap_gate(39,¶llel_interrupt);
}