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
参考: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博客
标签:struct,中断,irq,work,workqueue,linux,tasklet From: https://www.cnblogs.com/zhiminyu/p/17618002.html