Linux内核编程(十)中断

本文目录

  

一、知识点

在这里插入图片描述

1. 什么是中断?

   CPU在正常运行期间,由外部或者内部引起的事件,让CPU停下当前正在运行的程序,转而去执行触发他的中断所对应的程序,处理完中断对应的程序以后在回来继续执行。这个就是中断。举例:同学A现在正在厨房做饭,突然电话响了,然后A关火去接电话。接完电话在回去开火继续做饭。这个过程就是一个中断的一个过程。

2. 中断上/下文

   中断可以提高CPU的运行效率,但是中断会打断内核进程中正常调度和运行,为了保证系统的实时性,中断服务程序必须足够短。但是在实际应用过程中发生中断的时候要处理很多事情。如果这时候都在中断服务函数中来完成,就会降低中断的实时性。基于这个原因,linux提出了一个概念,把中断服务程序分成俩个部分,中断上文(中断上半部分)和中断下文(中断下班部分)。
  上半部就是之前所说的中断处理函数,它能最快的响应中断,并且做一些必须在中断响应之后马上要做的事情。而一些需要在中断处理函数后继续执行的操作,内核建议把它放在下半部执行,也就是说原本应当在中断服务函数中执行但通过某种方式把它们放到中断服务函数外执行,这样中断处理函数就会快进快出。
   下半部是可中断的,而上半部是不可中断的。即下半部分可以被新的中断打断,而上班部分是不可以被打断的。

(1)中断上文(中断上半部分)

   完成尽可能少且比较急的任务。特点就是相应快。执行最低限度的工作,以尽快释放中断。不能执行可能导致睡眠的操作,如调用可能会阻塞的函数!

(2)中断下文(中断下半部分)

   处理比较耗时的任务。下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。
   实现中断下文常用的方法有:软中断、tasklet(特殊的软中断) 以及 工作队列。这里我们主要使用后两个方法来实现中断下文。

3. 中断控制器

   顾名思义,中断控制器就是用来控制中断的,可以通过中断控制器来控制中断的打开,关闭,优先级控制等等。ARM架构中的中断控制器一般叫做GIC。STM32上的中断控制器为NVIC。

在这里插入图片描述

中断控制器也可以级联,来完成不同的需求。
在这里插入图片描述

4. 中断号

中断号(Interrupt Number)是计算机系统中用来区分不同中断请求的标识符。

(1)软件中断号(IRQ_number)

   软件中断号是由软件发出的中断信号的标识符。软件中断通常用于系统调用、异常处理和其他需要中断当前执行流的操作。

(2)硬件中断号 (HW interrupt ID)

   硬件中断号是来标志外设中断的,由硬件设备产生的中断信号的标识符。每个硬件设备或每个中断源在中断控制器中都有一个唯一的中断号,用于通知CPU某个硬件设备需要处理。硬件中断与外部设备有关。

(2)IRQ domain

负责硬件中断号和软件中断号的映射关系。

5. 中断源类型

类型描述
SGI (Software Generated Interrupt)中断号在0~15之间。此类用于core之间相互通信,也叫做软件中断
PPI (Private Perpheral Interrupt)中断号在16~31之间。此类中断是每个core私有的,所以也叫做私有中断,比如每个core上有一个tick中断,用于进程调度使用。
SPI (Shared Perpheral Interrupt )中断号在32~1020之间。此类中断是由外设触发的中断信号,比如按键,串口等中断。也叫做共享中断
LPI (Local-sperical Perpherial Interrupt)此类中断不支持GIC-v1,GIC-v2。

   共享中断:指的是在计算机系统中,多个硬件设备共享同一个中断请求线(IRQ)的情况。当一个共享中断请求线被触发时,多个设备驱动程序需要检查是否是由它们控制的设备引发的中断,并进行相应的处理。在实现共享中断时,设备驱动程序通常会在中断服务程序(ISR)中检查硬件状态,以确定是否是该设备引发的中断。如果是,则处理该中断;如果不是,则将中断请求传递给下一个设备驱动程序进行检查。

   私有中断:是指每个硬件设备拥有自己专用的中断请求线(IRQ),不会与其他设备共享。当私有中断请求线被触发时,系统可以确定是哪一个特定设备发出的中断信号,因此处理起来更为简单和高效。私有中断通常用于关键任务或高性能的硬件设备,例如:处理器内部设备:如定时器,高优先级设备:如网络接口卡、磁盘控制器等,需要快速响应的设备。

二、中断上文常用API

头文件:
#include <linux/interrupt.h>
#include <linux/gpio.h>

1. 向内核申请一个中断

返回0成功,返回非0值失败。

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
//unsigned int irq :中断号
//irq_handler_t handler :中断处理函数。
//unsigned long flags:中断标志(上升沿/下降沿..../共享中断/...)
//const char *name :中断名称,自定,主要用于区分。
//void *dev :传递给中断处理函数的参数,没有时传NULL。

其中IRQ 处理函数的返回值用来通知内核中断处理的结果和后续动作。有以下返回值:

IRQ_NONE //表示中断处理函数没有处理该中断。这通常意味着中断不是由当前设备触发的,内核会继续查找其他可能的中断处理程序。
IRQ_HANDLED	 // 表示中断处理函数成功处理了该中断。这告诉内核中断已被正确处理,不需要进一步的动作。
IRQ_WAKE_THREAD	 //表示中断处理函数需要唤醒与中断关联的线程化中断处理程序。

2. 获取中断号函数(gpio序号转中断号)

返回值为中断号。

int gpio_to_irq(unsigned int gpio)
//unsigned int gpio :gpio序号

3. 注销中断函数

void free_irq(unsigned int irq, void *dev_id)
//unsigned int irq :中断号
//void *dev_id :如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。常用NULL。

★实验:GPIO中断(实现中断上文)

功能:gpio上升沿时触发中断,打印输出内容。

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/gpio.h>

int irq;
//中断上文!!
irqreturn_t interrupt_handler1(int irq, void *arg)
{
   printk("interrupt_handler1 !\n");
   return IRQ_HANDLED;
}

static int __init interrupt_init(void)
{
	int ret;
	irq=gpio_to_irq(38);
	printk("irq:%d\n",irq);
    ret=request_irq(irq, interrupt_handler1, IRQF_TRIGGER_RISING, "test_interrupt", NULL);
	if(ret <0){
       printk("request_irq error!\n");
	   return -1;
	}
 
   return 0;
}

static void __exit interrupt_exit(void)
{
	 free_irq(irq, NULL);
	 printk("bye bye!\n");
}

module_init(interrupt_init);
module_exit(interrupt_exit);
MODULE_LICENSE("GPL");

三、中断下文机制

1. tasklet机制

   tasklet是一种特殊的软中断,在Linux内核中,一般使用tasklet机制来实现中断下文,也是最常用的一种方法。tasklet绑定的函数在同一时间只能在一个CPU上运行,所以在多核处理系统上不会出现并发的问题。注意:在tasklet绑定的函数中不能调用任何可能会引起休眠的函数,否则会导致内核异常!!

#include <linux/interrupt.h>
struct tasklet_struct {
    struct list_head list;         // 用于将 tasklet 链入全局 tasklet 链表
    unsigned long state;           // 表示 tasklet 的状态。0(未调度),1(调度)
    atomic_t count;                // 引用计数, 0(使能),1(失能)
    void (*func)(unsigned long);   // 指向 tasklet 函数的指针
    unsigned long data;            // 传递给 tasklet 函数的参数
};

(1)初始化tasklet

注意:tasklet初始化后默认为使能且未被调度状态。

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data)
//struct tasklet_struct *t :指向要初始化的 tasklet_struct 结构体的指针。
//void (*func)(unsigned long):指向 tasklet 函数的指针,这个函数将在 tasklet 被调度时执行。
//unsigned long data:传入给调度函数的参数。

(2)tasklet使能函数

void tasklet_enable(struct tasklet_struct *t);//把tasklet变成使能状态。本质上就是将count成员的值-1;

(3)tasklet失能函数

void tasklet_disable(struct tasklet_struct *t);//把tasklet变成非使能状态。本质上就是将count成员的值+1;

(4)tasklet调度函数

   功能:在tasklet处于使能的状态时,调用tasklet调度函数,绑定的调度函数会在不确定的时间后被调度。

void tasklet_schedule(struct tasklet_struct *t);

(5) tasklet取消调度函数

   功能:销毁一个已初始化的 tasklet_struct,确保其已经被处理完毕并且不再被调度。它通常在模块卸载或者设备驱动程序退出时使用,以确保系统资源被正确释放。

void tasklet_kill(struct tasklet_struct *t);

★实验:GPIO中断(tasklet实现中断下文)

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/gpio.h>

int irq;
struct tasklet_struct my_tasklet;

//中断下文!
void tasklet_task(unsigned long data)
{
  printk("The data is:%d\n",data);
}

//中断上文!!
irqreturn_t interrupt_handler1(int irq, void *arg)
{
 printk("interrupt_handler1 !\n");
 tasklet_schedule(&my_tasklet); //调度tasklet,调度时间由系统分配。此时中断函数执行完毕,退出。
 return IRQ_HANDLED;
}

static int __init interrupt_init(void)
{
  int ret;
  irq=gpio_to_irq(38);
  printk("irq:%d\n",irq);
  ret=request_irq(irq, interrupt_handler1, IRQF_TRIGGER_RISING, "test_interrupt", NULL);
  if(ret <0){
     printk("request_irq error!\n");
     return -1;
  }
  tasklet_init(&my_tasklet, tasklet_task, 10);  //初始化tasklet,此时为使能状态。
  return 0;
}

static void __exit interrupt_exit(void)
{
   free_irq(irq, NULL);
   tasklet_kill(&my_tasklet);
   printk("bye bye!\n");
}

module_init(interrupt_init);
module_exit(interrupt_exit);
MODULE_LICENSE("GPL");

2. 工作队列

   工作队列是实现中断下半部分的机制之一, 是一种将工作推后执行的形式。工作队列和之前学的tasklet有什么不同呢? tasklet也是实现中断下半部分的机制之一。他们两个最主要的区别是tasklet不能休眠,而工作队列是可以休眠的。所以,tasklet可以用来处理比较耗时间的事情,而工作队列可以处理更耗时间的事情。
   工作队列将工作推后以后,会交给内核线程去执行。Linux在启动过程中会创建一个工作者内核线程,这个线程创建以后处于sleep状态。当有工作需要处理的时候,会唤醒这个线程去处理这个工作。

   ●内核工作队列分成共享工作队列、自定义工作队列两种。那这两种队列的区别是什么呢?
答:共享工作队列
  优点:共享工作队列是由内核预先定义并提供的全局工作队列。使用共享工作队列非常简单,多个任务共享同一个工作队列,减少了创建多个队列的内存开销和复杂性,不需要额外的初始化和销毁工作。减少了对代码的维护,因为不需要为每个任务创建和管理自己的工作队列。
  缺点:多个任务共享同一个队列可能导致资源竞争,影响任务的执行效率。任务可能因为队列中的其他任务而延迟执行。

  自定义工作队列正好与之相反。每个任务使用独立的工作队列,避免了与其他任务的资源竞争。但也增加了开销。
  在使用时优先使用共享工作队列,当共享工作队列不满足要求时。使用自定义工作队列。

●共享工作队列

  下面结构体为工作任务结构体,因为共享工作队列使用的是内核已经定义好的工作队列,所以不需要自定义即可使用。头文件:#include <linux/workqueue.h>

//任务结构体
struct work_struct {
        struct task task; /* FreeBSD task */
        work_state_t state; /* the pending or otherwise state of work. */
        work_func_t func; //工作任务函数,我们只需要实现这个成员即可!       
};
(1)初始化工作任务
INIT_WORK(_work, _func); 
//_work: work_struct结构体
//_func :工作函数。
(2)调度工作任务
bool schedule_work(struct work_struct *work);
(3)取消调度工作任务

  功能:取消一个已经调度的工作,如果被取消的工作已经正在执行,则会等待它执行完成在返回。

bool cancel_work_sync(struct work_struct *work);

★实验:GPIO中断(共享工作队列实现中断下文)

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/gpio.h>

int irq;
struct work_struct  my_work; //定义工作任务

//中断下文!
void work_task(struct work_struct *work)
{
    msleep(2000);
    printk("this is work_task!\n");
}

//中断上文!!
irqreturn_t interrupt_handler1(int irq, void *arg)
{
  	printk("interrupt_handler1 !\n");
  	schedule_work(&my_work);  //调度工作任务
  	return IRQ_HANDLED;
}

static int __init interrupt_init(void)
{
  	int ret;
  	irq=gpio_to_irq(38);
  	printk("irq:%d\n", irq);
  	ret=request_irq(irq, interrupt_handler1, IRQF_TRIGGER_RISING, "test_interrupt", NULL);
  	if(ret <0){
  		printk("request_irq error!\n");
  		return -1;
  	}
  	
  	INIT_WORK(&my_work, work_task);  //初始化共享工作任务
  	return 0;
}

static void __exit interrupt_exit(void)
{
  	free_irq(irq, NULL);
  	cancel_work_sync(&my_work);
  	printk("bye bye!\n");
}

module_init(interrupt_init);
module_exit(interrupt_exit);
MODULE_LICENSE("GPL");

●自定义工作队列

  内核使用struct workqueue_struct结构体描述一个工作队列,定义在#include <linux/workqueue.h>中。使用自定义工作队列,我们同样需要使用工作任务结构体work_struct来实现中断下文 。

//工作队列结构体
struct workqueue_struct {
	struct list_head	pwqs;		/* WR: all pwqs of this wq */
	struct list_head	list;		/* PL: list of all workqueues */
	........
}
(1)创建工作队列
struct workqueue_struct * create_workqueue(const char *name);
//创建工作队列,name为工作队列的名称,自定义。
(2)调度工作队列

  功能:用于将一个工作任务添加到特定 CPU 的工作队列上执行。

bool queue_work(struct workqueue_struct *wq, struct work_struct *work);
/*
wq:指向目标工作队列的指针。可以是系统提供的共享工作队列,也可以是用户自定义的工作队列。
work:指向要调度的工作任务的指针。
*/
(3)取消调度工作任务

  功能:取消一个已经调度的工作,如果被取消的工作已经正在执行,则会等待它执行完成在返回。

bool cancel_work_sync(struct work_struct * work);
(4)等待工作队列中任务全部完成

  用于等待特定工作队列中的所有工作项完成。它确保在函数返回时,工作队列中已提交的所有工作项都已经执行完毕。这个函数对于在卸载模块或销毁工作队列之前确保所有挂起的工作项已处理完毕非常有用。

void flush_workqueue(struct workqueue_struct * wq);
(5)删除工作队列

  注意:必须在确保工作队列中的所有工作项都已完成的情况下调用该函数!即在使用该函数前必须调用flush_workqueue()函数。

void destroy_workqueue(struct workqueue_struct * wq);

★实验:GPIO中断(自定义工作队列实现中断下文)

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/gpio.h>

int irq;
struct workqueue_struct *my_queue; //工作队列
struct work_struct  my_work; //工作任务

//中断下文!
void task_work(struct work_struct *work)
{
    msleep(2000);
    printk("this is task_work!\n");
}

//中断上文!!
irqreturn_t interrupt_handler1(int irq, void *arg)
{
  	printk("interrupt_handler1 !\n");
  	queue_work(my_queue,&my_work); //将工作任务放至工作队列中,等待调度。
  	return IRQ_HANDLED;
}

static int __init interrupt_init(void)
{
  	int ret;
  	irq=gpio_to_irq(38);
  	printk("irq:%d\n", irq);
  	ret=request_irq(irq, interrupt_handler1, IRQF_TRIGGER_RISING, "test_interrupt", NULL);
  	if(ret <0){
  		printk("request_irq error!\n");
  		return -1;
  	}
  	my_queue=create_workqueue("work_queue"); //创建工作队列
  	INIT_WORK(&my_work, task_work);  //初始化共享工作任务
  	return 0;
}

static void __exit interrupt_exit(void)
{
  	free_irq(irq, NULL); //释放申请的中断
  	cancel_work_sync(&my_work); //取消调度任务
  	flush_workqueue(my_queue); //等待任务全部完成。
  	destroy_workqueue(my_queue); //删除工作队列
  	printk("bye bye!\n");
}

module_init(interrupt_init);
module_exit(interrupt_exit);
MODULE_LICENSE("GPL");

●延迟工作

  延迟工作是 Linux 内核中的一种机制,允许开发者将某些任务延迟到未来的某个时间点执行。
  使用场景:①在处理硬件输入(如按键、触摸屏)时,去抖动是一个常见问题。延迟工作可以用来实现去抖动,通过延迟一段时间来确认输入的稳定性。②当有大量相似的任务需要处理时,可以将它们聚合在一起,通过延迟工作一次性处理,以减少开销。③有些任务需要定期执行,但不需要精确定时。延迟工作可以用来实现这种功能。例如,定期检查系统状态、刷新缓存等。
  内核使用struct delayed_work结构体描述一个延迟工作,定义在include/linux/workqueue.h当中。

struct delayed_work {
    struct work_struct work;       // 基础的工作结构体
    struct timer_list timer;       // 用于实现延迟的定时器
};
(1)定义并初始化延迟工作
INIT_DELAYED_WORK(_work,_func);
//_work:工作任务
//_func :工作函数。
(2)调度延迟工作

共享工作队列使用:

static inline bool schedule_delayed_work(struct delayed_work *dwork,
					 unsigned long delay);
//struct delayed_work *dwork:要排队的 struct delayed_work 结构体。
//delay:延迟时间,以 jiffies 为单位(一个 jiffy 是系统时钟的一个时间单位,通常为 1/HZ 秒)。

自定义工作队列使用:

static inline bool queue_delayed_work(struct workqueue_struct *wq,
				      struct delayed_work *dwork,
				      unsigned long delay)
/*
struct workqueue_struct *wq:工作队列
struct delayed_work *dwork:要排队的 struct delayed_work 结构体。
delay:延迟时间,以 jiffies 为单位(一个 jiffy 是系统时钟的一个时间单位,通常为 1/HZ 秒)。
*/
(3)取消调度延迟工作
bool cancel_delayed_work(struct delayed_work *dwork);

★实验:GPIO中断(延迟工作)

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/gpio.h>

int irq;
struct workqueue_struct *my_queue; //工作队列
struct delayed_work my_delaywork; //定义延时工作任务

//中断下文!
void task_work(struct work_struct *work)
{
    printk("this is task_work!\n");
}

//中断上文!!
irqreturn_t interrupt_handler1(int irq, void *arg)
{
  	printk("interrupt_handler1 !\n");
    queue_delayed_work(my_queue,&my_delaywork,3*HZ); //调度延迟工作。
  	return IRQ_HANDLED;
}

static int __init interrupt_init(void)
{
  	int ret;
  	irq=gpio_to_irq(38);
  	printk("irq:%d\n", irq);
  	ret=request_irq(irq, interrupt_handler1, IRQF_TRIGGER_RISING, "test_interrupt", NULL);
  	if(ret <0){
  		printk("request_irq error!\n");
  		return -1;
  	}
  	my_queue=create_workqueue("work_queue"); //创建工作队列
  	INIT_DELAYED_WORK(&my_delaywork, task_work);  //初始化共享工作任务
  	return 0;
}

static void __exit interrupt_exit(void)
{
  	free_irq(irq, NULL); //释放申请的中断
  	cancel_delayed_work(&my_delaywork); //取消调度任务
  	flush_workqueue(my_queue); //等待任务全部完成。
  	destroy_workqueue(my_queue); //删除工作队列
  	printk("bye bye!\n");
}

module_init(interrupt_init);
module_exit(interrupt_exit);
MODULE_LICENSE("GPL");

●并发管理工作队列(CMWQ)

  传统工作队列(单线程工作队列)每个 CPU 只有一个工作线程,所有的工作项按顺序执行,无法并行处理。这种方式在处理大量并发任务时,可能会导致任务积压,处理效率低下。在传统工作队列中,高优先级任务可能会被低优先级任务阻塞,无法及时得到处理,影响系统的优先级调度机制等此类问题。于是为了解决这些问题,引入了并发管理工作队列。
  并发管理工作队列使用API与传统工作队列(自定义工作队列)使用的API具有兼容性。唯一不同的地方是在创建工作队列时的API不一样,并发工作管理队列创建工作队列API如下所示:

struct workqueue_struct *alloc_workqueue(const char *fmt, unsigned int flags, int max_active, ...);
/*
const char *fmt :工作队列的名称,自定义。
unsigned int flags:
		WQ_UNBOUND:创建一个不受 CPU 绑定限制的工作队列,这样的工作队列中的工作项可以在任何 CPU 上执行。
		WQ_HIGHPRI:创建一个高优先级的工作队列。
		WQ_CPU_INTENSIVE:用于 CPU 密集型任务的工作队列。
		WQ_FREEZABLE:当系统冻结时,工作队列也会被冻结。
		WQ_MEM_RECLAIM:允许该工作队列在内存紧张时执行内存回收操作。
int max_active:工作队列中可以同时激活的工作项的最大数量。
*/

★实验:GPIO中断(并发管理工作队列实现中断下文)

  并发管理工作队列的代码与自定义工作队列的代码区别就在于创建工作队列时使用的API函数不一样,其余地方完全一致。

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/gpio.h>

int irq;
struct workqueue_struct *my_queue; //工作队列
struct work_struct  my_work; //工作任务

//中断下文!
void task_work(struct work_struct *work)
{
  msleep(2000);
  printk("this is task_work!\n");
}

//中断上文!!
irqreturn_t interrupt_handler1(int irq, void *arg)
{
	printk("interrupt_handler1 !\n");
	queue_work_on(0, my_queue,&my_work); //将工作任务放至工作队列中,等待调度。
	return IRQ_HANDLED;
}

static int __init interrupt_init(void)
{
	int ret;
	irq=gpio_to_irq(38);
	printk("irq:%d\n", irq);
	ret=request_irq(irq, interrupt_handler1, IRQF_TRIGGER_RISING, "test_interrupt", NULL);
	if(ret <0){
		printk("request_irq error!\n");
		return -1;
	}
	my_queue=alloc_workqueue("work_queue",WQ_UNBOUND,3); //创建工作队列
	INIT_WORK(&my_work, task_work);  //初始化共享工作任务
	return 0;
}

static void __exit interrupt_exit(void)
{
	free_irq(irq, NULL); //释放申请的中断
	cancel_work_sync(&my_work); //取消调度任务
	flush_workqueue(my_queue); //等待任务全部完成。
	destroy_workqueue(my_queue); //删除工作队列
	printk("bye bye!\n");
}

module_init(interrupt_init);
module_exit(interrupt_exit);
MODULE_LICENSE("GPL");

3. 中断线程化

  中断线程化是将中断处理的下半部分放到内核线程中执行的一种机制。通过这种方式,下半部分可以像普通进程一样调度和执行,可以阻塞、睡眠,从而避免了在中断上下文中执行长时间的操作。
  中断线程化的处理仍然可以看作是将原来的中断分成中断上半部分和中断下半部分。上半部分还是用来处理紧急的事情。下半部分也是处理比较耗时的操作,但是下半部分会交给一个专门的内核线程来处理。这个内核线程只用于这个中断。当发生中断的时候,会唤醒这个内核线程,然后由这个内核线程来执行中断下半部分的函数。

(1)创建中断,并使用线程处理。

  该API与前文中提到的request_irq()函数相同,只不过request_irq()是对该API的进一步封装,将第三个参数(线程处理任务函数)填为了NULL。而此处,我们需要使用中断线程化,则第三个参数我们不填NULL,具体使用查看标题四中的内容。

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)
/*
unsigned int irq :中断号。
irq_handler_t handler:中断上半部分执行的函数。
irq_handler_t thread_fn:交给线程处理的任务函数。即中断下半部分执行的函数。为NULL则不使用中断线程处理。
//unsigned long irqflags:中断标志(上升沿/下降沿..../共享中断/...)
//const char *devname :中断名称,自定,主要用于区分。
//void *dev :传递给中断处理函数的参数,没有时传NULL。
*/

(2)唤醒线程

  在中断上半部分函数中,返回IRQ_WAKE_THREAD 来唤醒中断线程,在中断线程函数中执行中断下半部分的代码,最后返回IRQ_HANDLED表示中断被正常处理完成。

★实验:GPIO中断(中断线程化实现中断下文)

  并发管理工作队列的代码与自定义工作队列的代码区别就在于创建工作队列时使用的API函数不一样,其余地方完全一致。

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/gpio.h>

int irq;

//中断下文!
irqreturn_t thread_task(int irq, void *arg)
{
  msleep(2000);
  printk("this is task_work!\n");
  return IRQ_HANDLED;
}

//中断上文!!
irqreturn_t interrupt_handler(int irq, void *arg)
{
	printk("interrupt_handler1 !\n");
	return IRQ_WAKE_THREAD;
}

static int __init interrupt_init(void)
{
	int ret;
	irq=gpio_to_irq(38);
	printk("irq:%d\n", irq);
	ret=request_threaded_irq(irq, interrupt_handler, thread_task, IRQF_TRIGGER_RISING, "test_interrupt", NULL);
	if(ret <0){
		printk("request_irq error!\n");
		return -1;
	}
	return 0;
}

static void __exit interrupt_exit(void)
{
	free_irq(irq, NULL); //释放申请的中断
	printk("bye bye!\n");
}

module_init(interrupt_init);
module_exit(interrupt_exit);
MODULE_LICENSE("GPL");
  • 17
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值