中断是计算机与外界联系的唯一途径。本文将分析在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;
}
分享到:
相关推荐
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
(1)准备阶段:在内核栈保存通用寄存器内容(称为现场信息),这部分大多 (2)处理阶段:采用C函数进行具体处理 (3)恢复阶段:恢复保存在内核栈中的
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...
默克(Merck Hung)编写的内核。 IA32体系结构,32位保护模式,内存分页,键盘,计时器中断处理和一个简单的内核外壳程序即可运行。 用C语言编写,在Linux环境下开发。
13.7 Real-World IA-32反编译 477 13.8 结论 477 附录A 揭密代码结构 479 附录B 理解编译后的算术运算 519 附录C 破译程序数据 537 索引 561 译 者 序 记得第一次做与逆向有关的工作是2000年,当时由于项目的需要,...
Linus Torvalds当时是一名赫尔辛基大学计算机科学系的二年级学生,经常要用自己的电脑去访问大学主机上的新闻组和邮件,为了方便读写和下载文件,他自己编写了磁盘驱动程序和文件系统,这成为了 Linux第一个内核的...
Linus Torvalds当时是一名赫尔辛基大学计算机科学系的二年级学生,经常要用自己的电脑去访问大学主机上的新闻组和邮件,为了方便读写和下载文件,他自己编写了磁盘驱动程序和文件系统,这成为了 Linux第一个内核的...