Linux驱动学习之中断与等待队列

本篇分为设备树部分和API接口部分

设备树

想要使用中断,设备树中需要有两个属性:

interrupts // 表示要使用哪一个中断, 中断的触发类型等等。

interrupt-parent // 这个中断要接到哪一个设备去? 即父中断控制器是谁

父中断控制器有两种指定方法:

  • 只有一个父中断设备
  • 有多个父中断设备

1)、只有一个父中断设备

interrupt-parent = <&父设备标号>;

interrupts = <... ...>, <... ...>;

2)、有多个父设备节点

interrupts-extended = <&父设备标号 .....>, <... ... ...>;

通过不断往里跳可以发现 设备树查找有这两种方式

方法一

此方法中,由于查看父节点是靠 interrupt-parent实现的,所以此属性不可少.

方法二

不断跳就会发现这个循环

此方法中是通过属性中第一个元素找出的父节点,所以无需指定中断父节点

上述属性用多少个u32表示

由它的父中断控制器来描述,在父中断控制器中, 至少有2个属性:

interrupt-controller; // 表示自己是一个中断控制器

#interrupt-cells // 表示自己的子设备里应该用几个U32的数据来描述中断

如此图,此节点是父节点中断控制器,他的子节点用2个u32 描述中断。

如何找到一个节点的父中断控制器

一般在描述子中断节点中都会有一个属性interrupt-parent,由此属性描述。

如果子中断节点中没有此属性,需要查看此节点的父节点,一级一级往上直到父节点中出现interrupt-parent。

API函数

获取中断号

cpp 复制代码
int platform_get_irq(struct platform_device *dev, unsigned int num)

参数一:平台设备,

参数二:设备树里第几个中断引脚,一般给0

返回值:中断号

ps:需要在设备树里指定。

cpp 复制代码
int gpio_to_irq(unsigned gpio)

参数:io口号

返回值:中断号

ps:此函数无需在设备树里指定中断信息,直接调用可直接在根目录下的gpio中断控制器里申请返回一个中断号,一旦在设备树里指定,也可获得中断号,不过与不指定的中断号可能存在差异,不过不影响,都可以实现中断。

注册中断

cpp 复制代码
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)

参数一:中断号

参数二:中断回调函数

参数三:在什么边沿触发

参数四:标签,可随意

参数五:中断回调函数传入的参数

cpp 复制代码
int 
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
		 unsigned long irqflags, const char *devname, void *dev_id)

此函数也可以注册中断,建议使用这个,参数跟上面函数大差不差不在重复介绍。

devm_request_irq和request_irq区别在于前者是申请的内核"managed"资源,不需要自己手动释放,会自动回收资源,而后者需要手动调用free_irq来释放中断资源

中断回调函数类型

flag类型

注销函数

cpp 复制代码
const void *free_irq(unsigned int irq, void *dev_id)

参数一:中断号

参数二:与注册函数传入参数一致。

cpp 复制代码
void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)

参数与上述函数一致,注意配套使用
使用中断后,我们就可以在按下按键后就可以收到一次消息,但是由于上层函数在while循环里一直执行,每调用一次read就会调用底层内核的read,不会阻塞,所以cpu占用率百分之百,此时这就是个病毒驱动。但是我们在学习系统编程的时候,read是一个阻塞函数,当然那时内核实现的他的阻塞,所以我们就需要在内核里把read阻塞,当按键按下进入中断再让read解除阻塞,减少cpu的占用率。

等待队列

等待队列是目前内核最常用的阻塞同步机制之一
可以在内核层产生一个阻塞( 等于放弃 CPU 的! )
while(1); ->这种叫死等 -> 效率最低 占用 CPU 最高一种等待机制
所有的系统的等待机制都绝非死等-> 都是放弃 CPU ,后续被换唤醒机制!
在 FreeRTOS 大家应该接触过类似的概念
等待队列非常类似之前大家在系统层面学习 线程的同步和互斥:条件变量!
等待队列除了创建之外就两个函数,也是两个功能:
一个功能叫做: 阻塞 -> 调用后立刻产生挂起
wait_event
唤醒->调用会唤醒之前挂起进程 / 程序
wake_up
内核层的阻塞会引起上层的阻塞!
举个例子:我在 open-> 调用 " 阻塞 " -> 上层 open 也会阻塞
我在 read->调用 " 阻塞 " --> 上层 read 也会阻塞

等待队列的创建:(宏定义函数->用空间换时间)

cpp 复制代码
DECLARE_WAIT_QUEUE_HEAD(name);
cpp 复制代码
#define DECLARE_WAIT_QUEUE_HEAD(name)                                          \
    struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

// Expands to
struct wait_queue_head queue = { . lock = ( spinlock_t ) { { . rlock = { . raw_lock = { { . val = { ( 0 ) } } } , } } } , . head = { & ( queue ) . head , & ( queue ) . head } }

此函数为宏函数, 声明的时候已经帮我们初始化好了,括号里面是我们创建的变量名字

阻塞函数

cpp 复制代码
 wait_event_interruptible(wq_head, condition)

此函数也也为宏函数,可以看出,当condition为1时,不阻塞,为0时阻塞,另外第二个参数必须为变量,如果填0,则当唤醒函数执行后不会解除阻塞。wait_event_interruptible(以及wait_event打头的其他变体)是Linux的wait queue机制提供的线程同步接口(应先cond=0,把cond传进去)另外也可以看出,参数一直接传入变量名即可

唤醒函数

cpp 复制代码
wake_up_interruptible(x)

此函数也为宏函数, 跳进去可发现,参数传入变量名地址。调用完该函数cond设置为1.

整体代码

cpp 复制代码
#include "linux/cdev.h"
#include "linux/device.h"
#include "linux/device/class.h"
#include "linux/export.h"
#include "linux/fs.h"
#include "linux/gpio.h"
#include "linux/interrupt.h"
#include "linux/irq.h"
#include "linux/module.h"
#include "linux/of_device.h"
#include "linux/of_gpio.h"
#include "linux/platform_device.h"
#include "linux/printk.h"
#include "linux/types.h"
#include "linux/uaccess.h"
#include "linux/wait.h"
#include "linux/zconf.h"
uint32_t pin;
dev_t dev_num;
struct cdev *cdev;
struct class *cls;
struct device * dev;
uint8_t cond;
DECLARE_WAIT_QUEUE_HEAD(queue);
uint8_t val;
irqreturn_t fun(int i, void * a)
{
    val=gpio_get_value(pin);
   printk("%d\r\n",val);
    wake_up_interruptible(&queue);
    cond=1;
    return 0;
}
static ssize_t read(struct file *f, char __user *b, size_t s, loff_t *offt)
{
    cond=0;
    wait_event_interruptible(queue,cond);
    int a=copy_to_user(b,&val,1);
    if(a)
    {
        
    }
    return 0;
}
static int open(struct inode *i, struct file *f)
{
    int ret=devm_request_irq(dev, gpio_to_irq(pin),fun,IRQ_TYPE_EDGE_FALLING,"key", NULL);
   printk("%d\r\n",ret);
    return ret;
}
static int close(struct inode *i, struct file *f)
{
  devm_free_irq(dev,gpio_to_irq(pin),NULL);
    return 0;
}
struct file_operations fops={
   .owner=THIS_MODULE,
   .read=read,
   .open=open,
   .release=close,
};

int probe(struct platform_device *d)
{
    // DECLARE_WAIT_QUEUE_HEAD(name);
    // wait_event_interruptible(,);
    // wake_up_interruptible(x)
    
    platform_get_irq();
   //free_irq();
   // devm_free_irq();
   dev=&d->dev;
    pin=of_get_named_gpio(d->dev.of_node,"key_pin",0);
   
    printk("%d\r\n",pin);
   printk("%d\r\n", platform_get_irq(d,0));
    printk("irq_num=%d", gpio_to_irq(pin));
    gpio_request(pin,"key");
    gpio_direction_input(pin);
   // devm_request_irq(&d->dev, gpio_to_irq(pin),fun,IRQ_TYPE_EDGE_FALLING,"key", NULL);
    alloc_chrdev_region(&dev_num, 0, 1,"key");
    cdev=cdev_alloc();
    cdev->ops=&fops;
    cdev_add(cdev,dev_num,1);
    cls=class_create(THIS_MODULE, "key");
    device_create(cls, NULL,dev_num,NULL,"key");
    return 0;
}
int remove(struct platform_device *d)
{
    return 0;
}
static struct of_device_id match={
    .compatible="key",
};

static struct platform_driver driver={
    .probe=probe,
    .remove=remove,
    .driver={
        .name="key",
        .of_match_table=&match,
    },
};
static int __init start(void)
{
    platform_driver_register(&driver);
    printk(KERN_INFO "Hello, world!\n");
    return 0;
}
static void __exit stop(void)
{
    platform_driver_unregister(&driver);
    printk(KERN_INFO "Goodbye, world!\n");
}
module_init(start);
module_exit(stop);
MODULE_LICENSE("GPL");
相关推荐
AOwhisky5 小时前
Python 学习笔记(第一期与第二期)——基础语法——核心知识点自测与详解
开发语言·笔记·python·学习·云原生·运维开发
STLearner6 小时前
ICML 2026 | LLM×Graph论文总结[1]【图基础模型,文本属性图,多模态属性图,图对齐,图提示学习,关系深度学习
论文阅读·人工智能·python·深度学习·学习·机器学习·数据挖掘
什巳7 小时前
JAVA练习278- 和为 K 的子数组
java·学习·算法·leetcode
碎光拾影8 小时前
CPU与MCU核心区别揭秘
单片机·学习·51单片机
我命由我123459 小时前
复利极简理解
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
AOwhisky10 小时前
Python 学习笔记(第三期)——流程控制核心知识点自测与详解
开发语言·笔记·python·学习·云原生·运维开发·流程控制
tyqtyq2210 小时前
药品说明书解读 —— AI 安全用药助手,鸿蒙原生应用深度解析
人工智能·学习·华为·生活·harmonyos
小心亦新11 小时前
STM32学习13 定时器1中断
stm32·嵌入式硬件·学习
我命由我1234511 小时前
72 法则极简理解
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
qq_263_tohua12 小时前
第112期 CNN学习,不错的文章
学习