[驱动程序] 中断的实现(中断下半部)

Linux将中断分为两部分:上半部和下半部。
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。
中断上半部是关中断的状态,也就是CPU不接收其他中断,因此中断上半部要快,不然会丢中断。
中断下半部是开中断的状态,CPU可以接收其他中断。

对于一个工作是放在上半部还是放在下半部去执行,可以参考下面四条:
a)如果一个任务对时间非常敏感,将其放在中断处理程序中执行。
b)如果一个任务和硬件相关,将其放在中断处理程序中执行。
c)如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行。
d)其他所有任务,考虑放在下半部去执行。

①softirq用在对下半部执行时间要求比较紧急或者非常重要的场合,主要为一些子系统,一般的驱动用不上。(了解即可)
②tasklet和workqueue是普通驱动中用的比较多的,主要区别是tasklet在中断上下文执行,workqueue在进程上下文执行,进程上下文可以休眠。

1. softirq软中断

①softirq是静态定义的,内核不希望驱动开发者新增加软件中断类型。
②软中断的回调函数在开中断的环境下执行。
③softirq更倾向于性能,可能在多个CPU并发执行,要考虑重入。

1.1 分配索引、softirq类型

enum
{
    HI_SOFTIRQ=0, /* 高优先级tasklet */ /* 优先级最高 */
    TIMER_SOFTIRQ, /* 时钟相关的软中断 */
    NET_TX_SOFTIRQ, /* 将数据包传送到网卡 */
    NET_RX_SOFTIRQ, /* 从网卡接收数据包 */
    BLOCK_SOFTIRQ, /* 块设备的软中断 */
    BLOCK_IOPOLL_SOFTIRQ, /* 支持IO轮询的块设备软中断 */
    TASKLET_SOFTIRQ, /* 常规tasklet */
    SCHED_SOFTIRQ, /* 调度程序软中断 */
    HRTIMER_SOFTIRQ, /* 高精度计时器软中断 */
    RCU_SOFTIRQ, /* RCU锁软中断,该软中断总是最后一个软中断 */

    MYKEY_SOFTIRQ,//自行增加,增加后需要重新编译内核,最后将内核固化到开发板
 
    NR_SOFTIRQS /* 软中断数,为11 */
 
 };

1.2 注册软件中断处理程序

在运行时 通过调用open_softirq()注册软件中断处理程序,该函数有三个参数:软中断的索引号、处理函数和data域存放的数值:

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
    softirq_vec[nr].data = data;
    softirq_vec[nr].action = action;
}

1.3 触发对应的软中断

通过在枚举类型的列表中添加新项,调用open_softirq()进行注册之后,新的软中断处理程序就能够运行。raise_softirq()函数可以将一个软中断设
置为挂起状态,让它在下次调用do_softirq()函数时投入运行:

void fastcall raise_softirq(unsigned int nr)
{
    unsigned long flags;
    local_irq_save(flags);
    raise_softirq_irqoff(nr);
    local_irq_restore(flags);
}

该函数在触发一个软中断之前先要禁止中断,触发后再恢复原来的状态,如果中断本来就已经被禁止了,那么可以调用另一个函数
raise_softirq_irqoff():

在中断处理程序中触发软中断是最常见的形式。在这种情况下,中断处理程序执行硬件设备的相关操作,然后触发相应的软中断,最后退出。内核在执行完中断处理程序之后,马上调用do_softirq()函数,于是软中断开始执行中断处理程序留给它要完成的剩余任务。

2. tasklet小任务

①tasklet基于softirq实现,本质上是softirq的一个变种。
②tasklet可以静态定义,也可以通过tasklet_init动态初始化。
③tasklet更倾向于易用性,绑定CPU运行,不用考虑并发。

Linux 内核使用 tasklet_struct 结构体来表示 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 的参数 */
};

2.1 tasklet初始化

taskled_init 函数原型如下:

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 函数的参数。

2.2 调用小任务

在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行,tasklet_schedule 函数原型如下:

void tasklet_schedule(struct tasklet_struct *t)

函数参数和返回值含义如下:
t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。
返回值:没有返回值。

2.3 小任务处理函数

编写自己的小任务处理程序小任务处理程序必须符合如下的函数类型:

void tasklet_handler(unsigned long data)

由于小任务不能睡眠,因此不能在小任务中使用信号量或者其它产生阻塞的函数。但是小任务运行时可以响应中断。

2.4 tasklet示例

/* 定义 taselet */
struct tasklet_struct testtasklet;

/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
    /* tasklet 具体处理内容 */
}

/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
    ......
    /* 调度 tasklet */
    tasklet_schedule(&testtasklet);
    ......
}

/* 驱动入口函数 */
static int __init xxxx_init(void)
{
    ......
    /* 初始化 tasklet */
    tasklet_init(&testtasklet, testtasklet_func, data);
    /* 注册中断处理函数 */
    request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
    ......
}

3. workqueue工作队列

将work(需要推迟运行的函数)交给一个内核线程执行,因此是在线程上下文中,主要用在有sleep需要的时候。
work_struct结构体表示一个工作,如下:

struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func; /* 工作队列处理函数 */
};

这些工作组织成工作队列,工作队列使用workqueue-struct结构体表示,如下:

struct workqueue_struct {
    struct list_head pwqs;
    struct list_head list;
    struct mutex mutex;
    int work_color;
    int flush_color;
    ......
}

3.1 初始化和调度

在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。简单创建工作很简单,直接定义一个 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)

函数参数和返回值含义如下:
work :要调度的工作。
返回值:0 成功,其他值 失败。

3.2 workqueue示例

/* 定义工作(work) */
struct work_struct testwork;

/* work 处理函数 */

void testwork_func_t(struct work_struct *work);
{
    /* work 具体处理内容 */
}

/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
    ......
    /* 调度 work */
    schedule_work(&testwork);
    ......
}

/* 驱动入口函数 */
static int __init xxxx_init(void)
{
    ......
    /* 初始化 work */
    INIT_WORK(&testwork, testwork_func_t);
    /* 注册中断处理函数 */
    request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
    ......
}
  • 21
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值