📄 2.html
字号:
IRQ 0 [Timer]<br>
|<br>
\|/<br>
|IRQ0x00_interrupt // wrapper IRQ handler<br>
|SAVE_ALL ---<br>
|do_IRQ | wrapper routines<br>
|handle_IRQ_event ---<br>
|handler() -> timer_interrupt // registered IRQ 0 handler<br>
|do_timer_interrupt<br>
|do_timer<br>
|jiffies++;<br>
|update_process_times<br>
|if (--counter <= 0) { // if time slice ended then<br>
|counter = 0; // reset counter<br>
|need_resched = 1; // prepare to reschedule<br>
|}<br>
|do_softirq<br>
|while (need_resched) { // if necessary<br>
|schedule // reschedule<br>
|handle_softirq<br>
|}<br>
|RESTORE_ALL<p>
·IRQ0x00_interrupt, SAVE_ALL [include/asm/hw_irq.h]<br>
·do_IRQ, handle_IRQ_event [arch/i386/kernel/irq.c]<br>
·timer_interrupt, do_timer_interrupt [arch/i386/kernel/time.c]<br>
·do_timer, update_process_times [kernel/timer.c]<br>
·do_softirq [kernel/soft_irq.c]<br>
·RESTORE_ALL, while loop [arch/i386/kernel/entry.S]<p>
系统启动核心时,调用start_kernal()继续各方面的初始化,在这之前,各种中断都被禁止,只有在完成必要的初始化后,直到执行完Kmalloc_init()后,才允许中断(init\main.c)。与时钟中断有关的部分初始化如下:<p>
调用trap_init()设置各种trap入口,如system_call、GDT entry、LDT entry、call gate等。其中0~17为各种错误入口,18~47保留。<p>
调用init_IRQ()函数设置核心系统的时钟周期为10ms,即100HZ,它是以后按照轮转法进行CPU调度时所依照的基准时钟周期。每10ms产生的时钟中断信号直接输入到第一块8259A的INT 0(即irq0)。初始化中断矢量表中从0x20起的17个中断矢量,用bad_IRQ#_interrupt函数的地址(#为中断号)填写。<p>
调用sched_init()函数,设置启动第一个进程init_task。设置用于管理bottom_half机制的数据结构bh_base[],规定三类事件的中断处理函数,即时钟TIMER_BH、设备TQUEUE_BH和IMMEDIATE_BH。<p>
调用time_init()函数,首先读取当时的CMOS时间,最后调用setup_x86_irq(0,&irq0)函数,把irq0挂到irq_action[0]队列的后面,并把中断矢量表中第0x20项,即timer中断对应的中断矢量改为IRQ0_interrupt函数的地址,在irq0中,指定时间中断服务程序是timer_interrupt,<br>
static struct irqaction irq0 = { timer_interrupt, 0, 0, "timer", NULL, NULL}<br>
结构irqaction的定义如下:<br>
struct irqaction {<br>
void (*handler)(int, void *, struct pt_regs *); /* 中断服务函数入口 */<br>
unsigned long flags; /* 服务允中与否标记 */<br>
unsigned long mask;<br>
const char *name;<br>
void *dev_id;<br>
struct irqaction *next;<br>
};<br>
其中,若flag==SA_INTERRUPT,则中断矢量改为fast_IRQ#_interrupt,在执行中断服务的过程中不允许出现中断,若为其它标记,则中断矢量为IRQ#_interrupt,在执行中断服务的过程中,允许出现中断。<br>
Irq_action的定义与初始化如下:<br>
static void (*interrupt[17])(void) = {IRQ#_interrupt};<br>
static void (*fast_interrupt[16])(void) = {fast_IRQ#_interrupt};<br>
static void (*bad_interrupt[16])(void) = {bad_IRQ#_interrupt};(以上#为中断号)<br>
static struct irqaction *irq_action[16] = {<br>
NULL, NULL, NULL, NULL,<br>
NULL, NULL, NULL, NULL,<br>
NULL, NULL, NULL, NULL,<br>
NULL, NULL, NULL, NULL<br>
};<p>
irq_action是一个全局数组,每个元素指向一个irq队列,共16个irq队列,时钟中断请求队列在第一个队列,即irq_action[0]。当每个中断请求到来时,都调用setup_x86_irq把该请求挂到相应的队列的后面。<p>
以后,系统每10ms产生一次时钟中断信号,该信号直接输入到第一块8259A的INT 0(即irq0)。CPU根据中断矢量表和中断源,找到中断矢量函数入口IRQ0_interrupt(程序运行过程中允许中断)或者fast_IRQ0_interrupt(程序运行过程中不允许中断)或者bad_IRQ0_interrupt(不执行任何动作,直接返回),这些函数由宏BUILD_TIMER_IRQ(chip, nr, mask)展开定义。<br>
宏BUILD_TIMER_IRQ(chip, nr, mask)的定义如下:<br>
#define BUILD_TIMER_IRQ(chip,nr,mask) \<br>
asmlinkage void IRQ_NAME(nr); \<br>
asmlinkage void FAST_IRQ_NAME(nr); \<br>
asmlinkage void BAD_IRQ_NAME(nr); \<br>
__asm__( \<br>
"\n"__ALIGN_STR"\n" \<br>
SYMBOL_NAME_STR(fast_IRQ) #nr "_interrupt:\n\t" \<br>
SYMBOL_NAME_STR(bad_IRQ) #nr "_interrupt:\n\t" \<br>
SYMBOL_NAME_STR(IRQ) #nr "_interrupt:\n\t" \<br>
"pushl $-"#nr"-2\n\t" \<br>
SAVE_ALL \<br>
ENTER_KERNEL \<br>
ACK_##chip(mask,(nr&7)) \<br>
"incl "SYMBOL_NAME_STR(intr_count)"\n\t"\ /* intr_count为进入临界区的同步信号量 */<br>
"movl %esp,%ebx\n\t" \<br>
"pushl %ebx\n\t" \<br>
"pushl $" #nr "\n\t" \ /* 把do_irq函数参数压进堆栈 */<br>
"call "SYMBOL_NAME_STR(do_IRQ)"\n\t" \<br>
"addl $8,%esp\n\t" \<br>
"cli\n\t" \<br>
UNBLK_##chip(mask) \<br>
"decl "SYMBOL_NAME_STR(intr_count)"\n\t" \<br>
"incl "SYMBOL_NAME_STR(syscall_count)"\n\t" \<br>
"jmp ret_from_sys_call\n");<p>
其中nr为中断请求类型,取值0~15。在irq.c中通过语句BUILD_TIMER_IRQ(first, 0, 0x01)调用该宏,在执行宏的过程中处理时钟中断响应程序do_irq()。<p>
函数do_irq()的第一个参数是中断请求队列序号,时钟中断请求传进来的该参数是0。于是程序根据参数0找到请求队列irq_action[0],逐个处理该队列上handler所指的时钟中断请求的服务函数。由于已经指定时钟中断请求的服务函数是timer_interrupt,在函数timer_interrupt中,立即调用do_timer()函数。<p>
函数do_timer()把jiffies和lost_ticks加1,接着就执行mark_bh(TIMER_BH)函数,把bottom_half中时钟队列对应的位置位,表示该队列处于激活状态。在做完这些动作后,程序从函数do_irq()中返回,继续执行以后的汇编代码。于是,程序在执行语句jmp ret_from_sys_call后,跳到指定的位置处继续执行。<p>
代码段jmp ret_from_sys_call及其相关的代码段如下:<br>
ALIGN<br>
.globl ret_from_sys_call<br>
ret_from_sys_call:<br>
cmpl $0,SYMBOL_NAME(intr_count)<br>
jne 2f<br>
9: movl SYMBOL_NAME(bh_mask),%eax<br>
andl SYMBOL_NAME(bh_active),%eax<br>
jne handle_bottom_half<br>
#ifdef __SMP__<br>
cmpb $(NO_PROC_ID), SYMBOL_NAME(saved_active_kernel_processor)<br>
jne 2f<br>
#endif<br>
movl EFLAGS(%esp),%eax # check VM86 flag: CS/SS are<br>
testl $(VM_MASK),%eax # different then<br>
jne 1f<br>
cmpw $(KERNEL_CS),CS(%esp) # was old code segment supervisor ?<br>
je 2f<br>
1: sti<br>
orl $(IF_MASK),%eax # these just try to make sure<br>
andl $~NT_MASK,%eax # the program doesn't do anything<br>
movl %eax,EFLAGS(%esp) # stupid<br>
cmpl $0,SYMBOL_NAME(need_resched)<br>
jne reschedule<br>
#ifdef __SMP__<br>
GET_PROCESSOR_OFFSET(%eax)<br>
movl SYMBOL_NAME(current_set)(,%eax), %eax<br>
#else<br>
movl SYMBOL_NAME(current_set),%eax<br>
#endif<br>
cmpl SYMBOL_NAME(task),%eax # task[0] cannot have signals<br>
je 2f<br>
movl blocked(%eax),%ecx<br>
movl %ecx,%ebx # save blocked in %ebx for signal handling<br>
notl %ecx<br>
andl signal(%eax),%ecx<br>
jne signal_return<br>
2: RESTORE_ALL<p>
ALIGN<br>
signal_return:<br>
movl %esp,%ecx<br>
pushl %ecx<br>
testl $(VM_MASK),EFLAGS(%ecx)<br>
jne v86_signal_return<br>
pushl %ebx<br>
call SYMBOL_NAME(do_signal)<br>
popl %ebx<br>
popl %ebx<br>
RESTORE_ALL<p>
ALIGN<br>
v86_signal_return:<br>
call SYMBOL_NAME(save_v86_state)<br>
movl %eax,%esp<br>
pushl %eax<br>
pushl %ebx<br>
call SYMBOL_NAME(do_signal)<br>
popl %ebx<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -