Linux 中断

中断的上下部

  1. 上半部: 处理严格时限的工作, 严格意义上的中断处理函数.
  2. 下半部: 中断来了立即执行上半部, 下半部会等待时机再执行. 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来指定
};

中断申请和释放

设备树描述了硬件具有哪些中断, 软件要先检查有哪些中断, 要使用还需向内核申请,不用了要释放, 这样就可以动态的管理中断,更加灵活.

  1. 获取中断号
    • unsigned int irq_of_parse_and_map(struct device_node *dev,int index) //从设备节点dev中获取中断号, interrupts属性可以有多个中断信息, index指定哪一个
    • int gpio_to_irq(unsigned int gpio) //gpio可以用这个函数
  2. 申请中断
    • 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是用来区分中断来自哪个设备
  3. 释放中断
    • void free_irq(unsigned int irq,void *dev)

中断处理函数

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) // 恢复中断状态

下半部机制

软中断

  • 触发
    void raise_softirq(unsigned int nr)

触发前还得先注册 action
void open_softirq(int nr, void (*action)(struct softirq_action *))
//nr:要开启的软中断,也就是上面的10个软中断
//action:软中断对应的处理函数

  • 实现原理
    内核定义了一个
    static struct softirq_action softirq_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都有自己的工作队列
posted @ 2025-04-11 00:26  天刚刚破晓  阅读(34)  评论(0)    收藏  举报