中断分析

MINIX3 中断机制源码分析

下面我们来探讨下 MINIX 中断处理函数的具体处理流程:

由前面的章节知道,MINIX 的架构是标准的微内核结构,除了时钟中断处理程
序可以直接在内核态进行,其他的都只能从用户态,用户态像内核态发送消息。
假设我们是一个内核设计者,怎么能够将这种机制实现呢?我们会在系统任务里
设置一个中断注册调用,通过那个调用,我们能够将用户需要的中断处理函数挂

15

到相应的地方并且以后能够被用户识别。

现在继续往下面分析:我们现在又 16 根通用的中断地址,但是这是不够的,对 于一个用户拥有非常多的应用硬件而言,那些中断号很难满足。我们所能够想象 到得就是共享,想办法让多个中断处理程序共享一个中断号。怎么来实现这个共 享中断号呢?  MINIX 用了一种比较简单但是非常高效的方法来实现这个中断 共享机制。我们先来看一个数据结构:

typedef struct irq_hook{

struct irq_hook*next;  /* next hookin chain  */指向下一个链

int (*handler)(struct irq_hook*); /* interrupt handler */中断处理函数 首地址

int irq;  /* IRQ vector number  */中断向量号 int id;  /* id of this hook*/hook的id

int proc_nr;  /* NONE if not in use  */标志被哪个进程使用

irq_id_t notify_id;  /* id to return on interrupt  */用于中断返回的id irq_policy_t policy;  /* bit maskfor policy  */

} irq_hook_t;

这个数据结构就可以实现中断的共享。在 glo.h 中,就有如下 2 个变量,可以看
出,这 2 个变量是 2 个静态数组,数组元素要么是 irq_hook_t,要么是 irq_hook_t
指针。

/* Interrupt related variables.  */

EXTERN irq_hook_t irq_hooks[NR_IRQ_HOOKS]; /* hooks for general use

*/

EXTERN irq_hook_t *irq_handlers[NR_IRQ_VECTORS];/* list of IRQ handlers
*/

Irq_hanlders 里面的每一个数据结构就是用来支持这个中断共享功能。在后面可
以详细看出来这个功能。 Irq_hooks 这个数值就是供通用钩子使用的。
看完了中断共享,现在来看看怎么让内核知道或者来初始化一个中断程序!?
MINIX3 是微内核结构,也就是用户的设备驱动程序应该在用户态完成,驱动程
序和中断是紧密相连的,一般一个请求 I/O 操作完成后,设备就会产生一个中断。
但是怎么能够让内核知道呢?MINIX3  关于这点提出了一个观点:就是注册中断
号,通过发送一个消息给内核让内核来完成这个动作。这里又是通过消息机制来
完成

之后想象下,中断返回,中断返回会涉及到 TSS,进程栈,内核栈,中断返回到 用户进程,这里对于绝大部分多进程操作系统而言,可以说是一个绝佳的机会, 可以重新调用另外一个进程。MINIX3 也是抓住这个机会进行重新调度进程。具 体的调度算法会在 MINIX 进程调度有详细的分析

对于上面,我们所讨论的基本上是属于硬件中断,Intel386 还提供一个软中断模 式,其实就是 trap 模式或者是系统调用模式,在 minix3 中,才用微内核结构, trap 模式就是利用消息传递机制来完成,trap 所经过的就是调用门,在返回时, 同样可以重新调用另外一个进程。

涉及到中断相关问题的主要源文件

I8259 是关于注册和中断处理的源文件,exception 是关于异常的处理源文件,事
实上 clock.c 也实际涉及到相关中断。但是会单独在时钟处理机制来实现。最后

16

中断最核心的文件其实是 MPX386.s,这是一个汇编程序构成的源文件。在这里 我会对每一个源文件一一做较为详细的分析。

从执行流程来看,设想一个进程 P,我们准备在键盘敲一段文字,敲击键盘时, 就会产生中断,或者我们进程 P 有一段代码想获取当前的时间是多少,P 就会产 生系统调用,内核会把相关的内容返回给它,这个动作就是系统调用或者嵌入内 核中。以上 2 个过程就是我们需要一一详细进入 MINIX3 内核中详细的分析: 先看敲击键盘,当用户敲击键盘时,(为简单起见,我们不会考虑相关的 TTY 过 程),键盘将产生一个中断,这个中断会被 CPU 发现并嵌入内核中的相关程序, 我们先来分析这个过程和应该调用的相关函数:

敲击键盘,产生中断,由于是进程是用户状态,加之现在是保护模式,所以硬件 会自动涉及以下几个寄存器 TS,TS 会自动将加载 TSS 的 sp0 ss0 放到 sp 和 ss 中,同时暂把 sp 和 ss 存期来,之后将 SP 和 SS 压入新栈中,同时将 CS 和 IP 压 入栈中,通过调用门把 CS 和 IP 放入相应的寄存器中,IP 其实就是中断向量处 理地址,hwint_master(1),之后这里的内容就是这样处理的:

hwint_master(irq)---------àsave----------------à_intr_handle---------à_restart;

先看执行执行过程图:

17

现在深入真正源码一一分析:

先看/kernel/mpx386.s  与中断有关的代码:事实上这个是入口,hwint00-07  和 hwint08-15 其实本质上是一样的,但是它分了主芯片和从芯片,是为了 8259 中 断编程器的 2 级芯片而设立的。先往下面看

!*=========================================================== ================*

!* interrupt handlers *

!* interrupt handlers for 386 32-bit protected mode *

18

!*=========================================================== ================*

!*=========================================================== ================*

!* hwint00 - 07 *

!*=========================================================== ================*

! Note this is a macro, it just looks like a subroutine.

!下面这段代码不是一个例程,(这点要非常的注意),事实上仅仅是一个宏,为 了就是提高

!运行效率

!要理解这段代码需要理解 82539A 的结构,其分为主和从,这里是处理主,下面 是处理从,

!其实所要完成的操作都相同,sp 和 ss 是 TSS 的 sp0 和 ss0 里的值,注意此时 是该进程栈

!的值,(应该说是内核的栈值),可是经过第一个调用,既 call save 之后,栈的 指向就换成

!了内核栈,这个栈是内核公共栈。希望读者跟着我的思路往后走,这段比较复 杂,特别是

!栈的变化,我们现在进入 save 例程中(一定要跟着来) #define hwint_master(irq) \

call save /* save interrupted process state */;\

!我们从 save 中返回出来,我们这里发生了几个变化,eax 存储了进程栈的栈地
址,之后现在的 sp 是内核栈栈地址。同时内核栈压入了_restart 或者_restart1 地
址。

push (_irq_handlers+4*irq) /* irq_handlers[irq] */;\

!这里的语法在前面看过,不在说,把 irq 作为参数值传入 intr_handle 函数,这 个函数

!是处理整个中断机制最为关键的一个函数 我们现在不往里分析,我们现在只 要知道个

!函数就是中断处理函数。完成了相关的中断处理。

call _intr_handle  /* intr_handle(irq_handlers[irq]) */;\

!这个 pop ecx 要注意,由于_intr_handle 其实一个 C 语言函数,所以它的参数应 该被 pop 掉,也就是将调用号 irq POP 到 ECX 寄存器中。

pop ecx ;\

!判断 irq_actids[irq]是否为 0,也就是判断 interrupt 是否还是活动的,既判断这个 中断向量号

!是完成。

cmp (_irq_actids+4*irq), 0 /* interrupt still active? */;\

!这里的_irq_actids 事实上是一个记录 irq 号是否完成的例子,为 0 表示完成

jz 0f

!如果完成,则跳到 0,如果没有完成,则执行下面的

!下面的主要是禁止这个中断向量号 irq ;\

19

inb INT_CTLMASK /* get current mask */ ;\

orb al, [1<<irq] /* mask irq */ ;\

outb INT_CTLMASK /* disable the irq */;\

!可以重启这个中断向量号 irq

0:   movb   al, END_OF_INT ;\

outb INT_CTL /* reenable master 8259 */;\

ret /* restart (another) process */

!!这里的 ret 非常重要,其实他就是一个重新启动另外一个进程的标志,每次进 入来,都

!是由外进入内部,经过 ret 之后,就是从内核跳转到外部应用程序,在后面会详 细讨论。

!现在我们来分析这个 ret,着重分析,ret 在 intel 的默认操作是 pop ip,也就是将栈 顶元素 pop

!到 IP 地址中,这点可以说非常的巧妙,你可以看看,前面我们知道有一个栈顶 元素是_restart

!或者_restart1 函数首地址,也就是在此之后我们又进入 restart 或者 restart1 执行, 真正意义

!上的重新调度进程是在那个函数中。我们现在进入那个函数进行分析,读者请 跟我的步伐

!进入 restart 函数

!下面都是函数的入口,就是针对上面的宏,其实你们可以看到,在门描述符中, 都会有

hwint ***,下面就是定义这些函数的首地址。是一种语法的模式。
! Each of these entry points is an expansion of the hwint_master macro

.align 16

_hwint00: ! Interrupt routine for irq 0 (the clock).

hwint_master(0)

.align 16

_hwint01: ! Interrupt routine for irq 1 (keyboard)

hwint_master(1)

.align 16

_hwint02: ! Interrupt routine for irq 2 (cascade!)

hwint_master(2)

.align 16

_hwint03: ! Interrupt routine for irq 3 (second serial)

hwint_master(3)

.align 16

_hwint04: ! Interrupt routine for irq 4 (first serial)

hwint_master(4)

20

.align 16

_hwint05: ! Interrupt routine for irq 5 (XT winchester)

hwint_master(5)

.align 16

_hwint06: ! Interrupt routine for irq 6 (floppy)

hwint_master(6)

.align 16

_hwint07: ! Interrupt routine for irq 7 (printer)

hwint_master(7)

!*=========================================================== ================*

!* hwint08 - 15 这个就是从芯片中断,在这里基本可以忽略

*

!*=========================================================== ================*

! Note this is a macro, it just looks like a subroutine.

#define hwint_slave(irq) \

call save /* save interrupted process state */;\

push (_irq_handlers+4*irq) /* irq_handlers[irq] */;\

call _intr_handle /* intr_handle(irq_handlers[irq]) */;\

pop ecx ;\

cmp (_irq_actids+4*irq), 0 /* interrupt still active? */;\

jz 0f ;\

inb INT2_CTLMASK ;\

orb al, [1<<[irq-8]] ;\

outb INT2_CTLMASK /* disable the irq */;\

0:   movb   al, END_OF_INT ;\

outb INT_CTL /* reenable master 8259 */;\

outb INT2_CTL /* reenable slave 8259 */;\

ret /* restart (another) process */

! Each of these entry points is an expansion of the hwint_slave macro

.align 16

_hwint08: ! Interrupt routine for irq 8 (realtime clock)

hwint_slave(8)

.align 16

_hwint09: ! Interrupt routine for irq 9 (irq 2 redirected)

hwint_slave(9)

21

.align 16

_hwint10: ! Interrupt routine for irq 10

hwint_slave(10)

.align 16

_hwint11: ! Interrupt routine for irq 11

hwint_slave(11)

.align 16

_hwint12: ! Interrupt routine for irq 12

hwint_slave(12)

.align 16

_hwint13: ! Interrupt routine for irq 13 (FPU exception)

hwint_slave(13)

.align 16

_hwint14: ! Interrupt routine for irq 14 (AT winchester)

hwint_slave(14)

.align 16

_hwint15: ! Interrupt routine for irq 15

hwint_slave(15)

!*===========================================================

================*

!* save 我们从上面的 hwint_handler(irq)进入这个 save,现在进

入内部分析 *

!*=========================================================== ================*

! Save for protected mode.

! This is much simpler than for 8086 mode, because the stack already points

! into the process table, or has already been switched to the kernel stack.

!这个例程也是非常重要,要好好理解一番,这个例程是用于保护模式,当然在 整个执行过

!程中,都是保护模式,感觉这个话贼其多余!当然在进入内核时,此时堆栈指 针已经是指

!进程表的栈结构,事实上 在 MINIX 中,每个进程都定义一个栈帧(其实就是 一个栈,说

!的太玄乎了!),这里指向的就是每个进程的栈结构,下面都是一系列的保存操
作。

.align 16

save:

cld ! set direction flag to a known value

22

pushad ! save "general" registers

o16     push    ds ! save ds

o16     push    es ! save es

o16     push    fs ! save fs

o16 push    gs ! save gs

!!上面一系列动作都是将寄存器的内容保存在进程栈帧中。

mov    dx, ss ! ss is kernel data segment

mov    ds, dx ! load rest of kernel segments

mov    es, dx ! kernel does not use fs, gs

!将 DS 和 ES 设置成内核数据段

mov    eax, esp ! prepare to return 这里是把 ESP 保存在 EAX 中,供以后返 回时使用,这

!里 esp 的值其实就是被中断的进程的栈指针。

incb (_k_reenter) ! from -1 if not reentering,这个_k_reenter 值代表的嵌套数, ! 如果是-1,就代表是没有嵌套,这个值主要是在后面的函数_restart 做文章。
jnz set_restart1 ! stack is already kernel stack

!,要非常的注意了,这里准备开始换栈了,准备把内

核栈和进程栈帧换掉,k_stkop 代表的是内核栈,这点要非常的注意,内核栈和 各个进程

!的栈帧是不一样的概念,但是 2 者的操作都是由内核完成。

!经过上面一个语句,栈由进程栈帧变化到内核栈,这里把_restart 压入内核栈 中,在后面

!会看到,这是非常巧妙的做法。暂且在这里留下悬念,到了 hwint_handler()讲 解完,自然

!就会明白。

push _restart ! build return address for int handler

xor ebp, ebp ! for stacktrace

jmp RETADR-P_STACKBASE(eax)

!这里是返回 save 调用原地址里。我继续回到前一个宏中-----hwint_handler(irq)

.align 4

set_restart1:

push    restart1!如果是嵌套中断,则将 restart1 压入内核中。之后返回 save 调用地址中,之后我们返回 hwint_handler(irq)分析

jmp RETADR-P_STACKBASE(eax)

!*=========================================================== ================*

!* restart    我们从 hwint_inhandler(irq)进入到这个函数,我们现在

来着重分析这个函数 !在分析之前,我们同样要搞清楚栈和相关寄存器的内

容,首先栈还是内核栈

之后 CS 和 IP 同样是指向这个函数的首地址,现在我们进入函数内部分析

*

!*=========================================================== ================*

_restart:

23

! Restart the current process or the next process if it is set.

!!这里非常的重要,因为这里主要是用于进程的调度。

cmp (_next_ptr), 0 ! see if another process is scheduled,这里的_next_ptr

表示在运行 restart 之后指向的进程地址。其实也就是

jz 0f

mov     eax, (_next_ptr)  !如果不是为 NULL,则执行流就从这里开始

!将_next_ptr 的值存入 eax,之后将 eax 的值设置存入_proc_ptr 内存地址 中,pro_ptr 是指

!向当前进程指针,这些指针都是已经设定好的,这个指针就就是用于下次 返回所运

!行的指针

mov (_proc_ptr), eax ! schedule new process

mov (_next_ptr), 0! 这里为啥把_next_ptr 设置成为 NULL,主要原因是标

志下次在执行

!这个 restart 例程时是否有重新设置上面那 2 个指针。如果没有就直接进入 0 标志,这个

!就是从最高代码效率来考虑

0:   mov  esp, (_proc_ptr)  ! will assume P_STACKBASE == 0

!这个需要注意,在后面进程和内存管理可以看到,这里的进程首地址就是该 进程的栈帧

!地址,这是经过精心安排的结果 也就是 esp 指向我们即将运行的进程的栈地址。

lldt P_LDT_SEL(esp) ! enable process‘ segment descriptors

!把该进程的段描述符装入到相应的寄存器中

lea  eax, P_STACKTOP(esp)  ! arrange for next interrupt

!将 esp 的栈头指针存入 eax,之后再_tss+TSS3_S_SP0 里,这需要参考 Intel TSS 结构,这个

!值其实就是状态为 0 的栈指针,这样做是让下次中断时可以直接调用将这个栈 地址放置到

!esp 寄存器中。

mov (_tss+TSS3_S_SP0), eax ! to save state in process table

restart1:

!下面这段代码是说明 如果是在内核中嵌套另一个过程,不需要设置上面的各
个指

!针,只要直接返回即可

decb (_k_reenter)

o16     pop gs
o16     pop fs
o16     pop es

o16     pop ds
popad

add esp, 4 ! skip return adr

iretd ! continue process

24

上面我们分析了整个执行的内核过程,现在我们尝试从另外一个角度来分析中断 机制-----执行中断处理程序。这个中断处理程序基本上在内核中 i8259.c 中。当 然还有一些系统调用。我们先仔细的分析 i8259.c i8259 主要包 put_irq_handler, rm_irq_handler intr_handle,intr_init.既然有执行中断处理程序,那么首先肯定要向 内核注册中断处理程序,同时也应该提供删除一个中断处理程序。I8259.c 就是 基于上面的想法而设定的。

/* This file contains routines for initializing the 8259 interrupt controller: *  put_irq_handler: register an interrupt handler

*  rm_irq_handler: deregister an interrupt handler
*  intr_handle: handle a hardware interrupt
*  intr_init: initialize the interrupt controller(s)
*/

//这个文件就是初始 8259A 中断处理器 当然还包括处理中断一些关键功能 #include "kernel.h"

#include "proc.h"

#include <minix/com.h>

//用于设置 8259A 的一些宏定义,便于编程,详细参数含义需要看 INTEL386 的 具体含义。

#define ICW1_AT

#define ICW1_PC

#define ICW1_PS

#define ICW4_AT_SLAVE
*/

0x11 /* edge triggered, cascade, need ICW4 */

0x13 /* edge triggered, no cascade, need ICW4 */ 0x19 /* level triggered, cascade, need ICW4 */
0x01  /* not SFNM, not buffered, normal EOI, 8086

#define ICW4_AT_MASTER

*/

#define ICW4_PC_SLAVE
#define ICW4_PC_MASTER

#if _WORD_SIZE == 2

0x05 /* not SFNM, not buffered, normal EOI, 8086

0x09 /* not SFNM, buffered, normal EOI, 8086 */

0x0D  /* not SFNM, buffered, normal EOI, 8086 */

typedef _PROTOTYPE( void (*vecaddr_t), (void) );

FORWARD _PROTOTYPE( void set_vec, (int vec_nr, vecaddr_t addr) );

PRIVATE vecaddr_t int_vec[] = {

int00, int01, int02, int03, int04, int05, int06, int07,

};

//中断向量表入口

PRIVATE vecaddr_t irq_vec[] = {

hwint00, hwint01, hwint02, hwint03, hwint04, hwint05, hwint06, hwint07,
hwint08, hwint09, hwint10, hwint11, hwint12, hwint13, hwint14, hwint15,
};

#else

#define set_vec(nr, addr)  ((void)0)

25

#endif

/*=========================================================== ================*

* intr_init

*这个例程就是用于初始 8259 芯片

*============================================================ ===============*/

PUBLIC void intr_init(mine)
int mine;

{

/* Initialize the 8259s, finishing with all interrupts disabled.    This is

* only done in protected mode, in real mode we don‘t touch the 8259s, but
* use the BIOS locations instead.    The flag "mine" is set if the 8259s are
* to be programmed for MINIX, or to be reset to what the BIOS expects.
*/

int i;

//在设置中断的时候,毫无疑问首先要关闭中断标志,防止干扰 intr_disable();

//如果芯片的保护模式打开,就必须设置 8259 的相关参数

//这里将主从片  0 号中断向量设置成 0X20,这个刚好是 32,为啥要这样设置

//主要是因为 0 到 31 被 intel 保留用于处理相关异常程序 if (machine.protected) {

/* The AT and newer PS/2 have two interrupt controllers, one master, * one slaved at IRQ 2. (We don‘t have to deal with the PC that

* has just one controller, because it must run in real mode.)
*/

outb(INT_CTL, machine.ps_mca ? ICW1_PS : ICW1_AT);

outb(INT_CTLMASK, mine ? IRQ0_VECTOR : BIOS_IRQ0_VEC);

/* ICW2 for master */

outb(INT_CTLMASK, (1 << CASCADE_IRQ)); /* ICW3 tells slaves

*/

outb(INT_CTLMASK, ICW4_AT_MASTER);

outb(INT_CTLMASK, ~(1 << CASCADE_IRQ));  /* IRQ 0-7 mask */

outb(INT2_CTL, machine.ps_mca ? ICW1_PS : ICW1_AT);

outb(INT2_CTLMASK, mine ? IRQ8_VECTOR : BIOS_IRQ8_VEC);

/* ICW2 for slave */

outb(INT2_CTLMASK, CASCADE_IRQ);  /* ICW3 is slave nr */

outb(INT2_CTLMASK, ICW4_AT_SLAVE);

outb(INT2_CTLMASK, ~0); /* IRQ 8-15 mask */

/* Copy the BIOS vectors from the BIOS to the Minix location, so we

26

* can still make BIOS calls without reprogramming the i8259s.
*/

#if IRQ0_VECTOR != BIOS_IRQ0_VEC

phys_copy(BIOS_VECTOR(0) * 4L, VECTOR(0) * 4L, 8 * 4L);

#endif

#if IRQ8_VECTOR != BIOS_IRQ8_VEC

phys_copy(BIOS_VECTOR(8) * 4L, VECTOR(8) * 4L, 8 * 4L);

#endif

} else {

/* Use the BIOS interrupt vectors in real mode.    We only reprogram the * exceptions here, the interrupt vectors are reprogrammed on demand. * SYS_VECTOR is the Minix system call for message passing.

*/

//这个是用于实模式,不做考虑

for (i = 0; i < 8; i++) set_vec(i, int_vec[i]);
set_vec(SYS_VECTOR, s_call);
}

}

/*=========================================================== ================*

* put_irq_handler 这个函数就是注册一个硬件中

断处理程序,主要是设置 hook 相应的域,并且做出一些合法的检测。

*============================================================ ===============*/

PUBLIC void put_irq_handler(hook, irq, handler) irq_hook_t *hook;

int irq;

irq_handler_t handler;
{

/* Register an interrupt handler. */

//这里主要用于注册一个中断处理程序 int id;

irq_hook_t **line;

//如果向量号不在接受的范围,则返回并且输出相关警告 if (irq < 0 || irq >= NR_IRQ_VECTORS)

panic("invalid call to put_irq_handler", irq); //irq 为首队列

line = &irq_handlers[irq];

id =1;//每注册一个中断处理程序,id 就向左移一位,也就是说最多能够注册

32 个中断处理程序

//在普通机中,根本不需要注册这么多 while (*line != NULL) {

27

if (hook == *line) return; /* extra initialization */

line = &(*line)->next;
id <<= 1;

}

if (id == 0) panic("Too many handlers for irq", irq);

//下面将这些挂钩信息都设置成应有的信息,这就是当做注册中断核心步骤
hook->next = NULL;

hook->handler = handler;
hook->irq = irq;

hook->id = id;
*line = hook;

irq_use |= 1 << irq;

}

/*=========================================================== ================*

* rm_irq_handler *

*============================================================ ===============*/

PUBLIC void rm_irq_handler(hook)
irq_hook_t *hook;

{

/* Unregister an interrupt handler. */

//解除一个已经注册的中断处理程序 int irq = hook->irq;

int id = hook->id;
irq_hook_t **line;

if (irq < 0 || irq >= NR_IRQ_VECTORS)

panic("invalid call to rm_irq_handler", irq);

line = &irq_handlers[irq];

while (*line != NULL) {

if ((*line)->id == id) {

(*line) = (*line)->next;

if (! irq_handlers[irq]) irq_use &= ~(1 << irq); return;

}

line = &(*line)->next;

}

/* When the handler is not found, normally return here. */

}

28

/*=========================================================== ================*

* intr_handle *

*============================================================ ===============*/

PUBLIC void intr_handle(hook)
irq_hook_t *hook;

//注意这里处理的入口是 hook,也就是将 irq_handlers[NR_IRQ_VECTORS]里的 hook 位置上

//所有应该处理的中断函数都应该处理掉
{

/* Call the interrupt handlers for an interrupt with the given hook list.

* The assembly part of the handler has already masked the IRQ, reenabled the * controller(s) and enabled interrupts.

*/

//为一个中断来调用中断处理程序,整个 hook 构成一个链表,依次进行检查,如 果有需要服务的,就进行

//服务

/* Call list of handlers for an IRQ. */
while (hook != NULL) {

/* For each handler in the list, mark it active by setting its ID bit,
* call the function, and unmark it if the function returns true.
*/

irq_actids[hook->irq] |= hook->id;//一个 irq 最多可以对应 32 个中断处理程

//每个 id 就是标志一个中断处理程序,irq_actids 每位就标志一位 //将 Hook 里面的每一个中断都进行处理掉

if ((*hook->handler)(hook)) irq_actids[hook->irq] &= ~hook->id; hook = hook->next;

}

/* The assembly code will now disable interrupts, unmask the IRQ if and only * if all active ID bits are cleared, and restart a process.

*/

}

#if _WORD_SIZE == 2

/*=========================================================== ================*

* set_vec

*针对 16 位芯片,在这里忽略

*============================================================

29

===============*/

PRIVATE void set_vec(vec_nr, addr)

int vec_nr; /* which vector */

vecaddr_t addr; /* where to start */

{

/* Set up a real mode interrupt vector. */

u16_t vec[2];

/* Build the vector in the array ‘vec‘. */
vec[0] = (u16_t) addr;

vec[1] = (u16_t) physb_to_hclick(code_base);

/* Copy the vector into place. */

phys_copy(vir2phys(vec), vec_nr * 4L, 4L);

}

#endif /* _WORD_SIZE == 2 */

2.6MINIX3 其部件处理中断举例

关于 i8259 几个非常重要的函数,我准备结合 CLOCK 任务来分析: 首先看 clock.c 文件:先看 init_clock.c 函数:

{

put_irq_handler(&clock_hook, CLOCK_IRQ, clock_handler);/* register handler */

????

}

我们深入里面分析:我们继续把 put_irq_handler()函数拿出来 首先上面的函数的各个参数分析下:

Clock_hook 是时钟钩子,irq 对于的就是时钟向量号,clock_handler 就是时钟处 理函数。我们现在以这几个参数进入 put_irq_handler()函数
PUBLIC void put_irq_handler(hook, irq, handler)
irq_hook_t  *hook;

int irq;

irq_handler_t handler;

30

{

/* Register an interrupt handler.  */
int id;

irq_hook_t  **line;

//irq时钟向量号,这点很显然满足条件也就不用执行panic()
if  (irq  <  0  || irq  >= NR_IRQ_VECTORS)

panic("invalid call to put_irq_handler", irq);

//将line指针指向irq_handlers[irq],准备用line来操作这个钩子信息
line  = &irq_handlers[irq];

id  =1;

//时钟初始化是第一个做到得,所以这个循环实际上不执行
while  (*line  != NULL)  {

if  (hook  ==  *line) return;

line  = &(*line)->next;

id  <<=  1;

}

//出来之后id=1

/* extra initialization  */

if  (id  ==  0) panic("Too many handlers for irq", irq); //对时钟钩子相关信息进行设置

hook->next  = NULL;

//将钩子的处理函数是在这里就是clock_handler
hook->handler  = handler;

hook->irq  = irq;

hook->id  = id;
*line  = hook;

irq_use  |=  1  << irq;

}

irq_use |= 1 << irq;

}

函数示意图如下图表示:

31

现在讨论另外一个过程:就是 intr_handler(hook),前面已经介绍这个函数的内容, 现在我们所要考虑的是整个调用的大体过程,我们先看下执行流程图:

32

在进入 intr_handle 之后,我们在这里就是考虑 clock_handler,所以这里就是做 clock_handler 的调用工作。先看看 intr_handle 是干了啥内容:就是扫描 hook 队 列,完成相关的中断处理函数,这里就是完成 CLOCK 中断处理函数。

2.7 MINIX3 系统调用处理机制

以上就是整个硬件中断的基本处理过程。我们现在把话题换到软中断,也就是陷 阱,看看 MINIX3 是怎么处理陷阱过程:

MINIX3 自动陷入内核之后,会进入一个汇编处理函数_s_call

33

!*=================================================================== ========*

!* _s_call 这是系统调用门陷入进来的 *

!*=================================================================== ========*

.align  16
_s_call:
_p_s_call:

cld ! set direction flag to a known value

sub esp,  6*4 ! skip RETADR, eax, ecx, edx, ebx, est

!这些参数都可以跳过

!压栈,ebp已经由TSS自动放入,则ebp可以压入栈中保存

push    ebp ! stack already points into proc table

push    esi

push    edi

!这里不想硬件中断,调用SAVE来做这些压栈动作,这里不需要,主要是为了提

!高效率

o16 push    ds

o16 push    es

o16 push    fs

o16 push    gs

!设置内核段选择器
mov dx, ss

mov ds, dx
mov es, dx

incb (_k_reenter)

mov esi, esp ! assumes P_STACKBASE  ==  0

mov esp, k_stktop

xor ebp, ebp  ! for stacktrace

! end of inline save

34

! now set up parameters for sys_call()

!现在进入真正意义上的内核态,也就是说现在的栈是内核公用栈

!下面一系列操作其实是非常明确的,就是将这些参数全部压入栈中,主要是为 了调用_sys_call()函数,这个函数是处理消息机制的与外界唯一接口的函数, 同时ebx eax ecx都是编译器事先约定好的:ebx:指向用户消息 eax:源或者目的 进程号 ecx:代表的是SEND或者是RECEIVE或者是BOTH

push    ebx ! pointer to user message

push    eax ! src/dest

push    ecx ! SEND/RECEIVE/BOTH

call _sys_call ! sys_call(function, src_dest, m_ptr)

!这个函数我们在进程通信机制可以看到,整个函数是一个与外界的接口,主要 用于发送或者接受目的进程的消息

! caller is now explicitly in proc_ptr
mov AXREG(esi), eax ! sys_call MUST PRESERVE si

现在就是直接执行下面的操作 注意下面的函数没有放上来,下面其实马上跟着 的就是restart函数,也就是重新启动这个函数

! Fall into code to restart proc/task running

35

2.8MINIX3 异常处理

这里的异常简单的介绍下,其实异常的处理大体方法和前面的中断或者陷入是同 样的思路,只不过这里的异常是 CPU 自动检测,自动陷入,自动执行异常处理函 数。整个过程可以看下流程图:

!*=================================================================== ========*

!* exception handlers    MINIX3默认处理的异常处理方式,所有

的异常最终会进入一个专门的异常通用处理函数:exception,这个函数在后面

会有详细接受 *

!*=================================================================== ========*

_divide_error:

push    DIVIDE_VECTOR
jmp exception

36

_single_step_exception:
push    DEBUG_VECTOR
jmp exception

_nmi:

push    NMI_VECTOR
jmp exception

_breakpoint_exception:

push    BREAKPOINT_VECTOR
jmp exception

_overflow:

push    OVERFLOW_VECTOR
jmp exception

_bounds_check:

push    BOUNDS_VECTOR
jmp exception

_inval_opcode:

push    INVAL_OP_VECTOR
jmp exception

_copr_not_available:

push    COPROC_NOT_VECTOR
jmp exception

_double_fault:

push    DOUBLE_FAULT_VECTOR
jmp errexception

_copr_seg_overrun:

push    COPROC_SEG_VECTOR
jmp exception

_inval_tss:

push    INVAL_TSS_VECTOR
jmp errexception

_segment_not_present:

push    SEG_NOT_VECTOR
jmp errexception

37

_stack_exception:

push    STACK_FAULT_VECTOR
jmp errexception

_general_protection:

push    PROTECTION_VECTOR
jmp errexception

_page_fault:

push    PAGE_FAULT_VECTOR
jmp errexception

_copr_error:

push    COPROC_ERR_VECTOR
jmp exception

!*=================================================================== ========*

!* exception 异常同样处理函数 *

!*=================================================================== ========*

! This is called for all exceptions which do not push an error code.

.align  16

exception:

sseg   mov (trap_errno),  0 ! clear trap_errno

sseg   pop (ex_number)

jmp exception1!进入这个函数

!*=================================================================== ========*

!* errexception *

!*=================================================================== ========*

! This is called for all exceptions which push an error code.

.align  16

errexception:

sseg   pop (ex_number)
sseg   pop (trap_errno)

exception1: ! Common for all exceptions.

push    eax ! eax is scratch register

!这里将eax压栈主要是后面会用到eax用来操作相关参数

38

!将之前的EIP储存到EAX寄存器中,之后存储到(old_eip)变量中

mov eax,  0+4(esp) ! old eip

sseg   mov (old_eip), eax

!下面是一样的道理,存储cs,eflags存储到变量old_cs old_eflags中

movzx   eax,  4+4(esp) ! old cs

sseg   mov (old_cs), eax

mov eax,  8+4(esp) ! old eflags

sseg   mov (old_eflags), eax
pop eax

call    save

!调用save 做好寄存器保护工作,save前面看到,SAVE之后就是用户栈变成内
核栈

!下面是将参数压栈,之后调用exception()函数来处理异常,这个就深入往里 面分析,处理完之后,就跳过这些参数,准备做好返回工作,就是从内核态返回 用户态,这个处理过程和中断基本上是一样的

push (old_eflags)

push (old_cs)

push (old_eip)

push (trap_errno)

push (ex_number)

call _exception !  (ex_number, trap_errno, old_eip,

!   old_cs, old_eflags)

add esp,  5*4

ret

至此中断,系统调用,异常基本分析完,一中断为例,MINIX3 采用一种能够处 理共享中断的方式来处理中断程序,中断处理程序的操作系统部分 MINIX3 尽量 设计的简单高效,主要就是栈之间的变化,以及一些寄存器的储存到哪个栈中。 以及怎么处理返回动作。

时间: 2024-08-07 05:13:04

中断分析的相关文章

edge中断分析

目前正在调试msix中断,在测试过程中发现会概率性的丢失中断.Msix中断默认是edge触发的中断,edge触发的中断是在中断对应pin发生电平信号跳变的时候,会发出一个中断请求.因为跳变是一瞬间的,不会像level触发中断那样一直保持电平不变,这样就可能会漏掉某一个跳变的瞬间,表现就是丢失了一个中断. 内核中处理edge触发中断的函数为handle_edge_irq,此函数有do_IRQ函数调用而来. void handle_edge_irq(unsigned int irq, struct 

LINUX-内核-中断分析-中断向量表(3)-arm【转】

转自:http://blog.csdn.net/haolianglh/article/details/51986987 arm中断概念 在<ARM体系结构与编程>第9章中说到,ARM 中有个概念叫做“异常中断”,也就是包括外部中断在内的各种异常.显然,ARM体系的“异常中断”概念更加接近MIPS体系中的“异常”概念. 既然更类似MIPS体系,那么自然的ARM体系就存在“异常中断入口”和“异常中断向量表”的概念. arm的异常中断向量表 非向量化中断 ARM体系定义了7种异常中断,在<AR

逻辑中断分析

一.逻辑与&& 使用格式:条件A&&条件B 运算过程: 先判断条件A是否成立 如果条件A成立,则接着再判断条件B是否成立,如果条件B成立"条件A&&条件B"的结果为1即真,如果条件B不成立,结果为0,即为假 如果条件A不成立,就不会去判断条件B是否成立,因为条件A已经不成立了,不管条件B是否成立,"条件A&&条件B"的结果肯定是0,也就是假 这样就执行不了条件B,提高了运行的性能 例如: /Volume

非常好!!!Linux源代码阅读——中断【转】

Linux源代码阅读——中断 转自:http://home.ustc.edu.cn/~boj/courses/linux_kernel/2_int.html 目录 为什么要有中断 中断的作用 中断的处理原则 Linux 中断机制 中断控制器 中断描述符 中断数据结构 中断的初始化 内核接口 中断处理过程 CPU 的中断处理流程 保存中断信息 处理中断 从中断中返回 编写中断处理程序 软中断.tasklet与工作队列 上半部与下半部 软中断 tasklet 工作队列 1 为什么要有中断 1.1 中

Java Core和HeapDump

什么是Java Core和Heap Dump Java程序运行时,有时会产生Java Core及Heap Dump文件,它一般发生于Java程序遇到致命问题的情况下. 发生致命问题后,Java进程有时可以继续运行,但有时会挂掉. 为了能够保留Java应用发生致命错误前的运行状态,JVM在死掉前产生两个文件,分别为JavaCore及HeapDump文件. JavaCore和Heap Dump的区别 l JavaCore是关于CPU的 JavaCore文件主要保存的是Java应用各线程在某一时刻的运

ucosii任务切换OS_TASK_SW()

stm32F103中任务切换定义 1 //任务切换宏,由汇编实现. 2 #define OS_TASK_SW() OSCtxSw() os_cpu_a.asm中任务切换函数的定义 1 NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制寄存器 2 NVIC_SYSPRI2 EQU 0xE000ED20 ; 系统优先级寄存器(2) 3 NVIC_PENDSV_PRI EQU 0xFFFF0000 ; PendSV中断和系统节拍中断 4 ; (都为最低,0xff). 5 NVIC_

OR1200处理器的可编程中断控制器PIC分析

以下内容摘自<步步惊芯--软核处理器内部设计分析>一书 16.3可编程中断控制器PIC分析 16.3.1 PIC介绍 可编程中断控制器Programmable Interrupt Controller(PIC)用来响应各种中断事件,如:键盘事件.串口数据到达等,PIC收集所有的中断,并通知CPU中断到达,后者转入到中断处理例程进行处理.OR1200最多支持32个中断.其功能实现主要依靠两个特殊寄存器:中断屏蔽寄存器PICMR.中断状态寄存器PICSR.通过PICMR可以设置是否屏蔽某些中断,通

Linux x86_64 APIC中断路由机制分析

不同CPU体系间的中断控制器工作原理有较大差异,本文是<Linux mips64r2 PCI中断路由机制分析>的姊妹篇,主要分析Broadwell-DE X86_64 APIC中断路由原理.中断配置和处理过程,并尝试回答如下问题: 为什么x86中断路由使用IO-APIC/LAPIC框架,其有什么价值? pin/irq/vector的区别.作用,取值范围和分配机制? x86_64 APIC关键概念 Pin 此处的pin特指APIC的中断输入引脚,与内外部设备的中断输入信号相连.从上图中可以看出,

6.分析request_irq和free_irq函数如何注册注销中断

上一节讲了如何实现运行中断,这些都是系统给做好的,当我们想自己写个中断处理程序,去执行自己的代码,就需要写irq_desc->action->handler,然后通过request_irq()来向内核申请注册中断 本节目标:      分析request_irq()如何申请注册中断,free_irq()如何注销中断 1.request_irq()位于kernel/irq/ manage .c,函数原型如下: int request_irq(unsigned int irq, irq_handl