上一篇博文我们分析了中断描述符表的中断门初始化过程,并且在interrupt数组中初始化过程中,可以看到每个中断处理程序都会跳入common_interrupt中。下面我们分析下common_interrupt汇编片段(arch/x86/kernel/entrt_32.S)。

     .p2align CONFIG_X86_L1_CACHE_SHIFT
common_interrupt:
ASM_CLAC
addl $-0x80,(%esp) /* Adjust vector into the [-256,-1] range */
SAVE_ALL
TRACE_IRQS_OFF
movl %esp,%eax
call do_IRQ
jmp ret_from_intr
ENDPROC(common_interrupt)
CFI_ENDPROC

第5行SAVE_ALL也是一个汇编片段(宏),用来将当前多个寄存器压栈,因为在do_IRQ中可能会用到这些寄存器。第8行调用了do_IRQ函数,接下来我们分析do_IRQ函数(arch/x86/kernel/irq.c)。

 1 __visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
2 {
3 struct pt_regs *old_regs = set_irq_regs(regs);
4
5 /* high bit used in ret_from_ code */
6 unsigned vector = ~regs->orig_ax;
7 unsigned irq;
8
9 irq_enter();
10 exit_idle();
11
12 irq = __this_cpu_read(vector_irq[vector]);
13
14 if (!handle_irq(irq, regs)) {
15 ack_APIC_irq();
16
17 if (irq != VECTOR_RETRIGGERED) {
18 pr_emerg_ratelimited("%s: %d.%d No irq handler for vector (irq %d)\n",
19 __func__, smp_processor_id(),
20 vector, irq);
21 } else {
22 __this_cpu_write(vector_irq[vector], VECTOR_UNDEFINED);
23 }
24 }
25
26 irq_exit();
27
28 set_irq_regs(old_regs);
29 return 1;
30 }

第12行的vector_irq数组保存了中断向量号和中断线号(中断号)的对应关系,利用__this_cpu_read函数获得当前中断向量号所对应的中断号。第14行中handle_irq函数,使用中断号irq作为参数,进入该中断号所对应的中断服务例程中,下面分析下handle_irq函数(arch/x86/kernel/irq_32.c)。

 bool handle_irq(unsigned irq, struct pt_regs *regs)
{
struct irq_desc *desc;
int overflow; overflow = check_stack_overflow(); desc = irq_to_desc(irq);
if (unlikely(!desc))
return false; if (user_mode_vm(regs) || !execute_on_irq_stack(overflow, desc, irq)) {
if (unlikely(overflow))
print_stack_overflow();
desc->handle_irq(irq, desc);
} return true;
}

第8行获取到中断号irq所对应的struct irq_desc结构体指针,内核使用struct irq_desc类型结构体数组来存放所有的中断服务例程,中断号irq作为数组元素下标,如下所示(kernel/irq/irqdesc.c)。

 struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[ ... NR_IRQS-] = {
.handle_irq = handle_bad_irq,
.depth = ,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};

再来看下struct irq_desc结构体(include/linux/irqdesc.h)。

 struct irq_desc {
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler;
#endif
struct irqaction *action; /* IRQ action list */
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;
atomic_t threads_handled;
int threads_handled_last;
raw_spinlock_t lock;
struct cpumask *percpu_enabled;
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint;
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
unsigned long threads_oneshot;
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;

真正的中断处理程序并不直接放在struct irq_desc结构体中,而是存放在struct irq_desc结构体的action成员所指向的struct irqaction结构体中,第8行。下面看下struct irqaction结构体类型(include/linux/interrupt.h)。

 struct irqaction {
irq_handler_t handler;                    
void *dev_id;
void __percpu *percpu_dev_id;
struct irqaction *next;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned int irq;
unsigned int flags;
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;

第1行handler中存放着中断服务例程。第5行next中存放下一个该类型结构体指针。因为一个中断号可以对应多个中断服务例程(中断线共享),内核将中断号相同的多个中断服务例程组织成一个链表,挂到以irq号作为下标的irq_desc数组元素中。

回到handle_irq函数中,第8行获取到irq号所对应的struct irq_desc结构体指针desc,接着第15行执行了desc->handle_irq函数,在该函数中执行irq号的所有中断服务例程。

在这里,一定要区别清楚IDT表idt_table和irq_desc数组的区别,idt_table中存放的是中断处理程序,而且这些中断处理程序的开头部分代码都是相同的,都要跳到common_interrupt函数中,进而去寻找中断服务例程。而irq_desc数组中存放的是中断服务例程,中断处理程序最终要通过该数组找到对应的中断服务例程并执行它。我们在编写驱动程序时,很多时候需要编写设备的中断服务例程,我们将中断服务例程存放在申请的struct irqaction结构体当中,并将该结构体挂到irq_desc数组的对应链表中,当中断发生后,系统会自动通过IDT--->GDT--->中断处理程序--->common_interrupt(属于中断处理程序)--->do_IRQ--->handle_irq,然后将irq_desc[NR_IRQS]数组中irq号对应的所有中断服务例程全部执行一遍。

当中断服务例程执行结束后,就会返回文章最开始的common_interrupt函数中,开始执行第9行,跳入到ret_from_intr函数中(arch/x86/kernel/entry_32.S)。

     ALIGN
RING0_PTREGS_FRAME
ret_from_exception:
preempt_stop(CLBR_ANY)
ret_from_intr:
GET_THREAD_INFO(%ebp)
#ifdef CONFIG_VM86
movl PT_EFLAGS(%esp), %eax # mix EFLAGS and CS
movb PT_CS(%esp), %al
andl $(X86_EFLAGS_VM | SEGMENT_RPL_MASK), %eax
#else
/*
* We can be coming here from child spawned by kernel_thread().
*/
movl PT_CS(%esp), %eax
andl $SEGMENT_RPL_MASK, %eax
#endif
cmpl $USER_RPL, %eax
jb resume_kernel # not returning to v8086 or userspace ENTRY(resume_userspace)
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx # is there any work to be done on
# int/exception return?
jne work_pending
jmp restore_all
END(ret_from_exception)

第15行从当前栈中将cs寄存器的值存入eax中,第16行通过掩码计算,将eax中的DPL字段提取出来再存入eax,第18行比较eax(DPL)和用户空间权限的大小,如果DPL权限大的话,执行19行,恢复到内核态中,否则恢复到用户态,21行。

中断——中断处理程序的进入与退出 (基于3.16-rc4)的更多相关文章

  1. 软中断与硬中断 & 中断抢占 中断嵌套

    参考了这篇文章:http://blog.csdn.net/zhangskd/article/details/21992933 从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通 ...

  2. Linux内核学习之中断 中断本质【转】

    转自:http://www.linuxidc.com/Linux/2011-11/47657.htm [中断概述] 中断本质上是一种特殊的电信号,由硬件设备发向处理器.异常和中断的不同是异常在产生时必 ...

  3. 做自己的docker镜像(基于ubuntu:16.04)

    基于ubuntu:16.04 apt-get update -y apt-get install sudo -y 换源 sudo apt-get install vim sudo vim /etc/a ...

  4. 中断——中断描述符表的定义和初始化(二) (基于3.16-rc4)

    上篇博文对中断描述符表(IDT)中异常和非屏蔽中断部分的初始化做了说明,这篇文章将分析外部中断部分的初始化. 在上篇博文中,可以看到,内核在setup_once汇编片段中,对中断和异常部分做了初步的初 ...

  5. 中断——中断描述符表的定义和初始化(一) (基于3.16-rc4)

    1.中断描述符表的定义(arch/x86/kernel/traps.c) gate_desc debug_idt_table[NR_VECTORS] __page_aligned_bss; 定义的描述 ...

  6. Linux的中断 & 中断和异常的区别

    参考 http://www.yesky.com/20010813/192117.shtml 结构化程序设计思想认为:程序 = 数据结构 + 算法.数据结构体现了整个系统的构架,所以数据结构通常都是代码 ...

  7. mount过程分析之一(基于3.16.3内核)【转】

    转自:https://blog.csdn.net/zr_lang/article/details/39963253 一直想写有些关于文件系统的博文,但是由于近一年来实在太忙,所以没有时间写.前几日赶上 ...

  8. EJBCA的安装(基于Ubuntu 16.04 LTS + wildfly8 + ejbca6.3.11 + jdk7)

    前一段时间折腾了一下PKI,用EJBCA在研究院内网搭建了一个CA,目前是提供给手机端(安卓和IOS)来和服务器端(nginx + Java应用)做安全连接的(客户端和服务器端双向认证) 由于EJBC ...

  9. linux内核--中断处理程序

    一个设备的中断处理程序是它设备驱动程序的一部分--设备驱动程序是用于对设备进行管理的内核代码.中断处理程序与其他内核函数的真正区别在于,中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断 ...

随机推荐

  1. 本人arcgis api for javascript中常见错误总结

    1. 2.对象不支持"replace"属性或方法 解决办法:一般在ie中执行js会报这样的错误,基本问题就是你引用了某个对象中不存在的方法,可能是这个方法本来存在而你写错了,或者调 ...

  2. 项目SVN的IP地址发生变化时修改SVN为新的IP地址

    在eclipse或者Myeclipse自带的svn:subclipse中修改ip地址 项目开发中有可能要修改SVN的IP地址,entries文件里面包含svn服务器的地址信息.每个文件夹都会产生一个e ...

  3. PHP 语言需要避免的 10 大误区

    PHP是一种非常流行的开源服务器端脚本语言,你在万维网看到的大多数网站都是使用php开发的.但是,你大概很奇怪的注意到有少部分的人发誓要离php 远远的.但是令人更奇怪的是或者很震惊的说他们不用php ...

  4. C# App.config 详解

      读语句: String str = ConfigurationManager.AppSettings["DemoKey"]; 写语句: Configuration cfa = ...

  5. Android开发之定义app在手机的安装位置

    定义app在手机的安装位置,可以通过在清单文件中添加属性 android:installLocation="" 该属性有三个值:auto(自动),preferExternal(外部 ...

  6. Android仿iPhone晃动撤销输入功能(微信摇一摇功能)

    重力传感器微信摇一摇SensorMannager自定义alertdialogSensorEventListener 很多程序中我们可能会输入长文本内容,比如短信,写便笺等,如果想一次性撤销所有的键入内 ...

  7. 循环中不要放入openSession()

    for(Shop s:list) { System.out.println(s.getName()); String sql="select shopId,sum(ele_bank+ele_ ...

  8. bzoj1312

    忘写题解了,经典的最大密度子图 可以类似分数规划的做,二分密度,然后转化为最大权闭合子图做,判断是否大于0 注意方案的输出 const eps=1e-6; lim=1e-12; inf=; type ...

  9. uva12169 Disgruntled Judge

    扩展欧几里得. 枚举a,根据x1,x3和递推式可得. (a+1)*b-k*mod=f[3]-a*a*b. 通过扩展欧几里得求出b. 带入原式进行计算. #include<cstdio> # ...

  10. UVa 11361 (计数 递推) Investigating Div-Sum Property

    题意: 统计[a, b]中有多少个数字满足:自身是k的倍数,而且各个数字之和也是k的倍数. 分析: 详细分析见<训练之南>吧,=_=|| 书上提出了一个模板的概念,有了模板我们就可以分块计 ...