您的位置首页>企业动态>

Linux驱动技术之一内核中断

导读大家好,我是极客范的本期栏目编辑小友,现在为大家讲解Linux驱动技术之一内核中断问题。在硬件上,中断源可以通过中断控制器向中央处理器

大家好,我是极客范的本期栏目编辑小友,现在为大家讲解Linux驱动技术之一内核中断问题。

在硬件上,中断源可以通过中断控制器向中央处理器提交中断,进而引发中断处理程序的执行,不过这种硬件中断体系每一种中央处理器都不一样,而Linux操作系统操作系统作为操作系统,需要同时支持这些中断体系,如此一来,Linux中就提出了软中断的概念,也有人叫内核中断,其本质就是使用统一的方式对不同硬件中断体系中的中断号进行再映射,在操作系统中操作的中断号都是这些映射过的软中断号。就是下图中的irq_num

Linux操作系统操作系统内核中定义了名为中断请求的中断例程描述符表来描述中断服务例程,每一个中断请求对象数组中的下标就是软中断号。其中的结构动作对象描述了这个中断服务例程的中断的具体信息。

//包含/Linux/IRQ desc。h 40结构IRQ _ desc { 41结构IRQ _ data IRQ _ data42无符号int _ _ percpu * kstat _ irqs 43 IRQ _ flow _ handler _ t handle _ IRQ;44 # ifdef CONFIG _ IRQ _ PREFFLOW _ FASTOI 45 IRQ _ PREFFLOW _ handler _ t PREFFLOW _ handler;46 # endif 47 struct IrqaActIon * ActIon;48无符号int status _ use _ accessors49无符号int core _ internal _ state _ do _ not _ mess _ with _ it;50无符号整数深度;51无符号int wake _ depth52无符号int irq _ count53无符号长last _未经处理54无符号整数irqs _ unhandled55 raw_spinlock_t锁;56 struct CP umask * PERC pu _ 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 CPU掩码_ var _ t PENDING _ mask62 #endif 63 #endif 64无符号长线程_ oneshot65个原子t线程_活动;66个wait _ queue _ head _ t wait _ for _ threads;67 # ifdef CONFIG _ PROC _ FS 68 struct PROC _ dir _ entry * dir;69 # endif 70 int parent _ irq71结构模块*所有者;72个常量字符*名称;73 } _ _ _缓存行_ internodelized _ in _ SMP74

76外部结构desc desc;//NR_IRQS表示中断源的数目

//linux/interrupt.h104 //一个中断请求动作的描述结构105结构IRQ动作{ 106 IRQ _ handler _ t handler107 void * dev _ id 108 void _ _ percpu * percpu _ dev _ id 109 struct IRQ action * next 110 IRQ _ handler _ t thread _ fn 111 struct task _ struct *线程;112无符号整数irq113个无符号整数标志;114个无符号长thread _ flags115无符号长thread _ mask116个常量字符*名称;117 struct proc _ dir _ entry * dir 118 } _ _ _ _ cache line _ intermodelized _ in _ SMP;

结构动作

- 105 - handler:中断处理函数

- 106 -名称:设备名

- 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的入口指针

raw_local_irq_save(x) //禁止所有中断raw_local_irq_enable //取消禁止中断

写中断处理程序

1. 编写中断处理函数

下面这个就是中断处理程序的原型,中断发生后,内核会将软中断号和注册时的data作为参数传入。中断处理程序ISR是在中断发生时被调用的用来处理中断的函数,不是进程上下文,在中断期间运行,不能执行可能休眠的操作,不能同用户空间交换数据,不能调用schedule()函数放弃调度实现中断处理有一个原则:尽可能快的处理并返回,冗长的计算处理工作应该交给tasklet或任务队列在安全的时间内进行。此外,硬件系统中常使用共享中断,即多个设备共享一根线。即该(软硬)中断号可以被多个设备共享,这在实际的硬件连接中经常见到,可以节约很多资源,但是如此一来,就给软件的编写的提出了要求,内核给出的方案是一旦接收到来自一条中断线的中断,它就会循环执行所有注册到该中断线(->硬中断号->软中断号)的handler,这样,每一个handler就有义务判断到底是不是自己负责的设备来的中断,handler原型的dev_id也正是为了这个目的而存在,我们可以将该handler负责的设备的中断状态寄存器的地址作为dev_id和handler一起注册,当内核遍历执行所有的handler的时候,会将每一个中断的dev_id作为参数依次传入每一个handler,在每一个handler内部首次通过读取这个寄存器来快速判断是否是自己负责的设备发出的。这也就是共享中断必须设置dev_id参数的原因之一。至此,就可以实现多个设备对这一中断线的"shared"。注意,对于这个"shared",并不是在时间上允许多个中断同时发生,而是一种空间上的、中断号上的"shared",此外,这个"shared"和三星芯片中常见的Shared Peripheral Interrupts(SPI)不是一回事,后者表示这个中断可以被GIC router到任意一个CPU中。

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表示多个设备共享中断,即该中断线上连接了多个相同或不同的设备。除了中断类型,flags还需要"位或"上触发方式,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()中最后一个参数相同,这样才能将这个handler从这个中断线中的handler链表中删除。

/** * 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);//调度tasklettasklet_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() 

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。