天天看点

中断与异常详解(三)

再次梳理会用到的一些数据结构和名词

中断向量表(中断描述符表) idt_table 全局,8字节64位,从低到高位16位段选择符,32位偏移量,16位状态信息 256项

起始地址在内核数据节的idt中

用于寻找各种门,门的作用是防止用户程序访问陷阱门、中断门等特殊资源,出于安全考虑,linux为用户留有3,4,5,128号系统调用门供用户使用

中断描述符表寄存器 IDTR 寄存器,6字节48位,低16位界限,高32位基址 1项 用于快速寻找中断描述符,linux定义的16位界限为8*256-1,即使用了2048字节来存储中断向量表;32位基址就是idt地址,整个48位内容在内存的位置为idt地址-2
中断服务程序数组 Irq_desc 全局,20字节160位,5个unsigned int 224项 中断线状态status,中断控制器描述符hw_irq_controller和中断服务例程链表irqaction,中断嵌套depth和多cpu锁lock,通过这些信息可以对中断线进行管理
中断服务例程描述符 irqaction 结构体,24字节,6个32位 用于挂载到中断线服务例程链表上的结构体,真正的对中断进行个性化处理,也是设备驱动程序主要实现内容
现场寄存器结构 pt_regs 函数参数,60字节,15个32位 通常作为中断或异常处理程序的参数,这些函数前面通常有asmlinkage,通过栈传参数,即第一个参数就是ESP

《深入分析linux内核》一书74页对request_irq()函数解释为“将对应的中断服务例程挂入中断请求队列”,我产生了一些误解,个人认为解释为“请求将中断服务例程挂入中断服务队列(单链表)”更好理解一点,因为内核初始化挂载中断服务例程都是直接操作断服务例程的指针的,服务例程在内核而且肯定存在内存,所以不用管这些,但其他设备初始化的时候,挂载操作就需要严格要求,比如全局唯一div_id,中断号大小,申请空间存储中断服务例程指针等都需要确认,所以需要先请求,这也就是对挂载操作封装一层request_irq()的原因吧,所以只留一个中断服务队列的说法,请求队列容易误解。

接下来就说说do_irq()

asmlinkage unsigned int do_IRQ(struct pt_regs regs)

{        

int irq = regs.orig_eax & 0xff; 因为之前压栈压的irq-256,所以这里需要& 0xff来恢复,真是神奇,&可以这样用,irq在0-223之间
int cpu = smp_processor_id(); 获取cpu号
irq_desc_t *desc = irq_desc + irq; 获取中断服务描述符(中断服务程序)的入口,不是中断服务例程描述符哦
struct irqaction * action;
unsigned int status;
kstat.irqs[cpu][irq]++; 记录中断请求次数
spin_lock(&desc->lock); 锁cpu,中断设计成单cpu不可重入,因而一条中断线在一个时间点只能由一个cpu来处理
desc->handler->ack(irq); 对中断请求给予确认,也不知道确认什么,8259A控制器里面都没这个函数。。
status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING); 设置状态为未删除,非探测,因为是由硬件真正发出的中断请求
status |= IRQ_PENDING;  接受中断,并准备处理
action = NULL; 中断服务例程队列(单链表)初始为空
if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) { 如果不是中断线被禁用或者有中断服务例程正在处理
          action = desc->action; 获得中断服务例程队列
          status &= ~IRQ_PENDING; 提交处理
          status |= IRQ_INPROGRESS; 开始处理
}
desc->status = status; 更新中断线状态
if (!action) 如果获取中断服务队列失败
         goto out; 退出
for (;;) { 循环
           spin_unlock(&desc->lock); 释放多cpu锁,但此时中断线状态是IRQ_INPROGRESS,所以即使其他cpu无法获取当前中断线的中断服务例程队列,而且此时应该是关中断状态吧,这里有些疑问
          handle_IRQ_event(irq, &regs, action); 中断服务例程队列对中断进行处理,就是让队列里的每个例程都去试着处理一下
          spin_lock(&desc->lock); 获取多cpu锁,由于上面的中断服务例程处理会先关中断,然后再开中断,就有可能在关中断后获取锁之前再发生一次中断,也就是中断嵌套,虽然这应该尽量避免。但不理解的是即使这条中断线再次发生中断,也因为status为IRQ_INPROCESS而无法设置status为IRQ_PENDING啊,难道是怕驱动程序设置这个status?反正操作系统为我们考虑很全面了,有嵌套也能在这儿串行执行
         if (!(desc->status & IRQ_PENDING)) 再检测中断线是否有中断请求,如果没有嵌套。更新:既然上面说了这条中断线可能又来一个中断,那么就有可能被其他cpu甚至自己捕获并设置了IRQ_PENDING状态,但因为当前为IRQ_INPROCESS状态,这次新的中断请求时无法被他们处理的,所以需要再次处理
                     break; 退出
         desc->status &= ~IRQ_PENDING; 提交请求
} 再次处理中断
desc->status &= ~IRQ_INPROGRESS; 恢复中断线为没有中断例程正在服务状态

out:

desc->handler->end(irq); 不明,根据标志位是否启用中断线吧?
spin_unlock(&desc->lock); 释放多cpu锁
if (softirq_pending(cpu)) 不明,64位cpu才有的数据结构
       do_softirq(); 处理软中断
return 1;

}

转载于:https://www.cnblogs.com/hmxb/p/4904758.html

继续阅读