`
Michaelmatrix
  • 浏览: 208576 次
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

IA32上Linux内核中断机制分析

 
阅读更多
中断是计算机与外界联系的唯一途径。本文将分析在IA-32体系结构上的Linux内核对待中断系统的处理,针对的是2.6内核,引用的代码则具体则是2.6.14的。


一。几个相关概念的澄清
1, 中断信号:

在电路级别来说,中断就是输送到CPU的INTR引脚上的电平信号。

2, 可编程中断控制器(PIC,Programmable Interrupt Controller):

PIC是在计算机外部设备与CPU之间的芯片,它负责把自己接收到的外部中断信号,提交给CPU。在80386中,PIC是两片i8259A芯片级联;在Pentium以及后来的CPU中,集成了一个叫做高级可编程中断控制器(Advanced Programmable Interrupt Controller)的PIC。如果你想用IA32处理器搭建SMP系统,则APIC是必不可少的。

3, 中断向量与中断号:

中断向量是Intel从IA-32 CPU角度看到的中断信号划分;中断号则是Linux系统对外部中断的号码分配。当外设把中断信号递送给PIC时,与之关联的是一个“中断号”(每个中断号对应一条中断线,从软件的角度来看,这两个术语可以混用);当PIC把这个中断信号发送给CPU时,与之关联的是一个“中断向量”。

在IA-32体系结构中,所有的异常和不可屏蔽中断(Non-Maskable Interrupt)的中断向量都是Intel预先定义的,软件无法更改;可屏蔽中断的中断向量可以通过编程来更改。在Linux上,0号中断(也就是时钟中断)对应的中断向量是0x20,也就是十进制的32。

4, 异常(Exception)

顾名思义,异常是指CPU检测到了某种不正常的情形出现。CPU产生的异常是不可屏蔽的(eflags寄存器的IF位对异常不起作用),根据异常处理程序返回时,是否需要重新执行引发异常的那条指令,又可以把异常分为3种:

1)故障(Fault)。故障是比较轻微的异常,返回时重新执行引发故障的那条指令。

2)陷阱(Trap)。陷阱处理返回时,不重新执行引发陷阱的那条指令。

3)中止(Abort)。中止是严重的异常,将导致任务的中止而不会返回。

还有一种是程序产生的异常,如INT3指令、BOUND指令等。CPU把这种异常当作是陷阱来处理。

5, 中断描述表IDT

异常与中断发生时,都需要到IDT中查找相关信息,以找到对应的处理程序以及其他动作。需要注意的是,保护模式下发生权限提升时,中断穿越的是中断门,而异常穿越的是陷阱门。二者的区别是:当CPU穿越中断门时,是自动关中断的;而穿越异常门则不会。





二。重要数据结构与函数
在系统引导期间,需要初试化中断处理(asm/i386/kernel/entry.S):

422 #define BUILD_INTERRUPT(name, nr) /

423 ENTRY(name) /

424 pushl $nr-256; / #这里得到一个负数,因为正数留给系统调用

425 SAVE_ALL / #保存寄存器

426 movl %esp,%eax; /

427 call smp_/**/name; /

428 jmp ret_from_intr;




其中,SAVE_ALL宏就是用来保存寄存器的。




内核书籍中经常提到的中断上下文,指的是内核正在运行中断服务程序或softirq,无法代表当前进程的情形。中断上下文没有自己专有的堆栈,相反,它借用被中断进程的内核堆栈──IA-32上的Linux默认这个堆栈只有8k大小,而且很可能在处理中断的过程中又被另一个中断源中断。因此如果你自己编写中断处理程序,递归层次太深或者函数局部变量太大,都有可能导致栈溢出。(i386有一个4KStacks补丁,如果编译时打开该选项,则中断上下文使用独立的栈,而不占用被中断进程的。)




在include/linux/irq.h文件中,定义了一个中断描述数组iqr_desc[NR_IRQS],每一个中断向量都与它的一个元素相关联:




70 typedef struct irq_desc {

71 hw_irq_controller *handler;

72 void *handler_data;

73 struct irqaction *action; /* IRQ action list */

74 unsigned int status; /* IRQ status */

75 unsigned int depth; /* nested irq disables */

76 unsigned int irq_count; /* For detecting broken interrupts */

77 unsigned int irqs_unhandled;

78 spinlock_t lock;

79 #if defined (CONFIG_GENERIC_PENDING_IRQ) || defined (CONFIG_IRQBALANCE)

80 unsigned int move_irq; /* Flag need to re-target intr dest*/

81 #endif

82 } ____cacheline_aligned irq_desc_t; /*告诉GCC与CPU的L1告诉缓存对齐*/

83

84 extern irq_desc_t irq_desc [NR_IRQS];





当一个中断发生时,内核的处理是这样的(arch/i386/kernel/entry.S):




416 common_interrupt:

417 SAVE_ALL

418 movl %esp,%eax

419 call do_IRQ

420 jmp ret_from_intr




SAVE_ALL宏定义在entry.S中,负责保存寄存器,再将%esp寄存器移送到%eax中,调用do_IRQ()函数(arch/i386/kernel/irq.c):




/*

* do_IRQ()函数负责处理所有的外部设备中断(处理器间中断由它们各自

* 的处理函数来处理

*/

fastcall unsigned int do_IRQ(struct pt_regs *regs)

{

/* high bits used in ret_from_ code */

int irq = regs->orig_eax & 0xff;

/* i386上如果定义了CONFIG_4KSTAKS,就申请独立的栈,而不占用被中断进程的*/

#ifdef CONFIG_4KSTACKS

union irq_ctx *curctx, *irqctx;

u32 *isp;

#endif




irq_enter();

#ifdef CONFIG_DEBUG_STACKOVERFLOW

/* 检查堆栈溢出的代码,此处省去。 */

#endif




#ifdef CONFIG_4KSTACKS




curctx = (union irq_ctx *) current_thread_info();

irqctx = hardirq_ctx[smp_processor_id()];




/*

* 这是我们切换到中断栈的地方。然而,如果我们已经在使用中断栈(也

* 就是说,我们这次是中断了一个中断处理程序),我们就不切换栈,而

* 是继续使用当前的栈(此时,“当前的栈”是一个中断栈)

*/

if (curctx != irqctx) {

int arg1, arg2, ebx;




/* build the stack frame on the IRQ stack */

isp = (u32*) ((char*)irqctx + sizeof(*irqctx));

irqctx->tinfo.task = curctx->tinfo.task;

irqctx->tinfo.previous_esp = current_stack_pointer;




asm volatile(

" xchgl %%ebx,%%esp /n"

" call __do_IRQ /n"

" movl %%ebx,%%esp /n"

: "=a" (arg1), "=d" (arg2), "=b" (ebx)

: "0" (irq), "1" (regs), "2" (isp)

: "memory", "cc", "ecx"

);

} else

#endif

__do_IRQ(irq, regs); //真正的中断处理




irq_exit(); /* 如果需要,处理softirq。注意,这里有两种可能不需要处理softirq:1, local_softirq_pending为假;2,我们刚刚是中断了一个中断,嵌套中断没有最终返回之前,softirq是不能处理的。 */




return 1;

}

注意,fastcall是在include/asm-i386/linkage.h中定义的宏,它指导GCC连接时把fastcall修饰的函数的前三个参数用寄存器传递。另外一个类似的宏asmlinkage则告诉GCC不要用寄存器传递参数,asmlinkage和fastcall不能共存。


上面的do_IRQ()函数调用的__do_IRQ()代码如下(arch/i386/kernel/irq.c)




fastcall unsigned int __do_IRQ(unsigned int irq, struct pt_regs *regs)

{

irq_desc_t *desc = irq_desc + irq; /* 找到在irq_desc数组中的位置 */

struct irqaction * action; /* 取得相应的irqaction结构 */

unsigned int status;




kstat_this_cpu.irqs[irq]++;

if (CHECK_IRQ_PER_CPU(desc->status)) {

irqreturn_t action_ret;




/*

* 因为irq_desc[]数组中,每个CPU占一个元素,这里的desc就是本CPU

* 数据,所以此处不需要加锁。

*/

desc->handler->ack(irq);

action_ret = handle_IRQ_event(irq, regs, desc->action);

desc->handler->end(irq);

return 1;

}




spin_lock(&desc->lock);

desc->handler->ack(irq); /* 给i8259A或APIC应答信号 */

/*

* REPLAY is when Linux resends an IRQ that was dropped earlier

* WAITING is used by probe to mark irqs that are being tested

*/

status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);

status |= IRQ_PENDING; /* we _want_ to handle it */




/*

* If the IRQ is disabled for whatever reason, we cannot

* use the action we have.

*/

action = NULL;

if (likely(!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))) { /* 判断该IRQ是否是被禁止的,或者是已经在其他CPU上被处理 */

action = desc->action;

status &= ~IRQ_PENDING; /* 我们将处理它 */

status |= IRQ_INPROGRESS; /* 置位IRQ_INPROGRESS,以便其他CPU注意 */

}

desc->status = status;




/*

* 如果该IRQ没有处理函数,或者被禁止了,及早离开。

* 因为我们置位了PENDING,如果别的CPU正在处理该IRQ的

* 另一个实例,它就会小心些。

*/

if (unlikely(!action))

goto out;




/*

* Edge triggered interrupts need to remember

* pending events.

* This applies to any hw interrupts that allow a second

* instance of the same irq to arrive while we are in do_IRQ

* or in the handler. But the code here only handles the _second_

* instance of the irq, not the third or fourth. So it is mostly

* useful for irq hardware that does not mask cleanly in an

* SMP environment.

*/




for (;;) {

irqreturn_t action_ret;

spin_unlock(&desc->lock);

action_ret = handle_IRQ_event(irq, regs, action);

spin_lock(&desc->lock);

if (!noirqdebug)

note_interrupt(irq, desc, action_ret, regs);

if (likely(!(desc->status & IRQ_PENDING)))

break;

desc->status &= ~IRQ_PENDING;

}

desc->status &= ~IRQ_INPROGRESS;




out:

/*

* ->end()用来处理那些由于别的CPU正在运行其处理程序而被禁止的中断

*/

desc->handler->end(irq);

spin_unlock(&desc->lock);




return 1;

}
分享到:
评论

相关推荐

    Linux2.6内核标准教程(共计8-- 第1个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第6个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第8个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第3个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第7个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第4个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第2个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第5个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    4.第四周课件--IA-32Linux中异常中断处理课件1

    (1)准备阶段:在内核栈保存通用寄存器内容(称为现场信息),这部分大多 (2)处理阶段:采用C函数进行具体处理 (3)恢复阶段:恢复保存在内核栈中的

    清华大学Linux操作系统原理与应用

    1.5.3 Linux内核源代码分析工具 14 习题1 15 第2章 内存寻址 17 2.1 内存寻址简介 17 2.1.1 Intel x86 CPU寻址方式的演变 18 2.1.2 IA32寄存器简介 19 2.1.3 物理地址、虚拟地址及线性地址 21 2.2 分段机制 22 2.2.1...

    Olux Kernel-开源

    默克(Merck Hung)编写的内核。 IA32体系结构,32位保护模式,内存分页,键盘,计时器中断处理和一个简单的内核外壳程序即可运行。 用C语言编写,在Linux环境下开发。

    Reversing:逆向工程揭密

    13.7 Real-World IA-32反编译 477 13.8 结论 477 附录A 揭密代码结构 479 附录B 理解编译后的算术运算 519 附录C 破译程序数据 537 索引 561 译 者 序 记得第一次做与逆向有关的工作是2000年,当时由于项目的需要,...

    自己动手写操作系统(含源代码).part2

    Linus Torvalds当时是一名赫尔辛基大学计算机科学系的二年级学生,经常要用自己的电脑去访问大学主机上的新闻组和邮件,为了方便读写和下载文件,他自己编写了磁盘驱动程序和文件系统,这成为了 Linux第一个内核的...

    自己动手写操作系统(含源代码).part1

    Linus Torvalds当时是一名赫尔辛基大学计算机科学系的二年级学生,经常要用自己的电脑去访问大学主机上的新闻组和邮件,为了方便读写和下载文件,他自己编写了磁盘驱动程序和文件系统,这成为了 Linux第一个内核的...

Global site tag (gtag.js) - Google Analytics