1.
在entry_32.S 里面,会建立一个interrupt数组
,
.text
vector=vector+1
.endif
.endr
2: jmp common_interrupt
.endr
END(irq_entries_start)
3e4: 6a 5e push $0x5e ; 第2次,也是标号1的地址
3e6: eb 12 jmp 3fa <irq_entries_start+0x1a>
3e8: 6a 5d push $0x5d
3ea: eb 0e jmp 3fa <irq_entries_start+0x1a>
3ec: 6a 5c push $0x5c
3ee: eb 0a jmp 3fa <irq_entries_start+0x1a>
3f0: 6a 5b push $0x5b
3f2: eb 06 jmp 3fa <irq_entries_start+0x1a>
3f4: 6a 5a push $0x5a
3f6: eb 02 jmp 3fa <irq_entries_start+0x1a>
3f8: 6a 59 push $0x59 ; 第6次,也是标号1的地址
3fa: e9 e1 03 00 00 jmp 7e0 <common_interrupt> ; 这里就是标号2啦,所以上面的不需要象0-6那样,生成jmp 2f啦。
7.
IRQ分配到外部设备
--------------------------------------------------------
但为了兼容IBM PC体系,必须将以下设备静态的连接到指定的IRQ线:
(1)间隔定时设备必须连接到IRQ0线; 即时间中断必须连到IRQ0,而IRQ0对应的中断向量号为0x20 = 32.
interrupt数组,是一个函数指针数组。数组的每个元素都指向一个函数
其实呢,数组的每个元素,指向的是一段代码。
这段代码的功能为:
push $0x5f //这个其实就是将中断向量号压入
jmp 3fa <irq_entries_start+0x1a>当然,每个数组的内容指向的地址不一样,但是,指向的地址的内容跟上面的类似。
而3fa其实也是一个跳转,跳转到由common_interrupt进行处理。
jmp 7e0 <common_interrupt>
2. interrupt数组大概有32*7=224个。
3. interrupt数组的初始化比较有意思,是用汇编来写的。 编译的时候,初始化。
初始化代码如下:
.section .init.rodata,"a"
ENTRY(interrupt)
.text
.p2align 5
.p2align CONFIG_X86_L1_CACHE_SHIFT
ENTRY(irq_entries_start)
RING0_INT_FRAME
vector=FIRST_EXTERNAL_VECTOR=0x20=32
.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7 ; 重复32次, 即gcc重复生成代码32次
.balign 32
.rept 7 ; 重复7次,这样,其实一种重复32×7=224次
.if vector < NR_VECTORS
.if vector <> FIRST_EXTERNAL_VECTOR
CFI_ADJUST_CFA_OFFSET -4
.endif
1: pushl $(~vector+0x80) /* Note: always in signed byte range */ ; 这一行也会生成224次,但是push的内容在改。 标号1的地址也会改。
CFI_ADJUST_CFA_OFFSET 4
.if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6 ; 上面的7次循环中,0-5次都有这行代码,但是第6次没有
ENTRY(interrupt)
.text
.p2align 5
.p2align CONFIG_X86_L1_CACHE_SHIFT
ENTRY(irq_entries_start)
RING0_INT_FRAME
vector=FIRST_EXTERNAL_VECTOR=0x20=32
.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7 ; 重复32次, 即gcc重复生成代码32次
.balign 32
.rept 7 ; 重复7次,这样,其实一种重复32×7=224次
.if vector < NR_VECTORS
.if vector <> FIRST_EXTERNAL_VECTOR
CFI_ADJUST_CFA_OFFSET -4
.endif
1: pushl $(~vector+0x80) /* Note: always in signed byte range */ ; 这一行也会生成224次,但是push的内容在改。 标号1的地址也会改。
CFI_ADJUST_CFA_OFFSET 4
.if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6 ; 上面的7次循环中,0-5次都有这行代码,但是第6次没有
; 为什么第6次没有? 原因很简单,前0-5次,
; 都需要jmp 2f,2f所指向的在jmp到common_interrupt进行处理
,
; 而第6次,生成完pushl $(~vector+80) 之后,接下来正好就是 2: jmp common_interrupt 语句了
; 当然不要生成一个浪费的jmp了。
jmp 2f ; 跳转到标号2
.endif
.previous ; 把下面的内容放到
.long 1b ; 把标号1的地址放到interrupt数组里。注意, interrupt里面,只有标号1的地址,当然由于循环的原因,标号1的地址在变 。
.endif
.previous ; 把下面的内容放到
.init.rodata,"a", 即填到数组里面。 数组当让在数据段了。
.long 1b ; 把标号1的地址放到interrupt数组里。注意, interrupt里面,只有标号1的地址,当然由于循环的原因,标号1的地址在变 。
.text
vector=vector+1
.endif
.endr
2: jmp common_interrupt
.endr
END(irq_entries_start)
.previous
END(interrupt)
.previous
END(interrupt)
.previous
关于这段代码的更详细的分析,可以参见:
4. 看看entry_32.S 由entry_32.O objump -dS 出来的代码;
3e0: 6a 5f push $0x5f ; 第0次, 也是标号1的地址
3e2: eb 16 jmp 3fa <irq_entries_start+0x1a> ; jmp 到标号2
3e4: 6a 5e push $0x5e ; 第2次,也是标号1的地址
3e6: eb 12 jmp 3fa <irq_entries_start+0x1a>
3e8: 6a 5d push $0x5d
3ea: eb 0e jmp 3fa <irq_entries_start+0x1a>
3ec: 6a 5c push $0x5c
3ee: eb 0a jmp 3fa <irq_entries_start+0x1a>
3f0: 6a 5b push $0x5b
3f2: eb 06 jmp 3fa <irq_entries_start+0x1a>
3f4: 6a 5a push $0x5a
3f6: eb 02 jmp 3fa <irq_entries_start+0x1a>
3f8: 6a 59 push $0x59 ; 第6次,也是标号1的地址
3fa: e9 e1 03 00 00 jmp 7e0 <common_interrupt> ; 这里就是标号2啦,所以上面的不需要象0-6那样,生成jmp 2f啦。
5. 总结:
上面的代码,编译后的结果为:
-
interrupt[224]为一个函数指针数组,
-
interrupt[]的每个元素,指向位于irq_entries_start开始一段段代码,每段代码,都会执行push,然后跳转执行common_interrupt.
6. 上面的代码可以看到,标号1里面,push的时候,是一个负值。
为什么是负值?
--------------------------------------------------------
但为了兼容IBM PC体系,必须将以下设备静态的连接到指定的IRQ线:
(1)间隔定时设备必须连接到IRQ0线; 即时间中断必须连到IRQ0,而IRQ0对应的中断向量号为0x20 = 32.
即intel 8253/8254的输出管脚必须8259A的0号输入管脚
(2)8259A必须连接到IRQ2线;
(3)外部数学协处理器必须连接到IRQ13线;
(4)一个I/O设备可以连接到多个IRQ线;
(2)8259A必须连接到IRQ2线;
(3)外部数学协处理器必须连接到IRQ13线;
(4)一个I/O设备可以连接到多个IRQ线;
8. 时钟中断的处理过程
1. 注册
1. 在start_kernel函数中,调用late_time_init函数。
late_time_init其实被初始化为函数指针
x86_late_time_init
2.
x86_late_time_init
函数里面又会调用hpet_time_init函数
3. hpet_time_init里面则会调用setup_irq(irq=0, act=&irq0)
3. setup_irq函数里面,关键的是desc->action=&irq0, 这里很重要,即将irq0 查到desc的action 链表里面了。
2. 处理
1. 处理过程跟基本的interrupt的过程一样。
2. 直接根据中断向量跳转到common_interrupt处进行处理。
3. 在common_interrupt里面,先调用do_IRQ, 然后执行ret_from_intr函数。
4. 在do_IRQ函数中,则
1. 先调用handle_irq函数。
2. 然后调用irq_exit函数, 这个函数很重要,softirq就是在这里进行处理的。
5. handle_irq函数里面呢,调用desc->handle_irq(irq, desc)函数。
6. desc->handle_irq函数指针在start_kernel里面调用init_IRQ函数时,前面16个desc->handle_irq = handle_level_irq
即水平触发
7. 而
handle_level_irq函数里面,关键是调用
action_ret = handle_IRQ_event(irq, action);函数。
8.
handle_IRQ_event函数里面呢, 则又调用action->handler(irq, action->dev_id);来进行处理。
9. 关键来了,处理的函数为action->handler(irq, action->dev_id);
10. 前面我们说了, action=irq0, irq0的handler函数指针被初始化为timer_interrupt
11. 所以呢, 时钟中断的处理函数是timer_interrupt
12. timer_interrupt函数里面呢,执行了global_clock_event->event_handler(global_clock_event);函数。
13. global_clock_event->event_handler这个就比较特别了, 根据当前的时钟发生器来进行处理的。
即当时钟发生器是8253和APIC的时候,处理函数是不一样的。
14. 当时钟发生器是8253的时候,global_clock_event->event_handler = tick_handle_periodic
15. 这函数里面呢,
3. 中断返回。
1. 从上面的第3步可以看出,当执行完中断处理程序之后,会执行ret_from_intr函数。
9. 软中断的处理过程
1. 前面的2.4里面提到,在do_IRQ函数里面,调用中断处理函数之后,会调用irq_exit函数。
2. irq_exit里面呢,执行了下面的语句
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
这里in_interrupt()指,只要在硬中断,软中断,NMI里面,都算在中断里面, 就不再执行了。
也就说, 如果现在正在处理软中断,然后来硬中断了,然后又在irq_exit里面,再次进行判断, 就直接退出了。
也就说, 如果现在正在处理软中断,然后来硬中断了,然后又在irq_exit里面,再次进行判断, 就直接退出了。
就是这个软中断处理函数, 不会被多次进入。
trace_softirq_exit(h, softirq_vec);
3. 即执行了invoke_softirq()函数。
这里invoke_softirq是个宏, 如果说, 在CPU Arch上面IRQ退出的时候,Irq是disable的,则直接等同于 __do_softirq
而如果是enable的,则调用do_softirq, 这个函数里面呢,则先关中断,然后调用__do_softirq, 然后在开中断。
4. 由此可见, 在__do_softirq里面, 进来之前,中断是被关了的。
5. __do_softirq函数里面呢,
0. 先local_irq_enable, 也就是说,
在执行软中断对应的处理函数的时候, 中断是打开的。
1. 循环执行挂在softirq_vector里面的函数
trace_softirq_entry(h, softirq_vec);
h->action(h);
trace_softirq_exit(h, softirq_vec);
2. 做完上面的循环之后呢, 尝试若干次(10次,代码里面定义好)之后,则如果还有软中断没有处理,
调用wakeup_softirqd函数来处理。
3. 然后呢,接下来
6. wakeup_softirqd函数里面,调用了wake_up_process(tsk);函数。
7.
10. 软中断的处理
1. 除了上面所说的在中断返回的时候, 在irq_exit里面,会处理软中断
2. 同时呢, 在ksoftirqd daemon线程里面, 也会定期来处理。
3. 但是在执行软中断处理函数之前,已经打开中断了(如5.0所示)
4. 中断处理函数(也包含软中断)里面是不能睡眠的,这里只是中断处理例程,睡眠了, 谁来调度再次执行呀。