本次以bbblack板子的内核为研究,中断向量表IDT,有CPU的本身的中断,还有外设的中断,其中CPU的中断里面的INT 0X80是软中断,很多系统调用就是用的这个中断。
其中外设的中断,可能多个外设共用一个中断号,所以猜测每一个中断号都有一个单独的中断请求队列,比如串口和定时器共用一个中断号,那么这两个中断都发送到同一个中断请求队列给CPU去处理,仅为猜测。
不过看书,在IDT的初始化阶段只是为每个中断向量,也即每一个表项准备一个中断请求队列,从而形成一个中断请求队列的数组irq_desc[NR_IRQS];。然后我去看下代码。
在\bb-black-kernel-3.8\kernel\kernel\include\linux找到这个数组。 Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。
struct irq_desc {
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
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;
raw_spinlock_t lock;
struct cpumask *percpu_enabled;
unsigned long threads_oneshot;
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;
extern struct irq_desc irq_desc[NR_IRQS];
研究下具体中断是怎么实现的。看下struct irqaction *action;这个结构体,其中最重要的是函数指针handler,指向具体的中断服务程序。其中thread_fn是线程的中断,对这个东西不是很清楚,不作为此次研究的重点。
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;
依然要回到原来的问题,串口和定时器共用一个中断号的时候,在代码级是怎么实现的?真正的中断服务要到具体设备的初始化程序将其中断服务程序通过request_threaded_irq()向系统登记,挂入某个中断请求队列以后才会发生。找到这个函数\bb-black-kernel-3.8\kernel\kernel\kernel\irq\manage.c,这个就是先申请了一个内存,存放中断处理函数的指针
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
if ((irqflags & IRQF_SHARED) && !dev_id)
return -EINVAL;
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
if (!irq_settings_can_request(desc) ||
WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
chip_bus_lock(desc);
retval = __setup_irq(irq, desc, action);
chip_bus_sync_unlock(desc);
if (retval)
kfree(action);
return retval;
}
看下__setup_irq这个函数,找到这个函数
\bb-black-kernel-3.8\kernel\kernel\kernel\irq\manage.c,用户驱动程序通过request_irq函数向内核注册中断处理函数,request_irq函数根据中断号找到irq_desc数组项,然后在它的action链表添加一个表项。
用一个图片去看下整体的结构图,会更加的直观,可以看到action这个链表,可以添加串口中断服务函数,添加定时器中断服务函数。