Linux 中断
中断的上下部
- 上半部: 处理严格时限的工作, 严格意义上的中断处理函数.
- 下半部: 中断来了立即执行上半部, 下半部会等待时机再执行. linux提供了这种机制
设备树中断描述
和GPIO结构类似, 中断控制器负责管理一组中断源, 在设备书中中断控制器有如下描述:
intc:interrupt-controller @00a01000
{
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>; //中断描述cell个数, 对arm GIC来说一般是3个, 第一个表示中断类型, 第二个表示中断号, 第三步标志位: [触发方式(上升沿下降沿)][ppi中断cpu掩码]
interrupt-controller; // 类似gpio-controller
reg = <0x00a01000 0x1000>, <0x00a02000 0x100>;
};
同样, 中断源也需要描述它要用的中断信息
gpio5 : gpio @020ac000{
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>, //GIC_SPI spi中断类型, 74是中断号, IRQ_TYPE_LEVEL_HIGH表示中断触发方式是高电平
<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&combiner>; //在上面的例子中 GIC 前缀已经表明了它使用的是GIC中断控制器, 如果是别的,就用interrupt-parent来指定
};
中断申请和释放
设备树描述了硬件具有哪些中断, 软件要先检查有哪些中断, 要使用还需向内核申请,不用了要释放, 这样就可以动态的管理中断,更加灵活.
- 获取中断号
- unsigned int
irq_of_parse_and_map
(struct device_node *dev,int index) //从设备节点dev中获取中断号, interrupts属性可以有多个中断信息, index指定哪一个 - int
gpio_to_irq
(unsigned int gpio) //gpio可以用这个函数
- unsigned int
- 申请中断
- int
request_irq
(unsigned int irq, //要申请的中断号
irq_handler_t handler, //中断处理函数
unsigned long flags, //interrupt.h里定义了中断标志: IRQF_SHARED(共享中断,所有共享此中断的设备都要设置为shared), IRQF_TRIGGER_HIGH(高电平触发)... ...
const char *name, //中断名字
void *dev) //shared是用来区分中断来自哪个设备
- int
- 释放中断
- void
free_irq
(unsigned int irq,void *dev)
- void
中断处理函数
irqreturn_t (*irq_handler_t) (int, void *) //第一个中断号, 第二个是request_irq里的dev.
中断控制函数
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq) //等待当前的中断处理函数执行完才返回
void disable_irq_nosync(unsigned int irq) //直接返回,不等待当前的中断处理函数
local_irq_save(flags) // 关闭中断, 并保存当前中断状态到flags
local_irq_restore(flags) // 恢复中断状态
下半部机制
软中断
- 触发
voidraise_softirq
(unsigned int nr)
触发前还得先注册 action
void open_softirq
(int nr, void (*action)(struct softirq_action *))
//nr:要开启的软中断,也就是上面的10个软中断
//action:软中断对应的处理函数
- 实现原理
内核定义了一个
static struct softirq_actionsoftirq_vec
[NR_SOFTIRQS],open_softirq
就是把处理函数加到这个数组里.raise_softirq
是设置中断标志位, 之后内核会在合适的时机调用__do_softirq
函数来处理软中断, 这个函数会遍历softirq_vec
数组, 找到对应的处理函数执行.
softirq_vec是静态数组, 所有的进程都共享这个数组, 不应该去覆盖已有的action, 通常都是内核自己设置.
struct softirq_action
{
void (*action)(struct softirq_action *);
};
enum
{
HI_SOFTIRQ=0, /* 高优先级软中断 /
TIMER_SOFTIRQ, / 定时器软中断 /
NET_TX_SOFTIRQ, / 网络数据发送软中断 /
NET_RX_SOFTIRQ, / 网络数据接收软中断 /
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ, / tasklet 软中断 /
SCHED_SOFTIRQ, / 调度软中断 /
HRTIMER_SOFTIRQ, / 高精度定时器软中断 /
RCU_SOFTIRQ, / RCU 软中断 */
NR_SOFTIRQS
};
tasklet
- 触发
tasklet_schedule
(&testtasklet);
要使用先初始化
void tasklet_init
(struct tasklet_struct t, //要初始化的tasklet,
void (func)(unsigned long), // tasklet执行的函数
unsigned long data); // func的参数
DECLARE_TASKLET
(name, func, data) //使用宏更方便
tasklet_kill
(struct tasklet_struct *t) //不用的时候还可以删掉, 这个函数会等当前的tasklet执行完毕再移除.
- 实现原理
tasklet 是基于软中断的, 软中断就那么几个,固定的,肯定不够用, 所有tasklet就是一个软中断的扩展,又一级路由.tasklet_schedule
把tasklet添加到待执行队列中, 不会重复添加, 所以同一个tasklet只会串行执行. 并且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 的参数 */
};
workqueue
工作队列在进程上下文执行, 相比tasklet, 最主要的是可以休眠. 所以用来处理非常复杂且费时的任务.
- 触发
schedule_work
(&testwork);
使用前要初始化
define INIT_WORK
(_work, _func)
_work:
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
_func: 工作对应的处理函数
或者使用
define DECLARE_WORK
(name, f)
- 实现原理
schedule_work
把work添加到 workqueue中, 也只会添加一次, 然后唤醒工作线程, 每个cpu都有自己的工作队列