[LDD3阅读笔记]中断处理

来源:互联网 发布:佳能打印机排版软件 编辑:程序博客网 时间:2024/05/16 07:06

中断处理

1. 安装中断程序

#include <linux/sched.h>

typedef irqreturn_t (*irq_handler_t)(int, void *);

extern int __must_check

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,

    const char *name, void *dev);

返回0表示成功

参数说明:

irq: 中断号

handler: 中断处理函数, 返回IRQ_HANDLED 表示已经处理了这个中断

flags:位掩码

SA_INTERRUPT:快速中断处理程序,当设置这个标志时,在进入处理程序前当前处理器的所有中断都被禁用(注意,其它处理器仍然可以处理中断)。一般不要设置这个标志。

SA_SHIRQ:表示中断可以在设备之间共享。

SA_SAMPLE_RANDON: 这个中断(产生的时间点)能对随机数熵池(entropy pool)有贡献。注意要是真正的随机产生中断。

name:

dev:用于共享中断,是一个标识符,用来识别哪一个设备产生的中断, 可以把它当成中断的私有数据指针, 要不要注意唯一性??

extern void free_irq(unsigned int irq, void *dev);

2. proc接口

a. /proc/interrupts 查看已经安装中断处理程序的中断触发信息

b. /proc/stat 查看所有中断的触发信息

3. 自动检测IRQ

并不是所有设备都能从设备的寄存器读到或像一个标准接口有规定的固定中断号。所以,我们需要自动检测.

#include <linux/interrupt.h>

unsigned long probe_irq_on(void);

返回未分配中断的位掩码。 驱动程序必须保存位掩码以传入后面的probe_irq_off函数。调用这个函数后, 驱动要安排设备产生至少一次中断。

int probe_irq_off(unsigned long);

在设备产生中断后,驱动程序调用这个函数, 并将前面probe_irq_on返回的位掩码作为参数传递给它。 probe_irq_of()返回probe后的中断编号。如果没有发生,返回0, 如果 产生多次中断, 返回负数。

4. DIY中断检测

事实上我们不必检测所有中断, 我们可以充分发挥我们对硬件的了解,减少检测范围。

我们可以向可能需要处理的中断号注册中断处理函数,然后触发中断,删除中断处理函数。然后查看哪些中断处理函数已经被触发。

5. 中断处理函数的特殊性

a. 它在中断时间内运行,不在任何进程的上下文中运行。

b. 不能向用户空间发送可接收数据。

c. 不能做任何可能触发休眠的操作,如调用 wait_event, 使用不带GFP_ATOMIC标志的内存分配, 锁住一个信号量等

d. 不能调用schdule函数

e. 函数耗时要尽可能的短,如果需要长时间的任务,最好使用tasklet或工作队列

6. 禁用单个中断

#include <asm/irq.h>

void disable_irq(int irq);//这函数会先调用diable_irq_nosync,然后等待被禁用irq处理程序完成。在被禁中断处理程序正在运行时需要等待被禁中断处理程序运行完成。如果调用者拥有禁用中断处理程序的资源就有可能造成死锁。

void disable_irq_nosync(int irq);//通过操作可编程中断控制器里的中断掩码完成。注意有可能造成竞态

void enable_irq(int irq);

不鼓励禁用中断, 还有共享中断不能禁用

这些函数是可嵌套的,即两次被禁用,需要两次打开。

7. 禁用所有中断

强烈不建议使用。

void local_irq_save(unsigned long flags);

void local_irq_disable(void);

void local_irq_restore(unsigned long flags);

void local_irq_enable(void);

注意与disable_irq不同,local_irq_disable不会保存多次调用跟踪。

8. 顶半部和底半部

只是在耗时的中断操作里的概念

顶半部: 是指真正的中断程序。记住些状态,call 底半部, 然后快速返回。

底半部指耗时的任务。用tasklet或工作队列完成。

9. tasklet

tasklet在很多方面与内核定时器类似: 它始终会在调用它的同一CPU的软中断期间运行。

a. 结构

struct tasklet_struct

{

struct tasklet_struct *next;

unsigned long state;

atomic_t count;

void (*func)(unsigned long);

unsigned long data;

};

#define DECLARE_TASKLET(name, func, data) \

struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) \

struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

b. tasklet的特性

i与定时器类似,tasklet也会在“软件中断”上下文以原子模式执行。

ii一个tasklet 可在稍后被禁止或者重新启用;只有启用的次数和禁止的次数相同时,tasklet才会被执行

iii和定时器类似, tasklet可以注册自己本身

iv, tasklet可被调度以在通常的优先级或者高优先级执行。高优先级的tasklet总会首先执行。

v如果系统负荷不重,则tasklet 会立即得到执行,但始终不会易于下一个定时器滴答。

vi,. 一个tasklet可以和其它tasklet并发,但对自身来讲是严格串行处理的,也就是说,同一tasklet永远不会在多个处理器上同时执行。当然我们已经指出,tasklet始终会在调度自己的同一CPU运行。

c. tasklet相关函数

i. void tasklet_diable(struct tasklet_struct *t);

这个函数禁用指定的tasklet。该tasklet仍然可以用tasklet_schedule调度,但其执行会被推迟到该tasklet被重新利用。如果tasklet当前正在运行,该函数会进入忙等待tasklet退出为止;因此,在调用taklet_disable之后,我们可以确信该tasklet不会在系统中的任何地方运行。

ii. void tasklet_disable_nosync(struct tasklet_struct *t);

禁用指定的tasklet, 但不会等待正在运行的taklet

iii.void tasklet_enable(struct tasklet_struct *t);

启用一个先前被禁用的tasklet。如果该tasklet已经被调度,它很快就会运行。对于taklet_enable的调用必须和对每个tasklet_disable的调用匹配,因为内核对每个tasklet保存有一个“禁用计数”。

iv. void tasklet_schedule(struct tasklet_struct *t);

调试执行指定的tasklet。如果在获得运行机会之前,某个tasklet被再次调试,则该tasklet只会运行一次。但是如果在该tasklet运行时被调试,就会在完成后再次运行。这样,可确保正在处理事件时发生的其它事件也会被 接收并注意到。这种行为也允许tasklet重新调度自身。

v. void task_hi_schedule(struct tasklet_struct *t);

调度指定的tasklet以高优先级执行。当软件中断处理例程运行时,它会在处理其它软件中断任务(包含“通常”的tasklet)之前处理高优先级的tasklet。理想状态下,只有具备低延迟需求的任务(比如填充音频缓冲区)才能使用这个函数,这样可以避免由其它软件中断处理例程引入的额外延迟。

vi. void tasklet_kill(struct tasklet_struct *t);

该函数确保指定的tasklet不会被 再次调度运行;当设备要被关闭或者模块要被 移除时, 我们通常调用这个函数。如果tasklet正在被调度执行,该函数会等待其退出。如果tasklet重新调度自己,则应该避免在调用tasklet_kill之前完成 重新调度。

10. 工作队列(work_queue)

a. tasklet的区别

i. tasklet在软件中断上下文中运行,因此,所有的tasklet代码都必须是原子的。相反,工作队列函数在一个特殊内核进程的上下文中运行,因此它们具有更好的灵活性。尤其是,工作队列可以休眠。

ii. tasklet始终运行在被初始提交的同一处理器上,但这只是工作队列的默认方式。

iii内核代码可以请求工作队列函数的执行延迟给定的时间间隔。

关键区别:tasklet会在很短的时间 段内很快的执行,并且以原子模式执行,而工作队列函数可具有更长的延迟并且不必原子化。两种机制有各自的适合情形。

b. 初始化及基本函数

i声明或初始化

struct workqueue_struct;

#include <linux/workqueue.h>

struct workqueue_struct * create_workqueue(const char* name);

struct workqueue_struct *create_singlethread_workqueue(const char *name);

每个工作队列有一个或多个专用的进程(“内核线程”),这些进程运行提交到该队列的函数。如果我们使用create_workqueue,则内核会在系统中的每个处理器上为该工作队列创建专用的纯种。在许多情况下, 众多的纯种可能对性能有一次的影响;因此,如果单个工作纯种足够使用,那么应该使用create_singlethread_workqueue创建工作队列。

ii任务初始化

要向一个工作队列提交一个任务,需要填充一个work_struct结构,这可通过下面的宏在编译时完成:

DECLARE_WORK(name, void (*function)(void *), void *data);

name:声明的结构名称

function:是要从工作队列中调用的函数

data:是要传递给该函数的值。

如果是运行时构造,可使用下面的两个宏

INIT_WORK(struct work_struct *work, void (*function) (void *), void *data);

PREPARE_WORK(struct work_struct *work, void (*function) (void *), void *data);

INIT_WORK完成地更彻底,在首次构造时,应该使用这个宏

PREPARE_WORK不会初始化用来将work_struct结构链接到工作队列的指针。如果结构已经被提交到工作队列,而只是需要修改该结构,则应该使PREPARE_WORK而不是INIT_WORK

iii将工作提交到工作队列

int queue_work(struct workqueue_struct *queue, struct work_struct *work);

int queue_delayed_work(struct workqueue_struct *queue,

struct work_struct *work, unsigned long delay);

delay的单位是jiffies.

工作队列同样不能访问用户空间。因为它运行在内核线程。

如果要取消某个扶起的工作队列入口项,可调用:

int cancel_delayed_work(struct work_struct *work);

返回非零,说明该入口项在开始执行前被取消

返回零:说明该入口项已经在其它处理器上运行。为了确保工作函数不会在系统中的任何地方运行,应该调用flush_workqueue
void flush_workqueue(struct workqueue_strcut *queue);//返回后,任何在该调用之前被提交的工作函数都不会在系统的任何地方运行。

Forces execution of the workqueue and blocks until its completion. This is typically used in driver shutdown handlers.

We sleep until all works which were queued on entry have been handled, but we are not livelocked by new incoming ones.

iv销毁工作队列

void destory_workqueue(struct workqueue_struct *queue);

总体上看:工作队列就像是一个消息处理内核线程。消息里带有工作函数及参数。

11. 共享队列

a. 初始化

由于是共享的, 我们不需要创建工作队列,但我们不能长期战胜队列,即不能长期休眠。初始化只需要调用宏INIT_WORK就行了

b. 提交任务

int schedule_work(struct work_struct *work);

int schedule_delayed_work(struct work_struct *work, unsigned long delay);

c. 取消已提交到共享队列中的工作入口项

可以使用cancel_delayed_work函数,但是,刷新共享队列时需要另一个函数:

void flush_scheduled_work(void); //这个函数耗时是未知的

12. 中断共享

1. 安装共享中断

与普通非共享中断一样,共享中断申请也是通过request_irq。只有两点不同:

i. flags 必须有SA_SHIRQ

ii. dev_id必须唯一的。不能设置成NULL.

2. request_irq成功的条件

i中断线空闲

ii任何已经注册该中断号的处理例程也标识了SA_SHIRQ.

3. 探测函数不再可用

4. 禁止使用enable_irq, disable_irq.否则会有极大的麻烦

原创粉丝点击