中断下半部 tasklet

中断下半部任务:处理耗时操作

中断下半部 tasklet

当一个中断要耗费很多时间来处理时,它的坏处是:在这段时间内,其他中断无法被处理。也就是说这段时间内,系统是关中断的。

如果某个中断就是要做那么多事,我们能不能把它拆分成两部分:紧急的、不紧急的?

中断下半部的实现有很多种方法,主要有两种

  • tasklet(小任务)
  • work queue(工作队列)

当下半部比较耗时是能忍受的,并且它的处理比较简单时,可以用 tasklet 来处理下半部。tasklet 是使用软件中断来实现。
在这里插入图片描述
假设硬件中断A 的上半部函数为 irq_top_half_A ,下半部为 irq_bottom_half_A

使用情景分析,才能理解上述代码:

  • 硬件中断A 处理过程中,没有其他中断发生:
    一开始,preempt_count = 0;
    上述流程图①~⑨依次执行,上半部、下半部的代码各执行一次

  • 硬件中断 A 处理过程中,又再次发生了中断 A:
    一开始, preempt_count = 0;
    执行到第⑥时,一开中断后,中断 A 又再次使得 CPU 跳到中断向量表。

注意:

这时 preempt_count 等于1,并且中断下半部的代码并未执行。
CPU 又从①开始再次执行中断 A 的上半部代码;
在第①步 preempt_count 等于 2;
在第③步 preempt_count 等于 1;
在第④步发现 preempt_count 等于 1,所以直接结束当前第 2 次中断的处理;

注意:重点来了

第2次中断发生后,打断了第一次中断的第⑦步处理。当第2次中断处理完毕, CPU 会继续去执行第⑦步。可以看到,发生 2 次硬件中断 A 时,它的上半部代码执行了 2 次,但是下半部代码只执行了一次。

所以,同一个中断的上半部、下半部,在执行时是多对一的关系。

  • 硬件中断 A 处理过程中,又再次发生了中断 B:
    一开始, preempt_count = 0;
    执行到第⑥时,一开中断后,中断 B 又再次使得 CPU 跳到中断向量表。

注意: 这时 preempt_count 等于 1,并且中断 A 下半部的代码并未执行。
CPU 又从①开始再次执行中断 B 的上半部代码:
在第①步 preempt_count 等于 2;
在第③步 preempt_count 等于 1;
在第④步发现 preempt_count 等于 1,所以直接结束当前第 2 次中断的处理;

注意:重点来了, 第2次中断发生后,打断了第一次中断A的第⑦步处理。当第2次中断B处理完毕, CPU 会继续去执行第⑦步。在第⑦步里,它会去执行中断 A 的下半部,也会去执行中断 B 的下半部。

所以,多个中断的下半部,是汇集在一起处理的。

总结:

  1. 中断的处理可以分为上半部,下半部
  2. 中断上半部,用来处理紧急的事,它是在关中断的状态下执行的
  3. 中断下半部,用来处理耗时的、不那么紧急的事,它是在开中断的状态下执行的
  4. 中断下半部执行时,有可能会被多次打断,有可能会再次发生同一个中断
  5. 中断上半部执行完后,触发中断下半部的处理
  6. 中断上半部、下半部的执行过程中,不能休眠:中断休眠的话,以后谁来调度进程啊?

在这里插入图片描述
当发生某一个硬件中断时,内核会调用对应的硬件中断处理函数,处理完之后,会去处理软件中断;从类似soft数组中取出action函数,对于lastlet这种软件中断,对应的函数是tasklet_action,这个函数会从某个队列/链表里取出每一个tasklet结构体,执行里面的函数。

处理硬件中断是上半部(中断禁止);处理软件中断的是下半部(中断打开,有可能被打断)。

上半部可以使能/调用下半部:把一个tasklet结构体放进队列/链表。
在这里插入图片描述
软件中断不会叠加
下半部就是构造tasklet结构体,提供函数。

内核函数

  1. 定义 tasklet,中断下半部使用结构体tasklet_struct来表示,它在内核源码include\linux\interrupt.h 中定义:
struct tasklet_struct
{
   
   
    struct tasklet_struct *next;
    unsigned long state;
    atomic_t count;
    void (*func)(unsigned long);
    unsigned long data;
};
  • 其中的 state 有 2 位:

    • bit0 表示 TASKLET_STATE_SCHED等于 1 时表示已经执行了 tasklet_schedule 把该 tasklet 放入队列了;tasklet_schedule 会判断该位,如果已经等于 1 那么它就不会再次把tasklet 放入队列。
    • bit1 表示 TASKLET_STATE_RUN等于 1 时,表示正在运行 tasklet 中的 func 函数;函数执行完后内核会把该位清 0。
  • 其中的 count 表示该 tasklet 是否使能:等于 0 表示使能了,非 0 表示被禁止了。对于 count 非 0 的 tasklet,里面的 func 函数不会被执行。

使用下半部执勤,要实现一个 tasklet_struct 结构体,这可以用2个宏来定义结构体:

#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 }
  • 使用 DECLARE_TASKLET 定义的 tasklet 结构体,他是使能的;
  • 使用 DECLARE_TASKLET_DISABLED 定义 tasklet 结构体,它是禁止的;使用之前要先调用 tasklet_enable 使能它。

也可以使用函数来初始化 tasklet 结构体

extern void tasklet_init(struct tasklet_struct *t,
                         void (*func)(unsigned long), unsigned long data);
  1. 使能/禁止 tasklet
static inline void tasklet_enable(struct tasklet_struct *t);
static inline void tasklet_disable(struct tasklet_struct *t);
  • tasklet_enable 把 count 减1;
  • tasklet_disable 把 count 增1。
  1. 调度 tasklet

static inline void tasklet_schedule(struct tasklet_struct *t);

把 tasklet 放入链表,并且设置它的 TASKLET_STATE_SCHED 状态为 1

  1. kill tasklet

extern void tasklet_kill(struct tasklet_struct *t);

  • 如果一个 tasklet 未被调度, tasklet_kill 会把它的TASKLET_STATE_SCHED 状态清 0;
  • 如果一个 tasklet 已被调度, tasklet_kill 会等待它执行完华,再把它的TASKLET_STATE_SCHED 状态清 0。

通常在卸载驱动程序时调用 tasklet_kill。

tasklet 使用方法

先定义 tasklet,需要使用时调用 tasklet_schedule,驱动卸载前调用tasklet_kill。tasklet_schedule 只是把 tasklet 放入内核队列,它的 func 函数会在软件中断的执行过程中被调用。

驱动程序

驱动程序在定时器的基础上修改。

  1. 在原来的gpio_key结构体中添加一个tasklet属性,每个按键都有一个tasklet;在probe函数中初始化tasklet;在remove函数中删除tasklet;
  2. 在tasklet处理函数中打印数据;
  3. 在中断服务程序,启动一个下半部。
#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值