中断子系统理论与实例分析

概念

中断上下文(也有叫:上半部、下半部)
中断上文:完成尽可能少且急的任务(不能被打断,一般就添加个任务到等待队列)
中断下文:处理耗时的部分(这部分可以被打断)

CPU通过中断控制器控制中断的打开、关闭、优先级……
在这里插入图片描述
它甚至还能级联:
在这里插入图片描述
中断号概念
IRQ number:软中断号,再Linux系统中是唯一的
HW interrupt ID:中断控制器需要对外设进行编号,中断控制器使用 HW interrupt ID 来标识外设中断,是硬件中断号。
IRQ domain:负责实现硬件中断号与中断号的映射

中断源的类型

  • SGI(软中断)
    中断号在0-15之间,主要用于 core 之间通信。
  • PPI(私有中断)
    中断号在16-31之间,这类中断是每个 core 独有的,每个 core 上有一个 tick 中断,用于进程调度使用。
  • SPI(共享中断)
    中断号在32-1020之间,是由外设触发的中断信号。
  • LPI:
    是一种在GICv3/v4中引入的中断类型,不支持 GICv1/v2。
    中断是向处理器发出的一个信号,表示发生了需要处理的事件。中断通常由外围设备产生。LPI通常用于产生消息信号中断(MSIs)的外围设备。

LPI的配置和管理与其他中断类型不同,因为它们的状态保存在内存中,而不是寄存器中。LPI是消息信号中断(MSIs)
在这里插入图片描述
GIC主要由各类总线、仲裁器、CPU接口组成。
在这里插入图片描述

一、中断 API

获取中断号:

int gpio_to_irq(unsigned int gpio);

gpio:管脚编号
返回值:Linux系统分配的 gpio 对应的中断号

注册中断:

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);

irq:要申请中断的中断号。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志,这里看几个常用的中断标志
IRQF_SHARED 多个设备共享一个中断线,共享的所有中断都必须指定此标志。
如果使用共享中断的话,request_irq 函数的 dev 参数就是唯一
区分他们的标志。

IRQF_ONESHOT             单次中断,中断执行一次就结束。
IRQF_TRIGGER_NONE         无触发。
IRQF_TRIGGER_RISING     上升沿触发。
IRQF_TRIGGER_FALLING     下降沿触发。
IRQF_TRIGGER_HIGH         高电平触发。
IRQF_TRIGGER_LOW         低电平触发。

name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev:如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将
dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
返回值:0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经
被申请了。

释放中断号:

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

int 参数是要中断处理函数要相应的中断号。
void * 参数是一个通用指针,需要与 request_irq 函数的 dev 参数保持一致,用于区分共享中断的不同设备,dev 也可以指向设备数据结构。
中断处理函数的返回值为 irqreturn_t 类型,irqreturn_t 类型定义如下所示:

enum irqreturn {
    IRQ_NONE = (0 << 0),
    IRQ_HANDLED = (1 << 0),
    IRQ_WAKE_THREAD = (1 << 1),
};

typedef enum irqreturn irqreturn_t;

一般中断服务函数返回值使用如下形式:

return IRQ_RETVAL(IRQ_HANDLED)

案例:

#include <linux/gpio.h>

int irq;
/* 中断回调函数 */
irqreturn_t test_interrupt(int irq, void *args)
{
    ......
    return IRQ_RETVAL(IRQ_HANDLED);
}
/* 初始化设备时注册中断 */
static int xxx_init(void)
{
    int ret=0;
    irq = gpio_to_irq(13);    //获取13号管脚的中断号
    ret=request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL); //注册中断
    ......
}
/* 卸载设备时释放注册的中断 */
static void interrupt_irq_exit(void)
{
    free_irq(irq);
    ......
}

二、request_irq() 如何向内核申请中断?

/* /include/linux/interrupt.h */
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
{
    return request_thread_irq(irq, handler, NULL, flags, name, dev);
}
/* dev 参数只有在共享中断的时候才会用到 */

/* /kernel/irq/manage.c */
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
             irq_handler_t thread_fn, unsigned long irqflags,
             const char *devname, void *dev_id)
{
    struct irqaction *action;
    struct irq_desc *desc;
    int retval;

    ......
    /* 核心:将中断描述符添加进内核中断描述符链表 */
    retval = __setup_irq(irq, desc, action);
    ......
 }

中断三大结构体

struct irqaction {
	irq_handler_t handler;      //等于用户注册的中断处理函数,中断发生时就会运行这个中断处理函数
	unsigned long flags;        //中断标志,注册时设置,比如上升沿中断,下降沿中断等
	cpumask_t mask;             //中断掩码
	const char *name;           //中断名称,产生中断的硬件的名字
	void *dev_id;               //设备id,只有在共享中断的时候才会用到!!!
	struct irqaction *next;     //指向下一个成员
	int irq;                    //中断号,
	struct proc_dir_entry *dir; //指向IRQn相关的/proc/irq/

} ____cacheline_internodeligned_in_smp;

/* 用于描述每个外设的外部中断,相当于中断描述符 */
struct irq_desc {
	irq_flow_handler_t    handle_irq;    //指向中断函数, 中断产生后,就会执行这个handle_irq
	struct irq_chip       *chip;         //指向irq_chip结构体,用于底层的硬件访问,下面会介绍
	struct msi_desc       *msi_desc; 
	void                  *handler_data;  
	void                  *chip_data;
	struct irqaction      *action;       /* IRQ action list */   //action链表,用于中断处理函数
	unsigned int          status;        /* IRQ status */
	unsigned int          depth;         /* nested irq disables */
	unsigned int          wake_depth;    /* nested wake enables */
	unsigned int          irq_count;     /* For detecting broken IRQs */
	unsigned int          irqs_unhandled;
	spinlock_t            lock;          
	... ...
	const char            *name;         //产生中断的硬件名字
} ;

struct irq_chip {
	const char   *name;
	unsigned int    (*startup)(unsigned int irq);   //启动中断 
	void            (*shutdown)(unsigned int irq);  //关闭中断
	void            (*enable)(unsigned int irq);    //使能中断
	void            (*disable)(unsigned int irq);   //禁止中断
	void            (*ack)(unsigned int irq);       //响应中断,就是清除当前中断使得可以再接收下个中断
	void            (*mask)(unsigned int irq);      //屏蔽中断源 
	void            (*mask_ack)(unsigned int irq);  //屏蔽和响应中断
	void            (*unmask)(unsigned int irq);    //开启中断源
	... ...
	int              (*set_type)(unsigned int irq, unsigned int flow_type);  //将对应的引脚设置为中断类型的引脚
     ... ...
#ifdef CONFIG_IRQ_RELEASE_METHOD
         void            (*release)(unsigned int irq, void *dev_id);       //释放中断服务函数
#endif
};

管理中断描述符的方法

方法1:(静态方法)

struct irq_desc irq_desc[NR_IRQS];

方法2:(一般都是用动态方法,使用中断少的时候有节省内存的优势)

#ifdef CONFIG_SPARSE_IRQ                     /* 动态处理方法 */
extern void irq_lock_sparse(void);
extern void irq_unlock_sparse(void);
#else                                        /* 静态处理方法 */
static inline void irq_lock_sparse(void);
static inline void irq_unlock_sparse(void);
static struct irq_desc irq_desc[NR_IRQS];
#endif

参考文章:https://blog.csdn.net/qq_42230338/article/details/90053947

共享中断

多个外设的中断信号线接在同一个GPIO的情况。
这时注册中断的时候需要向 request_irq 设备号参数,用于判断是哪个外设触发了中断。

那么request_irq() 如何向内核申请中断?
1.初始化中断描述符(irq_desc)中的 irqaction 结构体;
2.将中断描述符加入内核中断描述符链表。

禁止中断

在调用 free_irq() 时,为了防止此时中断被触发,需要先禁止中断。

void disable_irq_nosync(unsigned int irq);
void disable_irq(unsigned int irq);

disable_irq() 在调用后不会立刻返回,需要等待中断程序执行完才返回,所以不能在中断服务函数中调用该函数。
disable_irq_nosync() 则会在调用后立刻返回。

使能中断

函数原型:

void enable_irq(unsigned int irq);

中断上下文

Linux 内核提供的四种中断下半部中 softirq(软中断)、tasklet(小任务)、workqueue(工作队列) 、request thread(中断线程)。
tasklet 其效率仅次于软中断,但远高于request thread 和 workqueue。
软中断(softirq) 之所以性能高的原因,在 SMP 系统下多个 cpu 同时并发处理
例如:网卡的 fifo 半满中断触发,被 cpu0 处理,cpu0 会在关闭中断后,将数据从网卡的 fifo 拷贝到 ram 之后触发软中断,再打开中断,基于谁触发谁处理原则,cpu0 会继续执行软中断服务函数。若网卡的 fifo 全满中断有再次触发,就会被 cpu1 处理,同样是关闭中断后拷贝数据再开启中断,再去触发和执行软中断进行网卡数据包处理。若此时 cpu0\cpu1 都还在软中断处理数据,网卡再次产生中断,那么 cpu2 就会继续相同的流程。由此可见,软中断充分利用的多 cpu 进行并发处理,因此性能非常高,但也同时因为并发的存在,就需要考虑临界区的问题。
小任务(tasklet) 之所以性能较软中断差,是因为同一种小任务在多个 cpu 上不会并发执行
由于 tasklet 基于 softirq 的基础实现,为了易用性考虑,同一种 tasklet 在多个 cpu 上不会并行执行,因此不存在并发问题,在使用上就可以少一些顾虑。但也正是因为不存在并发,导致了性能较之 softirq 差一些。
tasklet 比 workqueue 和 request thread 性能高,是因为 tasklet 在软中断上下文件工作(意味着不能调用任何阻塞的接口),而后两者是在进程上下文工作(实质上是在内核线程里面执行)。

下文类型taskletsoftirqworkqueuerequest thread
可阻塞?

Tasklets

Tasklets 机制是linux中断处理机制中的软中断延迟机制。在linux中存在着硬中断和软中断的概念区分。
机制流程:当linux接收到硬件中断之后,通过tasklet函数来设定软中断被执行的优先程度从而导致软中断处理函数被优先执行的差异性。
特点:tasklet的优先级别较低,而且中断处理过程中可以被打断。但被打断之后,还能进行自我恢复,断点续运行。

在 Linux 内核中,一般使用 tasklet 机制来实现中断下文,tasklet 任务在同一时间只能在一个 CPU 上运行,所以在多核系统上不会存在并发问题,但正因如此,它的执行优先级会比硬中断低。
另外,tasklet 任务函数不能调用任何可能会引起休眠的函数,否则会导致内核异常。

/* include/linux/interrupt.h */
struct tasklet_struct 
{
     struct tasklet_struct *next;      // 链表中的下一个 tasklet 结构体节点
     unsigned long state;              // tasklet 状态 
     atomic_t count;                   // 原子类型计数器
     void(*func) (unsigned long data); // tasklet 处理函数 
     unsigned long data;               // tasklet 处理函数的参数 
}

tasklet 初始化
动态初始化:动态初始化 tasklet_struct 结构体,将计数器 count 和状态 state 置 0

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

t: tasklet_struct 结构体指针
func:tasklet 处理函数
data:tasklet 处理函数的参数
静态初始化

静态初始和动态初始化功能相同,实际上就是动态初始化的宏函数,

DECLARE_TASKLET(name, func, data);            /* 创建并初始化为使能状态 */
DECLARE_TASKLET_DISABLED(name, func, data);   /* 创建并初始化为失能状态 */

name 对应 tasklet_struct 结构体名,其他参数与动态初始化相同。

DECLARE_TASKLET 和 DECLARE_TASKLET_DISABLED 的区别在于初始化时 tasklet_struct 结构体变量中 count 的值不同,当 count 为 0 时,表示 tasklet 使能,当 count 为 1 时,表示 tasklet 失能。
这里的 count 值会影响 tasklet 任务的调度,只有 count 为 0 时,tasklet 任务才会被调度。

使能 tasklet
tasklet 使能实际就是把 t->count 减 1。

void tasklet_disable(struct tasklet_struct *t);

tasklet 调度函数
tasklet_schedule() 会让 t->func() 执行(前提是 t->count 为 0)。

void tasklet_schedule (struct tasklet_struct *t)

由于 tasklet 任务执行优先级并不高,所以连续多次调用调度函数,可能只会执行一次。

tasklet 取消调度函数
tasklet_kill() 会将已经调度的 tasklet 停止调度。

void tasklet_kill(struct tasklet_struct *t)

如果 tasklet 任务正在被调度执行,tasklet_kill() 将会等待其退出,tasklet_kill() 完成前应避免再次调度。另外,不能在 tasklet 处理函数里调用 tasklet_kill()。

tasklet 实验
在上一个中断实验的代码上添加中断下文处理函数,即 tasklet 任务。

#include <linux/interrupt.h>

int    irq;
struct tasklet_struct mytasklet;

// 中断下文,tasklet 处理函数
void my_tasklet(unsigned long data)
{
    printk("data is %ld.\n", data);
}

// 静态初始化 tasklet
DECLARE_TASKLET(mytasklet, my_tasklet, 6);

// 中断服务函数
irqreturn_t my_interrupt(int irq, void *args)
{
 // tasklet 调度
 tasklet_schedule(&mytasklet);
 return IRQ_RETVAL(IRQ_HANDLED);
}

static int interrupt_irq_init(void)
{
 int ret = 0;
 // 获取中断号
 irq = gpio_to_irq(101);
 // 申请中断
 ret = request_irq(irq, my_interrupt, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);
    if(ret < 0)
     return 0;
 return 0;    
}

static void interrupt_irq_exit(void)
{
 // 注销中断
    free_irq(irq, NULL);

    // 摧毁 tasklet
    tasklet_kill(&mytasklet);
}

当中断被触发时,先执行中断服务函数(中断上文),再执行 tasklet 注册的函数(中断下文)。

软中断

软中断也可以实现中断下文,并且效率更高,但是由于资源有限(中断号少),一般使用在 网络设备驱动、块设备驱动。

/* include/linux/interrupt.h */
enum
{
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,     /* 定时器软中断 */
    NET_TX_SOFTIRQ,    /* 网络发送软中断 */
    NET_RX_SOFTIRQ,    /* 网络接收软中断 */
    BLOCK_SOFTIRQ,     /* 块设备软中断 */
    BLOCK_IOPOLL_SOFTIRQ,
    TASKLET_SOFTIRQ,
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ,
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

    NR_SOFTIRQS        /* 软中断号的数量 */
};

上面是内核中给出了软中断中断号 ,中断号越小就代表级别越高。
TASKLET_SOFTIRQ 是给出来用于中断下文操作,其他的最好用于专用的途径,我们也可以在 NR_SOFTIRQS 之上添加自己的软中断号(注意级别尽量低)。
软中断的接口:

/* 初始化软中断 */
void open_softirq(int nr, void (*action) (struct softirq_action *));
/* 打开软中断 */
void raise_softirq(unsigned int nr);
/* 关闭软中断 */
void raise_soft_irqoff(unsigned int nr);

nr:中断号
action:软中断的处理函数指针

案例:

#include <linux/interrupt.h>

int    irq;

// 软中断处理函数
void testsoft_func(struct softirq_action *softirq_action)
{
    printk("my soft_irq\n");
} 

// 中断服务函数
irqreturn_t my_interrupt(int irq, void *args)
{
 // 打开软中断
 raise_softirq(TASKLET_SOFTIRQ);
 return IRQ_RETVAL(IRQ_HANDLED);
}

static int interrupt_irq_init(void)
{
 int ret = 0;
 // 获取中断号
 irq = gpio_to_irq(101);
 // 申请中断
 ret = request_irq(irq, my_interrupt, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);
 if(ret < 0)
   return 0;
 
 open_softirq(TASKLET_SOFTIRQ,testsoft_func);    

 return 0;    
}

static void interrupt_irq_exit(void)
{
 // 关闭软中断
 raise_soft_irqoff(TASKLET_SOFTIRQ);
 // 注销中断
 free_irq(irq, NULL);
}

以上就可以实现上一个案例的功能了。
但是如果不用 TASKLET_SOFTIRQ ,而使用自己添加的软中断号的话,就需要使用 EXPORT_SYSBOL 宏,将使用到的自定义软中断号的函数在源码都导出到符号表,然后需要再次编译一次内核,才能正常编译出该驱动。

那么为什么说 tasklet 是一种特殊的软中断?

/* kernel/softirq.c */
void __init softirq_init(void)
{
	int cpu;
    for_each_possible_cpu(cpu) {
        per_cpu(tasklet_vec, cpu).tail =
            &per_cpu(tasklet_vec, cpu).head;
        per_cpu(tasklet_hi_vec, cpu).tail =
            &per_cpu(tasklet_hi_vec, cpu).head;
    }
    open_softirq(TASKLET_SOFTIRQ, tasklet_action);
    open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

tasklet_vec 、tasklet_hi_vec 是用于多核CPU中存放用户自定义的软中号。
tasklet_action 是实际处理 tasklet 软中断的回调函数。

softirq_init() ->
    tasklet_action() ->
        tasklet_action_common() 

tasklet_action_common() 中的实现完完全全就是 tasklet API的调用。
另外,tasklet 的调度函数:tasklet_schedule() 最终会调用 __tasklet_schedule_common()

static void __tasklet_schedule_common(struct tasklet_struct *t,
                      struct tasklet_head __percpu *headp,
                      unsigned int softirq_nr)
{
    struct tasklet_head *head;
    unsigned long flags;

    local_irq_save(flags);
    head = this_cpu_ptr(headp);
    t->next = NULL;
    *head->tail = t;
    head->tail = &(t->next);
    raise_softirq_irqoff(softirq_nr);
    local_irq_restore(flags);
}

__tasklet_schedule_common() 会把 tasklet 结构体加载到 CPU 维护的链表上,软中断号 TASKLET_SOFTIRQ 实际就是触发了 tasklet 机制。
参考博客:https://blog.csdn.net/m0_51231887/article/details/130859515

工作队列

工作队列是实现中断下半部分的机制之一,是一种将工作推后执行的形式,工作队列和同为中断下半部分的 tasklet 的区别在于 tasklet 不能休眠(且以原子模式执行),而工作队列可以休眠(不必原子执行)。
内核工作队列分为共享工作队列和自定义工作队列两种。
工作队列的特性
通过工作队列来执行一个工作相比于直接执行,会有一下特性:

  • 异步:工作不是在本中断或线程中执行,而是交由其他线程执行。
  • 延期:交由低优先级线程执行,执行前可能被抢占,也可能有其他工作排在前面执行,所以从提交工作队列到工作真正被执行之间会延时不定长时间。
  • 排队:FIFO 模式先到先执行。也肯会有多个优先级的工作队列,低优先级的工作队列要等到高优先级的工作队列全部执行完成后才能执行。但同一个工作队列内部的各项都是按时间先后顺序执行的额,不会进行钱赞重排。
  • 缓存:既然是队列它就能缓存多个项,需要异步执行丢进去就行,队列会逐个执行。虽然工作队列能缓存多个项,但也是有上限的当队列已满时,新的入队项就会被丢弃,丢弃的个数会被统计下来。

共享工作队列:将新创建的工作任务添加到Linux内核创建的全局工作队列system_wq中,无需自己创建工作队列

自定义工作队列:将新创建的工作任务添加到自己创建的工作队列中;

共享工作队列

共享工作队列是内核提供的默认工作队列,共享意味着不能长时间独占该队列,既不能长时间休眠,且我们的任务可能需要等待更长的时间才能被执行。

#include <linux/workqueue.h>

struct work_struct
{
    atomic_long_t data;
    struct list_head entry;
    work_func_t func;
};

该结构体最重要的成员为 func 函数指针,该函数指针原型为:

typedef void (*work_func_t)(struct work_struct *work);

初始化 work_struct
INIT_WORK() 和 DECLARE_WORK() 用来初始化一个 work_struct 结构体,前者为动态初始化,后者为静态初始化,函数定义为:

INIT_WORK(_work, _func);
DECLARE_WORK(n, f);

_work:work_struct 结构体指针
_func:工作函数指针。

调度工作队列函数

static inline bool schedule_work(struct work_struct *work);

如果想在中断下文执行工作函数,则需要在中断处理函数中调用该函数。

案例:

#include <linux/interrupt.h>
#include <linux/workqueue.h>

int irq;
struct work_struct my_work; 

//要加入到工作队列中的函数
void my_work_func(struct work_struct *work)
{
    printk("my work func.\n");
    msleep(1000);
    printk("msleep finish.\n");
}

//中断服务函数
irqreturn_t my_interrupt(int irq, void *args)
{
    // 将工作队列加入到调度表
    schedule_work(&my_work);

    return IRQ_RETVAL(IRQ_HANDLED);
}

static int interrupt_irq_init(void)
{
 int ret = 0;
 // 获取中断号
 irq = gpio_to_irq(101);
 // 申请中断
 ret = request_irq(irq, my_interrupt, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);
 if(ret < 0)
   return 0;
 // 将工作函数加入到工作队列结构体
 INIT_WORK(&my_work, my_work_func);

 return 0;    
}

static void interrupt_irq_exit(void)
{
    // 注销中断
    free_irq(irq, NULL);
}

自定义工作队列

使用自定义工作队列需要构建一个 workqueue_struct 结构体

/* include/linux/workqueue.h */
struct workqueue_struct {
    struct list_head pwqs;
    struct list_head list;
    ......
    struct list_head maydays;
    struct worker    *rescuer;
    ......
};

创建工作队列函数(都是宏函数):

create_workqueue(name);
create_singlethread_workqueue(name);

name:工作队列名
create_workqueue() 可以给每个 CPU 都创建一个工作队列,创建成功返回 workqueue_struct 结构体指针,失败返回 NULL。
create_singlethread_workqueue() 只给一个 CPU 创建工作队列。

调度和取消调度工作队列

bool queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work);
bool cancel_work_sync(struct work_struct *work);

queue_work_on() 用来调度自定义工作队列;
cancel_work_sync() 用来取消已经调度的工作,并且会等待其完成再返回。

刷新工作队列函数

void flush_workqueue(struct workqueue_struct *wq);

告知内核尽快处理工作队列的工作。

删除工作队列函数

void destroy_workqueue(struct workqueue_struct *wq);

案例:

#include <linux/interrupt.h>
#include <linux/workqueue.h>

int irq;
struct work_struct my_work;
struct workqueue_struct *my_workqueue; 

//工作函数
void my_work_func(struct work_struct *work)
{
    printk("my work func.\n");
    msleep(1000);
    printk("msleep finish.\n");
}

//中断服务函数
irqreturn_t my_interrupt(int irq, void *args)
{
    // 将自定义工作队列加入到内核工作队列
    queue_work(my_workqueue, &my_work);

    return IRQ_RETVAL(IRQ_HANDLED);
}

static int interrupt_irq_init(void)
{
 int ret = 0;
 // 获取中断号
 irq = gpio_to_irq(101);
 // 申请中断
 ret = request_irq(irq, my_interrupt, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);
 if(ret < 0)
   return 0;
    

 // 创建自定义工作队列
 my_workqueue = create_workqueue("my_workqueue");
 // 初始化工作队列
 INIT_WORK(&my_work, my_work_func);

 return 0;    
}

static void interrupt_irq_exit(void)
{
    // 注销中断
    free_irq(irq, NULL);
    // 取消自定义工作队列调度
    cancel_work_sync(&my_work);
    // 刷新工作队列
    flush_workqueue(my_workqueue);
    // 删除工作队列
    destroy_workqueue(my_workqueue);
}

参考博客:https://blog.csdn.net/u013253075/article/details/128396565

内核延时工作队列

struct delayed_work
{
    struct work_struct work;
    struct timer_list timer;
}

work 成员为之前提到的工作结构体,延时工作结构体只比工作结构体多了内核定时器。

初始化延时工作函数
初始化分为静态初始化和动态初始化,DECLARE_DELAYED_WORK(n, f) 用来静态初始化延时工作结构体,INIT_DELAYED_WORK(_work, _func) 用来动态初始化延时工作。

调度延时工作函数
schedule_delayed_work() 的作用是调度共享工作队列上的延时工作,queue_delayed_work() 则用来调度自定义工作队列上的延时工作。

static inline bool schedule_delayed_work(struct delayed_work *dwork, unsinged long delay);
static inline bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay);

dwork 为延时工作结构体变量,delay 为要延时的时间,单位为节拍,queue_delayed_work() 的第一个参数为自定义工作队列结构体指针。

取消调度延时工作函数
用来取消已经调度的延时工作。

bool cancel_delayed_work_sync(struct delayed_work *dwork);

案例:

#include <linux/interrupt.h>
#include <linux/workqueue.h>

int irq;
struct delayed_work my_work; 
struct workqueue_struct *my_workqueue;

//工作函数
void my_work_func(struct work_struct *work)
{
    printk("my work func.\n");
    msleep(1000);
    printk("msleep finish.\n");
} 

//中断服务函数
irqreturn_t my_interrupt(int irq, void *args)
{
    // 调度延时自定义工作队列,延时 3 秒
    queue_delayed_work(my_workqueue, &my_work, 3 * HZ);
    
    return IRQ_RETVAL(IRQ_HANDLED);
}

static int interrupt_irq_init(void)
{
    int ret = 0;
    // 获取中断号
	irq = gpio_to_irq(101);
    // 申请中断
	ret = request_irq(irq, my_interrupt, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);
    if(ret < 0)
    	return 0;

    // 创建自定义工作队列
	my_workqueue = create_workqueue("my_workqueue");
    // 初始化工作队列
    INIT_DELAYED_WORK(&my_work, my_work_func);

    return 0;    
}

static void interrupt_irq_exit(void)
{
    // 注销中断
    free_irq(irq, NULL);
    // 取消延时自定义工作队列
    cancel_delayed_work_sync(&my_work);
    // 刷新工作队列
    flush_workqueue(my_workqueue);
    // 删除工作队列
    destroy_workqueue(my_workqueue);
}

工作队列传参

#include <linux/include/workqueue.h>

typedef void (*work_func_t)(struct work_struct *work);
struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func;
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

INIT_WORK(_work, _func)                     // 初始化一个work
INIT_WORK_ONSTACK(_work, _func)             // 在栈上初始化一个work
flush_work(struct work_struct *work);       // 销毁一个work
schedule_work(struct work_struct *work);    // 调度一个work开始运行

参考博客:https://blog.csdn.net/u010632165/article/details/88240665
工作处理函数的参数为 work_struct,即工作函数运行时可以使用对应的工作结构体变量,如果我们定义一个结构体,并将 work_struct 作为它的成员,那么就能在工作函数中访问我们自定义的结构体变量了。
具体实现需要用到 container_of() 宏函数(函数功能:从结构体某个成员的首地址获取整个结构体的首地址)
案例:

#include <linux/interrupt.h>
#include <linux/workqueue.h>

int irq;

//自定义数据结构体
struct work_data
{
    struct work_struct my_work;
    int a;
    int b;
};

struct work_data my_work_data;
struct workqueue_struct *my_workqueue;

//工作函数
void my_work_func(struct work_struct *work)
{
    struct work_data *pdata;
	pdata = container_of(work, struct work_data, my_work);

    printk("data a is %d.\n", pdata->a);
    printk("data b is %d.\n", pdata->b);
}

//中断服务函数
irqreturn_t my_interrupt(int irq, void *args)
{
    // 调度自定义工作队列
    queue_work(my_workqueue, &my_work_data.my_work);
    return IRQ_RETVAL(IRQ_HANDLED);
}

static int interrupt_irq_init(void)
{
    int ret = 0;
    
    // 获取中断号
	irq = gpio_to_irq(101);
    // 申请中断
	ret = request_irq(irq, my_interrupt, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);
    if(ret < 0)
		return 0;

    // 创建自定义工作队列
	my_workqueue = create_workqueue("my_workqueue");
    // 初始化工作队列
    INIT_WORK(&my_work_data.my_work, my_work_func);
    // 自定义数据结构体成员初始化
	my_work_data.a = 5;
	my_work_data.b = 6;

    return 0;    
}

static void interrupt_irq_exit(void)
{
    // 注销中断
    free_irq(irq, NULL);
    // 取消自定义工作队列
    cancel_work_sync(&my_work_data.my_work);
    // 刷新工作队列
    flush_workqueue(my_workqueue);
    // 删除工作队列
    destroy_workqueue(my_workqueue);
}

并发管理工作队列

并发管理工作队列(CMWQ)设计的目的为:试图保持最小的资源消耗,但要保证足够的并发性。CMWQ使用最小的资源来发挥它的全部能力。
创建一个并发管理工作队列

alloc_workqueue(fmt, flags, max_active);

fmt:要创建工作队列的名称,max_active 为线程池里最大的线程数量(默认填 0),flags 可取值包括:

WQ_UNBOUND             ungound 队列不绑定指定 CPU,不会参与并发管理(不会出现并发冲突问题)
WQ_FREEZABLE           在该队列上工作的工作不会被执行
WQ_MEM_RECLAIM         所有有可能运行在内存回收流程中的工作队列都需要设置该标记
WQ_HIGHPRI             highpri 队列上的工作会被指定的 CPU 上的线程池来处理
WQ_CPU_INTENSIVE       CPU 密集型工作队列

这里只用到了 WQ_UNBOUND 标志,之前介绍的工作队列,每个工作队列只运行在特定的 CPU 上,如果多个工作队列需要并发运行时,创建 unbound 队列可以使内核线程在多个处理器之间迁移。
案例:

#include <linux/interrupt.h>
#include <linux/workqueue.h>

int irq;
struct work_struct my_work;
struct workqueue_struct *my_workqueue;

//工作函数
void my_work_func(struct work_struct *work)
{
    printk("my work func.\n");
    msleep(1000);
    printk("msleep finish.\n");
}

//中断服务函数
irqreturn_t my_interrupt(int irq, void *args)
{
    // 调度自定义工作队列
    queue_work(my_workqueue, &my_work);

    return IRQ_RETVAL(IRQ_HANDLED);
}

static int interrupt_irq_init(void)
{
    int ret = 0;
    
    // 获取中断号
	irq = gpio_to_irq(101);
    // 申请中断
	ret = request_irq(irq, my_interrupt, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);
    if(ret < 0)
    	return 0;

    // 创建并发工作队列
	my_workqueue = alloc_workqueue("my_workqueue", WQ_UNBOUND, 0);
    // 初始化工作队列
    INIT_WORK(&my_work, my_work_func);

    return 0;    
}

static void interrupt_irq_exit(void)
{
    // 注销中断
    free_irq(irq, NULL);
    // 取消自定义任务队列调度
    cancel_work_sync(&my_work);
    // 刷新工作队列
    flush_workqueue(my_workqueue);
    // 删除工作队列
    destroy_workqueue(my_workqueue);
}

中断线程化

中断线程化也是中断下文的方式,与工作队列和软中断不同的是,中断线程只用于这个中断,当发生中断的时候,会唤醒这个内核线程,然后由这个内核线程来执行中断下半部分的函数。

#include <linux/interrupt.h>
int request_threaded_irq(unsigned int irq, irq_handler_t handler, 
                         irq_handler_t thread_fn, unsigned long flags,
                         const char *name, 
void *dev);
/* 向内核注册一个中断服务函数(当 irq 对应的中断发生时,会执行 handler 指向的中断服务函数)*/
typedef irqreturn_t (*irq_handler_t)(int, void *)

irq:中断号(在内核中是唯一的)
handler:中断服务函数指针
thread_fn:中断线程函数(为 NULL 表示不使用中断线程)
flags:中断标志,具体内容由中断源决定,如果中断源为外部中断,则存在上升沿和下降沿两种标志
name:中断名(会在 /proc/interrupts 下体现)
dev:中断服务函数的参数
由此可见,这个函数是直接把中断上下文都给承包了。

#include <linux/interrupt.h>

int irq;

//中断下文(中断线程函数)
irqreturn_t thread_func(int irq, void *args)
{
    msleep(1000);
    printk("This is inttrupt thread func.\n");
    return IRQ_RETVAL(IRQ_HANDLED);
}

//中断服务函数需要唤醒中断线程函数
irqreturn_t my_interrupt(int irq, void *args)
{
    printk("my interrupt handler.\n");    
    return IRQ_WAKE_THREAD;     /* 唤醒中断线程函数 */
}

static int interrupt_irq_init(void)
{
    int ret = 0;
    
    // 获取中断号
	irq = gpio_to_irq(101);
    /* 申请中断线程,这里就把中断号、中断服务函数、线程回调都一起注册了 */
 	ret = request_threaded_irq(irq, my_interrupt, thread_func, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);
    if(ret < 0)
    	return 0;
    return 0;    
}

static void interrupt_irq_exit(void)
{
    // 注销中断
    free_irq(irq, NULL);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值