Linux驱动技术(六) _内核中断
在硬件上,中断源可以通过中断控制器向CPU提交中断,进而引发中断处理程序的执行,不过这种硬件中断体系每一种CPU都不一样,而Linux作为操作系统,需要同时支持这些中断体系,如此一来,Linux中就提出了软中断的概念,也有人叫内核中断,其本质就是使用统一的方式对不同硬件中断体系中的中断号进行再映射,在操作系统中操作的中断号都是这些映射过的软中断号。就是下图中的irq_num
[blockquote]
[/blockquote]
Linux内核中定义了名为irq_desc的中断例程描述符表来描述中断服务例程,每一个irq_desc对象数组中的下标就是软中断号。其中的struct irqaction对象描述了这个中断服务例程的中断的具体信息。
//include/linux/irqdesc.h 40 struct irq_desc { 41 struct irq_data irq_data; 42 unsigned int __percpu *kstat_irqs; 43 irq_flow_handler_t handle_irq; 44 #ifdef CONFIG_IRQ_PREFLOW_FASTEOI 45 irq_preflow_handler_t preflow_handler; 46 #endif 47 struct irqaction *action; /* IRQ action list */ 48 unsigned int status_use_accessors; 49 unsigned int core_internal_state__do_not_mess_with_it; 50 unsigned int depth; /* nested irq disables */ 51 unsigned int wake_depth; /* nested wake enables */ 52 unsigned int irq_count; /* For detecting broken IRQs */ 53 unsigned long last_unhandled; /* Aging timer for unhandled count */ 54 unsigned int irqs_unhandled; 55 raw_spinlock_t lock; 56 struct cpumask *percpu_enabled; 57 #ifdef CONFIG_SMP 58 const struct cpumask *affinity_hint; 59 struct irq_affinity_notify *affinity_notify; 60 #ifdef CONFIG_GENERIC_PENDING_IRQ 61 cpumask_var_t pending_mask; 62 #endif 63 #endif 64 unsigned long threads_oneshot; 65 atomic_t threads_active; 66 wait_queue_head_t wait_for_threads; 67 #ifdef CONFIG_PROC_FS 68 struct proc_dir_entry *dir; 69 #endif 70 int parent_irq; 71 struct module *owner; 72 const char *name; 73 } ____cacheline_internodealigned_in_smp; 74
76 extern struct irq_desc irq_desc[NR_IRQS]; //NR_IRQS表示中断源的数目
//linux/interrupt.h 104 //一个irq action的描述结构 105 struct irqaction { 106 irq_handler_t handler; 107 void *dev_id; 108 void __percpu *percpu_dev_id; 109 struct irqaction *next; 110 irq_handler_t thread_fn; 111 struct task_struct *thread; 112 unsigned int irq; 113 unsigned int flags; 114 unsigned long thread_flags; 115 unsigned long thread_mask; 116 const char *name; 117 struct proc_dir_entry *dir; 118 } ____cacheline_internodealigned_in_smp;
[blockquote]
struct irqaction --105-->handler: 中断处理函数 --106-->name: 设备名 --107-->dev_id: 设备识别id --109-->next: 指向下一个irqaction的指针 --110-->irq: 硬件中断号!!! --113-->flags:IRQF_DISABLED .etc --110-->thread_fn: 线程中断的中断处理函数 --111-->thread: 线程中断的线程指针 --114-->thread_flags: 与@thread关联的flags --115-->thread_mask: 追踪@thread activity的位掩码 --116-->dir: 指向proc/irq/NN/name的入口指针
[/blockquote]
raw_local_irq_save(x) //禁止所有中断 raw_local_irq_enable //取消禁止中断
写中断处理程序
1. 编写中断处理函数
下面这个就是中断处理程序的原型,中断发生后,内核会将软中断号和注册时的data作为参数传入。中断处理程序ISR是在中断发生时被调用的用来处理中断的函数,不是进程上下文,在中断期间运行,不能执行可能休眠的操作,不能同用户空间交换数据,不能调用schedule()函数放弃调度 实现中断处理有一个原则:尽可能快的处理并返回,冗长的计算处理工作应该交给tasklet或任务队列在安全的时间内进行。此外,硬件系统中常使用共享中断,即多个设备共享一根线。所以在中断处理程序中要添加中断识别代码,通常就是读取寄存器,看具体是哪个中断被触发了。
88 typedef irqreturn_t (*irq_handler_t)(int, void *);
irqreturn_t xxx_interrupt(int irq, void *dev_id) { ... int status = read_irq_status(); /* 读取相应的寄存器获取中断源 */ if(!is_myirq(dev_id,status)){ /* 判断是否是本设备中断 */ return IRQ_NONE; } /* 中断处理程序 */ return IRQ_HANDLED }
2. 注册中断处理函数
下面这个就是注册中断的API,其中最重要的就是flags参数,它的取值在interrupt.h有定义,常用的有IRQF_DISABLED和IRQF_SHARED,前者表示中断程序调用时屏蔽所有中断,IRQF_SHARED表示多个设备共享中断,即该中断号可以被多个设备共享-SPI(另外两种SGI,PPI),此外,还要"位或"上触发方式,eg:IRQF_DISABLED|IRQF_TRIGGER_RISING
/** * @flags:中断标志位。 * @dev_id通常用于共享中断,用来标识是哪个设备触发了中断 * @name 是中断名称,可以在/proc/interrupt中看到 */ int requst_irq(unsigned int irq,irq_handler_t handler, unsigned long flags, const char *name,void * dev_id);
3. 释放中断处理函数
中断号也是一种系统资源,使用完毕后要释放,注意,free_irq的第二个参数应当与request_irq()中最后一个参数相同。
/** * free_irq - 释放irq */ void free_irq(unsigned int irqno,void * dev_id);
底半部
中断不属于进程上下文,所以不能被内核调度,如果进入了中断处理函数,就只能将其执行完毕,不能被打断,这样带来的一个问题是如果在中断处理函数中执行耗时操作,就会极大的影响系统性能,为了解决这个问题,Linux内核中提出了中断顶半部和`底半部(bottom half)的概念,对于耗时的中断处理程序,将重要的、必要的操作放在顶半部执行,这部分和传统的中断概念一样,一旦开始就必须执行完毕;将中断处理程序中耗时的,不那么紧要的操作放在底半部,防止整个中断处理程序过多的占用系统资源。 Linux内核提供的3种中断底半部机制:工作队列,tasklet,软中断。其中软中断机制是内核底层的机制,包括定时器,异步通知等很多机制都是基于软中断,开发者不应该直接操作,所以这里仅讨论前两种
Tasklet
每个tasklet都和一个函数相关联,当tasklet被运行时,该函数就被调用,并且tasklet可以调度自己。
//定义一个处理函数 void my_tasklet_fcn(unsigned long){} //定义一个tasklet结构my_tasklet,并与处理函数相关联, DECLARE_TASKLET(my_tasklet,my_tasklet_fcn,data); //调度tasklet tasklet_schedule(&my_tasklet);
工作队列
工作队列和tasklet类型,tasklet多运行于中断上下文,而工作队列多运行与进程上下文 此外,tasklet中不能睡眠,而工作队列处理函数允许睡眠(正是由于它被当作内核线程被调度)
//定义一个工作队列 struct work_queue my_wq; //定义一个处理函数 void my_wq_fcn(unsigned long){} //初始化工作队列并将其与处理函数绑定 INIT_WORK(&my_wq,my_wq_fcn); //调度工作队列执行 schedule_work(&my_wq);
static irqreturn_t handler_t(int irq, void *dev) { //top half schedule_work(&ws); return IRQ_HANDLED; } void work_func(struct work_struct *work) { //bottom half ssleep(3); } static int __init demo_init(void) { ... INIT_WORK(&ws, work_func); request_irq(irq, handler_t, IRQF_TRIGGER_RISING, "demo", NULL); ... }
其他API
除了上述API,内核还提供了其他的中断操作API,在内核代码中被广泛使用。
raw_local_irq_save(x) //禁止所有中断 raw_local_irq_enable //取消禁止中断 //下面三个函数用于可编程中断控制器,对所有CPU都有效 //屏蔽一个中断 void disable_irq(int irq); //立即返回 void disable_irq_nosync(); //等待目前中断处理返程 //使能一个中断 void enable_irq()