linux中断
1,申请中断API函数request_irq()
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
irq:要申请中断的中断号。
handler:中断处理函数。
name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev: 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志,这里我们介绍几个常用的中断标志。
标志 描述
IRQF_SHARED 多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话, request_irq 函数的 dev 参数就是唯一区分他们的标志
IRQF_ONESHOT 单次中断,中断执行一次就结束
IRQF_TRIGGER_NONE 无触发
IRQF_TRIGGER_RISING 上升沿触发
IRQF_TRIGGER_FALLING 下降沿触发
IRQF_TRIGGER_HIGH 高电平触发
IRQF_TRIGGER_LOW 低电平触发
返回值: 0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。
request_irq()函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 。
2,free_irq()
/** * free_irq - free an interrupt allocated with request_irq * @irq: Interrupt line to free * @dev_id: Device identity to free * * Remove an interrupt handler. The handler is removed and if the * interrupt line is no longer in use by any driver it is disabled. * On a shared IRQ the caller must ensure the interrupt is disabled * on the card it drives before calling this function. The function * does not return until any executing interrupts for this IRQ * have completed. * * This function must not be called from interrupt context. */ void free_irq(unsigned int irq, void *dev_id) { struct irq_desc *desc = irq_to_desc(irq); if (!desc) return; chip_bus_lock(irq, desc); kfree(__free_irq(irq, dev_id)); chip_bus_sync_unlock(irq, desc); }
unsigned int irq:要卸载的中断号
void *dev_id:这个是要卸载的中断action下的哪个服务函数
编程注意:
1.如果是采用非共享方式注册中断,则request_irq和free的最后一个参数都要为NULL。
2.如果采用共享中断方式,所有使用request_irq注册的中断时flags都要加上IRQF_SHARED这个共享参数,表明其实共享中断。
3.对于共享中断,每一个申请共享的中断,申请和释放时都要给request_irq和free_irq的最后一个参数dev和id_dev传递一个指针,将来来中断的时候,将会传递这个指针到每个中断函数中,而中断函数就可以用来区分到底是不是它的中断,是则执行,不是则判断后直接退出中断处理函数即可。同时在free_irq时也会使用这个指针,查找这个贡献中断链表上了所有注册的irq,只有在这个指针能对的上的时候,才会删除它所在的链表节点(如果是最后一个节点还要释放该中断)。所在在编写中断处理函数时该指针必须是唯一的,通常传的这个指针是该设备结构体的地址,这个每个设备不一样所以肯定是唯一的。
下面这几句是我查找资料的发现说的比较透彻的,我先引用一下,过几天我抽时间在代码层面分析一下中断的实现机制。
原来对于计算机设备比较少的时候,可能一个中断线好可以对应一个中断处理程序(非共享中断线),这时候参数4为NULL,没有任何用,但随着计算机设备的增加,一个中断线号对应一个中断处理程序已经不太现实,这个时候就使用了共享的中断线号,多个设备使用同一个中断线号,同一个中断设备线号的所有处理程序链接成一个链表,这样当在共享中断线号的方式下一个中断产生的时候,就要遍历其对应的处理程序链表,但这个中断是由使用同一个中断线号的多个设备中间的一个产生的,不可能链表里面的所有处理程序都调用一遍吧,呵呵,这个时候就该第四个参数派上用场了。
因为多个设备共享同一个中断线号,当中断产生的时候到底是那一个设备产生的中断呢,这个就取决于第四个参数dev_id,这个参数必须是唯一的,也就是能区分到底是那个设备产生的中断,而且从第二个参数可以看出来,这个参数被传入中断处理程序(第二个参数),可以这么理解,当中断产生的时候,如果是共享的中断线号,则对应链表的所有中断处理程序都被调用,不过在每个中断处理程序的内部首先检查(参数信息以及设备硬件的支持)是不是这个中断处理程序对应的设备产生的中断,如果不是,立即返回,如果是,则处理完成,如果链表中没有一个是,则说明出现错误。
3,使能和屏蔽中断
1)屏蔽指定中断源
void disable_irq(int irq); void disable_irq_nosync(int irq); void enable_irq(int irq); disable_irq_nosync()与 disable_irq()的区别在于前者立即返回,而后者等待目前的中断处理完成
2)屏蔽所有中断---< asm/system.h >中
void local_irq_save(unsigned long flags); void local_irq_disable(void);
对 local_irq_save的调用将把当前中断状态保存到flags中,然后禁用当前处理器上的中断发送。注意,flags 被直接传递, 而不是通过指针来传递。local_irq_disable不保存状态而关闭本地处理器上的中断发送; 只有我们知道中断并未在其他地方被禁用的情况下,才能使用这个版本。
void local_irq_restore(unsigned long flags); void local_irq_enable(void);
local_irq_restore将local_irq_save保存的flags状态值恢复, 而local_irq_enable无条件打开中断. 与disable_irq不同, local_irq_disable不会维护对多次的调用的跟踪。 如果调用链中有多个函数需要禁止中断, 应该使用local_irq_save。
4,底半部机制
主要包括tasklet,工作队列,软中断,线程化irq
4.1 tasklet
tasklet 是通过软中断实现的, 所以它本身也是软中断。 软中断用轮询的方式处理, 假如正好是最后一种中断, 则必须循环完所有的中断类型, 才能最终执行对应的处理函数。 为了提高中断处理数量, 顺道改进处理效率, 于是产生了 tasklet 机制。 tasklet 采用无差别的队列机制, 有中断时才执行, 免去了循环查表之苦, tasklet 机制的优点: 无类型数量限制, 效率高, 无需循环查表, 支持 SMP 机制, 一种特定类型的 tasklet 只能运行在一个 CPU 上, 不能并行, 只能串行执行。 多个不同类型的 tasklet 可以并行在多个CPU 上。 软中断是静态分配的, 在内核编译好之后, 就不能改变。 但 tasklet 就灵活许多, 可以在运行时改变(比如添加模块时) 。
struct tasklet_struct { struct tasklet_struct *next; /* 下一个 tasklet */ unsigned long state; /* tasklet 状态 */ atomic_t count; /* 计数器, 记录对 tasklet 的引用数 */ void (*func)(unsigned long); /* tasklet 执行的函数 */ unsigned long data; /* 函数 func 的参数 */ };
next: 链表中的下一个 tasklet, 方便管理和设置 tasklet;
state: tasklet 的状态。
count: 表示 tasklet 是否出在激活状态, 如果是 0, 就处在激活状态, 如果非 0, 就处在非激活状态---
void (*func)(unsigned long): 结构体中的 func 成员是 tasklet 的绑定函数, data 是它唯一的参数。
date: 函数执行的时候传递的参数。
如果要使用 tasklet, 必须先定义一个 tasklet, 然后使用 tasklet_init 函数初始化 tasklet, taskled_init 函数原型如下(动态初始化 tasklet):
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data); t---要初始化的 tasklet func---tasklet 的处理函数 data---要传递给 func 函数的参数
也可以使用宏 DECLARE_TASKLET 一次性完成 tasklet 的定义和初始化, DECLARE_TASKLET 定义在include/linux/interrupt.h 文件中, 定义如下:
DECLARE_TASKLET(name, func, data)
其中 name 为要定义的 tasklet 名字, 这个名字就是一个 tasklet_struct 类型的变量, func 就是tasklet 的处理函数, data 是传递给 func 函数的参数。
在需要调度 tasklet 的时候引用一个 tasklet_schedule() 函数就能使系统在适当的时候进行调度运行,该函数原型为如下所示:
void tasklet_schedule(struct tasklet_struct *t) t--要调度的 tasklet, 也就是 DECLARE_TASKLET 宏里面的 name。
杀死 tasklet 使用 tasklet_kill 函数,函数原型如下表所示:(这个函数会等待 tasklet 执行完毕, 然后再将它移除。 该函数可能会引起休眠, 所以要禁止在
中断上下文中使用。)
tasklet_kill(struct tasklet_struct *t) t--要删除的 tasklet
tasklet参考步骤:
1 /* 定义 taselet */ 2 struct tasklet_struct testtasklet; 3 /* tasklet 处理函数 */ 4 void testtasklet_func(unsigned long data) 5 { 6 /* tasklet 具体处理内容 */ 7 } 8 /* 中断处理函数 */ 9 irqreturn_t test_handler(int irq, void *dev_id) 10 { 11 ...... 12 /* 调度 tasklet */ 13 tasklet_schedule(&testtasklet); 14 ...... 15 } 16 /* 驱动入口函数 */ 17 static int __init xxxx_init(void) 18 { 19 ...... 20 /* 初始化 tasklet */ 21 tasklet_init(&testtasklet, testtasklet_func, data); 22 /* 注册中断处理函数 */ 23 request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev); 24 ...... 25 }
总结一下基本步骤为:
步骤一: 定义一个 tasklet 结构体
步骤二: 动态初始化 tasklet
步骤三: 编写 tasklet 绑定的函数
步骤四: 在中断上文调用 tasklet
步骤五: 卸载模块的时候删除 tasklet
4.2 工作队列
工作队列的执行上下文是内核线程,因此可以调度和睡眠,解决了如果软中断和tasklet执行时间过长会导致系统实时性下降等问题。
(1-1)work_struct工作
linux内核中使用work_struct
结构体来表示一个工作,如下定义(/inlcude/linux/workqueue.h):
struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func; #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
(1-2)workqueue工作队列
把工作(包括该工作任务的执行回调函数)添加到一个队列,称为workqueue,即工作队列,然后通过worker-pool中的内核工作线程(worker)去执行这个回调函数。工作队列使用workqueue_struct结构体来表示,定义如下(/kernel/workqueue.c):
1 struct workqueue_struct { 2 struct list_head pwqs; /* WR: all pwqs of this wq */ 3 struct list_head list; /* PR: list of all workqueues */ 4 5 struct mutex mutex; /* protects this wq */ 6 int work_color; /* WQ: current work color */ 7 int flush_color; /* WQ: current flush color */ 8 atomic_t nr_pwqs_to_flush; /* flush in progress */ 9 struct wq_flusher *first_flusher; /* WQ: first flusher */ 10 struct list_head flusher_queue; /* WQ: flush waiters */ 11 struct list_head flusher_overflow; /* WQ: flush overflow list */ 12 13 struct list_head maydays; /* MD: pwqs requesting rescue */ 14 struct worker *rescuer; /* I: rescue worker */ 15 16 int nr_drainers; /* WQ: drain in progress */ 17 int saved_max_active; /* WQ: saved pwq max_active */ 18 19 struct workqueue_attrs *unbound_attrs; /* WQ: only for unbound wqs */ 20 struct pool_workqueue *dfl_pwq; /* WQ: only for unbound wqs */ 21 22 #ifdef CONFIG_SYSFS 23 struct wq_device *wq_dev; /* I: for sysfs interface */ 24 #endif 25 #ifdef CONFIG_LOCKDEP 26 struct lockdep_map lockdep_map; 27 #endif 28 char name[WQ_NAME_LEN]; /* I: workqueue name */ 29 30 /* 31 * Destruction of workqueue_struct is sched-RCU protected to allow 32 * walking the workqueues list without grabbing wq_pool_mutex. 33 * This is used to dump all workqueues from sysrq. 34 */ 35 struct rcu_head rcu; 36 37 /* hot fields used during command issue, aligned to cacheline */ 38 unsigned int flags ____cacheline_aligned; /* WQ: WQ_* flags */ 39 struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */ 40 struct pool_workqueue __rcu *numa_pwq_tbl[]; /* FR: unbound pwqs indexed by node */ 41 };
(1-3)worker工作者线程
linux 内核使用工作者线程(worker thread)来处理工作队列中的各个工作,linux 内核使用worker 结构体表示工作者线程,worker 结构体定义如下(/kernel/workqueue_internal.h):
1 struct worker { 2 /* on idle list while idle, on busy hash table while busy */ 3 union { 4 struct list_head entry; /* L: while idle */ 5 struct hlist_node hentry; /* L: while busy */ 6 }; 7 8 struct work_struct *current_work; /*当前正在处理的work */ 9 work_func_t current_func; /* 当前正在执行的work回调函数 */ 10 struct pool_workqueue *current_pwq; /* 当前work所属的pool_workqueue*/ 11 bool desc_valid; /* ->desc is valid */ 12 struct list_head scheduled; /* 所有被调度并正准备执行的work都将加入到该链表中*/ 13 14 /* 64 bytes boundary on 64bit, 32 on 32bit */ 15 16 struct task_struct *task; /* 该工作线程的task_struct */ 17 struct worker_pool *pool; /* 该工作线程所属的worker_pool */ 18 /* L: for rescuers */ 19 struct list_head node; /* worker挂入的 链表 pool->workers */ 20 /* A: runs through worker->node */ 21 22 unsigned long last_active; /* L: last active timestamp */ 23 unsigned int flags; /* X: flags */ 24 int id; /* 工作线程的id */ 25 26 /* 27 * Opaque string set with work_set_desc(). Printed out with task 28 * dump for debugging - WARN, BUG, panic or sysrq. 29 */ 30 char desc[WORKER_DESC_LEN]; 31 32 /* used only by rescuers to point to the target workqueue */ 33 struct workqueue_struct *rescue_wq; /* I: the workqueue to rescue */ 34 };
workqueue工作队列的使用
每一个worker工作线程中都有一个工作队列,工作线程处理自己工作队列中的所有工作。
在实际开发中,推荐使用默认的workqueue·工作队列,而不是新创建workqueue。使用方法如下:
直接定义一个work_struct结构体变量,然后使用INIT_WORK宏来完成初始化工作,INIT_WORK定义如下:
#define INIT_WORK(_work, _func)
_work
表示要初始化的工作,_func
是工作对应的处理函数。
也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:
#define DECLARE_WORK(n, f)
n 表示定义的工作(work_struct),f 表示工作对应的处理函数。和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为schedule_work()
,函数原型如下所示:
bool schedule_work(struct work_struct *work)
使用cancel_work_sync()
取消一个工作,函数原型如下所示:
bool cancel_work_sync(struct work_struct *work)
当然也可以自己创建一个workqueue,特别是网络子系统、块设备子系统情况下等。具体步骤如下:
使用alloc_workqueue()创建新的workqueue。
使用INIT_WORK()宏声明一个work和该work的回调函数。
使用queue_work()在新的workqueue上调度一个work。
使用flush_workqueue()去flush 工作队列上的所有work。
除此之外,linux内核还提供了一个workqueue机制与timer机制相结合的延时机制—delayed_work
。
1 //定义一个工作(work) 2 static struct work_sturct my_work; 3 4 //定义一个工作处理函数 5 void my_work_func(struct work_struct *work) 6 { 7 /*.......*/ 8 } 9 10 11 //定义中断处理函数 12 irqreturn_t key_handler(int irq,void *dev_id) 13 { 14 //......... 15 16 //调度work 17 shcedule_work(&my_work); 18 19 // ...... 20 } 21 22 /* 驱动入口函数 23 */ 24 static int __init my_demo_init(void) 25 { 26 //... 27 28 //初始化work 29 INIT_WORK(&my_work,my_work_func); 30 31 //注册中断处理 函数 32 request_irq(xxx_irq,key_handler,0,"xxxx",&xxx_dev); 33 34 //.... 35 } 36 37 static void __exit my_demo_exit(void) 38 { 39 //执行一些释放操作 40 //.... 41 } 42 43 44 module_init(my_demo_init); 45 module_exit(my_demo_exit); 46 47 MODULE_LICENSE("GPL"); 48 MODULE_AUTHOR("iriczhao");
4.3 软中断--softirq
软中断(Softirq)也是一种传统的底半部处理机制,它的执行时机通常是顶半部返回的时候,tasklet是基于软中断实现的,因此也运行于软中断上下文。
在Linux内核中,用softirq_action结构体表征一个软中断,这个结构体包含软中断处理函数指针和传递给该函数的参数。使用open_softirq()函数可以注册软中断对应的处理函数,而raise_softirq()函数可以触发一个软中断。
软中断和tasklet运行于软中断上下文,仍然属于原子上下文的一种,而工作队列则运行于进程上下文。因此,在软中断和tasklet处理函数中不允许睡眠,而在工作队列处理函数中允许睡眠。
local_bh_disable() 和 local_bh_enable() 是内核中用于禁止和使能软中断及tasklet底半部机制的函数。
内核中采用softirq的地方包括 HI_SOFTIRQ、TIMER_SOFTIRQ、NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、SCSI_SOFTIRQ、TASKLET_SOFTIRQ等,一般来说,驱动的编写者不会也不宜直接使用softirq。
硬中断、软中断和信号的区别:
硬中断是外部设备对CPU的中断,软中断是中断底半部的一种处理机制,而信号则是由内核(或其他进程)对某个进程的中断。在设计系统调用的场合,人们也常说通过软中断(例ARM为swi)陷入内核,此时软中断的概念是指由软件指令引发的中断,和我们这个地方所说的softirq是两个完全不同的概念,一个是software,一个是soft。
需要特别说明的是,软中断以及基于软中断的tasklet如果在某段时间内大量出现的话,内核会把后续软中断放入 ksoftirqd 内核线程中执行。总的来说,中断优先级高于软中断,软中断优先级又高于任何一个线程。软中断适度线程化,可以缓解高负载情况下系统的响应。
4.4 thread_irq
在内核中除了可以通过request_irq()、devm_request_irq()申请中断以外,还可以通过request_threaded_irq() 和 devm_request_threaded_irq() 申请。这两个函数的原型为:
/** * request_threaded_irq - allocate an interrupt line * @irq: Interrupt line to allocate * @handler: Function to be called when the IRQ occurs. * Primary handler for threaded interrupts. * If handler is NULL and thread_fn != NULL * the default primary handler is installed. * @thread_fn: Function called from the irq handler thread * If NULL, no irq thread is created * @irqflags: Interrupt type flags * @devname: An ascii name for the claiming device * @dev_id: A cookie passed back to the handler function * * This call allocates interrupt resources and enables the * interrupt line and IRQ handling. From the point this * call is made your handler function may be invoked. Since * your handler function must clear any interrupt the board * raises, you must take care both to initialise your hardware * and to set up the interrupt handler in the right order. * * If you want to set up a threaded irq handler for your device * then you need to supply @handler and @thread_fn. @handler is * still called in hard interrupt context and has to check * whether the interrupt originates from the device. If yes it * needs to disable the interrupt on the device and return * IRQ_WAKE_THREAD which will wake up the handler thread and run * @thread_fn. This split handler design is necessary to support * shared interrupts. * * Dev_id must be globally unique. Normally the address of the * device data structure is used as the cookie. Since the handler * receives this value it makes sense to use it. * * If your interrupt is shared you must pass a non NULL dev_id * as this is required when freeing the interrupt. * * Flags: * * IRQF_SHARED Interrupt is shared * IRQF_TRIGGER_* Specify active edge(s) or level * IRQF_ONESHOT Run thread_fn with interrupt line masked */ 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); /** * devm_request_threaded_irq - allocate an interrupt line for a managed device * @dev: device to request interrupt for * @irq: Interrupt line to allocate * @handler: Function to be called when the IRQ occurs * @thread_fn: function to be called in a threaded interrupt context. NULL * for devices which handle everything in @handler * @irqflags: Interrupt type flags * @devname: An ascii name for the claiming device, dev_name(dev) if NULL * @dev_id: A cookie passed back to the handler function * * Except for the extra @dev argument, this function takes the * same arguments and performs the same function as * request_threaded_irq(). IRQs requested with this function will be * automatically freed on driver detach. * * If an IRQ allocated with this function needs to be freed * separately, devm_free_irq() must be used. */ int devm_request_threaded_irq(struct device *dev, unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id);
由此可见,它们比request_irq()、devm_request_irq()多了一个参数 thread_fn。用这两个API申请中断的时候,内核会为相应的中断号分配一个对应的内核线程。注意这个线程只针对这个中断号,如果其他中断也通过request_threaded_irq()申请,自然会得到新的内核线程。
参数handler对应的函数执行于中断上下文,thread_fn参数对应的函数则执行于内核线程。如果handler结束的时候,返回值是 IRQ_WAKE_THREAD,内核会调度对应线程执行 thread_fn 对应的函数。
request_threaded_irq() 和 devm_request_threaded_irq() 支持在 irqflags 中设置 IRQF_ONESHOT标记,这样内核会自动帮助我们在中断上下文中屏蔽对应的中断号,而在内核调度 thread_fn 执行后,重新使能该中断号。对于我们无法在上半部清除中断的情况, IRQ_ONESHOT 特别有用,避免了中断服务程序一退出,中断就洪泛的情况。
handler 参数可以设置为NULL,这种情况下,内核会用默认的 irq_default_primary_handler() 代替 handler,并会使用 IRQ_ONESHOT标记。 irq_default_primary_handler() 定义为:
/* * Default primary interrupt handler for threaded interrupts. Is * assigned as primary handler when request_threaded_irq is called * with handler == NULL. Useful for oneshot interrupts. */ static irqreturn_t irq_default_primary_handler(int irq, void *dev_id) { return IRQ_WAKE_THREAD; }
参考:request_irq和free_irq的使用_奔跑的小刺猬的博客-CSDN博客
Linux内核API disable_irq|极客笔记 (deepinout.com)
https://blog.csdn.net/yangxueyangxue/article/details/122661049
【linux kernel】linux中断管理—workqueue工作队列_cancel_work_sync_iriczhao的博客-CSDN博客
Linux中断底半部机制总结 - 闹闹爸爸 - 博客园 (cnblogs.com)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析