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");
相关推荐
西岸行者3 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意3 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码3 天前
嵌入式学习路线
学习
毛小茛3 天前
计算机系统概论——校验码
学习
babe小鑫3 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms3 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下3 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。3 天前
2026.2.25监控学习
学习
im_AMBER3 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J3 天前
从“Hello World“ 开始 C++
c语言·c++·学习