// 中断入口 // 注:gnu 每个符号分属global(被输出)和local(不被输出)两类中的一种。 1.1 #define ENTRY(name) \ .globl name; \ ALIGN; \//之后的代码对齐到32字节,使用NOP(0x90)补齐 name: // 代码对齐 // .align(n) power-of-2对齐 // 4 对齐到16字节, 5 对齐到32字节 // 0x90 NOP 指令的机器码,用于填充到指定的对齐字节 1.2 #define ALIGN .align 4,0x90 // 可屏蔽中断入口 // 1.IRQn中断处理程序所在的地址开始是保存在interrupt[n]之中的,之后才复制到IDT相应的表项中断门中 // 2..text段连续存储在一起,.data段连续存储在一起 // 3.最终在内存中: // 3.1 所有可屏蔽中断的入口地址依次连续存储在.data段,interrupt保存数组起始地址 // interrupt -> // interrupt[0] // interrupt[1] // interrupt[2] // . // . // interrupt[255] // 3.2 所有可屏蔽中断处理函数依次连续存储在.text段,irq_entries_start保存数组起始地址 // irq_entries_start -> // pushl -256 // jmp common_interrupt // pushl -255 // jmp common_interrupt // pushl -254 // jmp common_interrupt // . // . // pushl -1 // jmp common_interrupt 1.3 .data //数据段 ENTRY(interrupt) .text //代码段 // IRQ0~IRQ255 vector=0 ENTRY(irq_entries_start) .rept NR_IRQS //.rept,.endr之间的代码展开255次 ALIGN 1: pushl $vector-256 //IRQ号取负 jmp common_interrupt .data //数据段,会与35行合并,所有数据段连续存储起来 .long 1b // 标签1的地址 .text vector=vector+1 .endr //32行重复结束 ALIGN common_interrupt: //所有可屏蔽中断函数的公共部分 SAVE_ALL //寄存器入栈 movl %esp,%eax // eax保存栈顶指针 call do_IRQ //中断处理函数 jmp ret_from_intr 1.4 #define SAVE_ALL \ cld; \ //清除方向标志 pushl %es; \ pushl %ds; \ pushl %eax; \ //eax保存中断号 pushl %ebp; \ pushl %edi; \ pushl %esi; \ pushl %edx; \ pushl %ecx; \ pushl %ebx; \ movl $(__USER_DS), %edx; \ //es,ds指向用户数据段 movl %edx, %ds; \ movl %edx, %es; // 中断入口函数 // 参数:regs,通过eax传递被中断进程或被中断中断的上下文 // 函数主要任务: // 1.递增中断嵌套计数器 // 2.切换内核栈 // 2.1 4k核心栈,并且当前在进程上下文 // 3.__do_IRQ统一入口 // 3.1 向芯片屏蔽并确认中断 // 3.2 执行中断处理函数 // 3.3 解除屏蔽 // 4.退出中断 // 4.1 递减中断嵌套计数器 // 4.2 执行软中断,或调度进程执行 1.5 fastcall unsigned int do_IRQ(struct pt_regs *regs) { int irq = regs->orig_eax & 0xff; //eax低8位保存中断号 #ifdef CONFIG_4KSTACKS //每个线程使用4k的内核堆栈,中断使用独立的栈 union irq_ctx *curctx, *irqctx; u32 *isp; #endif //递增current->preempt_count中的中断嵌套计数 irq_enter(); #ifdef CONFIG_4KSTACKS //irq_ctx与thread_union结构相同,因此可以相互转换 curctx = (union irq_ctx *) current_thread_info(); //本cpu的中断栈 irqctx = hardirq_ctx[smp_processor_id()]; //当前在进程上下文,则需要切换到中断栈 //当前在中断上下文,则无需切换 if (curctx != irqctx) { //进程被中断 int arg1, arg2, ebx; //中断栈栈顶的位置 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" //交换esp,ebx,交换前ebx的值为被中断进程的用户代码段,esp为核心栈 " call __do_IRQ \n" " movl %%ebx,%%esp \n" : "=a" (arg1), "=d" (arg2), "=b" (ebx) //返回值:arg1=eax, arg2=edx, ebx=ebx : "0" (irq), "1" (regs), "2" (isp) //形参结合:$0=irq, $1=regs, $2=isp : "memory", "cc", "ecx" ); } else #endif __do_IRQ(irq, regs); //退出中断处理 irq_exit(); return 1; } // 中断通用处理函数 // 函数主要任务: // 1.如果为cpu本地中断 // 1.1 向中断控制器ack // 1.2 调用共享该中断号的所有中断处理程序 // 1.3 向中断控制器end // 2.否则 // 2.1 获取中断描述符锁 // 2.2 向中断控制器显示确认中断 // 2.3 设置IRQ_PENDING表示中断还没有被处理 // 2.4 如果当前中断没有被禁用,并且中断处理程序没有在运行中 // 2.4.1 清除IRQ_PENDING,设置IRQ_INPROGESS, 表示中断处理开始 // 2.4.2 释放irq描述符锁,执行中断处理函数 // 2.4.3 检查在中断处理函数执行过程中,此中断是否再次发生 // 2.4.3.1 如果pending设置,表示在中断函数处理过程中,此中断再次发生,再次执行处理函数 // 2.4.3.2 否则退出 // 2.5 向中断控制器end // 注: // 1.互斥使用中断控制器进行ack,mask,end。 // 2.中断处理函数irq_desc_t->action在cpu间可以并行 // 3.通过irq_desc_t->status = // IRQ_INPROGRESS 保证中断处理函数同时只有一个在执行 // IRQ_PENDING 保证在中断处理函数在执行时,可以记录第二次发生的中断 // 1.6 fastcall unsigned int __do_IRQ(unsigned int irq, struct pt_regs *regs) { //irq号对应的中断描述符 irq_desc_t *desc = irq_desc + irq; struct irqaction * action; unsigned int status; //统计信息 kstat_this_cpu.irqs[irq]++; //cpu本地中断 if (desc->status & IRQ_PER_CPU) { irqreturn_t action_ret; //向中断控制器显式确认 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); //IRQ_REPLAY IRQ已经被禁用 //IRQ_WAITING IRQ的自动检测 status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING); //IRQ_PENDING cpu注意到中断,但是尚未处理它 status |= IRQ_PENDING; //当前中断没有被禁用,当前中断没有在执行 action = NULL; if (likely(!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))) { action = desc->action; //清除IRQ_PENDING,设置IRQ_INPROGRESS表示马上执行处理函数 status &= ~IRQ_PENDING; status |= IRQ_INPROGRESS; } desc->status = status; if (action == NULL) goto out; //在处理中断的过程中,在此发生的同一irq,通过pending标识 for (;;) { irqreturn_t action_ret; //中断处理函数可以在cpu间并行执行 spin_unlock(&desc->lock); action_ret = handle_IRQ_event(irq, regs, action); spin_lock(&desc->lock); //没有新到来的中断,退出 if (likely(!(desc->status & IRQ_PENDING))) break; desc->status &= ~IRQ_PENDING; } desc->status &= ~IRQ_INPROGRESS; out: //通知中断控制器已经处理完中断 desc->handler->end(irq); spin_unlock(&desc->lock); return 1; }
时间: 2024-12-29 03:30:46