当前位置: 首页 > news >正文

Linux驱动异步通知机制详解

Linux驱动异步通知机制详解

1. 异步通知基本概念

1.1 什么是异步通知

异步通知(Asynchronous Notification)是Linux系统中一种重要的进程间通信机制,它允许驱动程序在特定事件发生时主动通知应用程序,而不是让应用程序通过轮询的方式不断查询设备状态。这种机制可以显著提高系统效率,减少CPU资源的浪费。

在传统的轮询模式中,应用程序需要不断地调用read()等系统调用来检查设备是否有数据可读,这种方式不仅消耗大量的CPU资源,而且响应速度也不够及时。而异步通知机制通过信号(Signal)的方式,在设备状态发生变化时立即通知应用程序,实现了事件驱动的编程模型。

1.2 SIGIO信号

异步通知的核心是SIGIO信号。当一个文件描述符被设置为支持异步通知后,每当该文件描述符对应的设备有数据可读或可写时,内核就会向拥有该文件描述符的进程发送SIGIO信号。应用程序可以通过signal()或sigaction()系统调用来注册SIGIO信号的处理函数,从而在信号到达时执行相应的处理逻辑。

1.3 FASYNC标志

要启用异步通知功能,需要在文件操作结构体中实现fasync()方法,并在应用程序中通过fcntl()系统调用设置FASYNC标志。FASYNC标志告诉内核,当文件描述符对应的设备状态发生变化时,需要发送SIGIO信号。

2. 驱动程序实现

2.1 数据结构定义

struct key_desc {char name[10];int gpio;int irqnum;unsigned char value;irqreturn_t (*handler)(int, void *);
};struct imx6uirq_dev {dev_t devid;int major;int minor;struct cdev cdev;struct class *class;struct device *device;struct device_node *key_nd;struct key_desc key[KEY_NUM];struct timer_list timer;atomic_t keyvalue;atomic_t release;struct fasync_struct *fasync_queue;
};

驱动程序定义了两个主要的数据结构:key_desc用于描述按键的相关信息,包括名称、GPIO引脚、中断号、键值和中断处理函数;imx6uirq_dev是设备的主结构体,包含了字符设备所需的各种成员,以及用于异步通知的fasync_queue指针。

2.2 文件操作结构体

static const struct file_operations imx6uirq_fops = {.owner = THIS_MODULE,.open = imx6uirq_open,.read = imx6uirq_read,.fasync = imx6uirq_fasync,.release = imx6uirq_release,
};

文件操作结构体中实现了四个关键方法:

  • imx6uirq_open:打开设备文件时调用
  • imx6uirq_read:读取设备数据时调用
  • imx6uirq_fasync:管理异步通知队列
  • imx6uirq_release:关闭设备文件时调用

其中fasync方法是实现异步通知的关键。

2.3 fasync方法实现

static int imx6uirq_fasync(int fd, struct file *filp, int on)
{struct imx6uirq_dev *dev = filp->private_data;return fasync_helper(fd, filp, on, &dev->fasync_queue);
}

imx6uirq_fasync方法调用了内核提供的fasync_helper函数来管理异步通知队列。这个函数会根据on参数的值来添加或删除文件描述符到异步通知队列中。当应用程序通过fcntl()设置FASYNC标志时,on为1,文件描述符会被添加到队列;当清除FASYNC标志时,on为0,文件描述符会被从队列中删除。

2.4 release方法实现

static int imx6uirq_release(struct inode *inode, struct file *filp)
{imx6uirq_fasync(-1, filp, 1);return 0;
}

在设备文件关闭时,需要调用imx6uirq_fasync方法将文件描述符从异步通知队列中移除,以防止后续的信号发送导致错误。

2.5 中断处理

static irqreturn_t key0_handler(int irq, void *filp)
{struct imx6uirq_dev *dev = filp;dev->timer.data = (volatile long)filp;mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));return IRQ_HANDLED;
}

中断处理函数中使用了定时器来实现按键消抖。当按键中断发生时,不是立即处理按键事件,而是启动一个20ms的定时器。这样可以避免由于机械按键的弹跳效应导致的多次中断。

2.6 定时器处理函数

static void timer_func(unsigned long arg)
{struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;int value = 0;value = gpio_get_value(dev->key[0].gpio);if (value == 0){atomic_set(&dev->keyvalue, dev->key[0].value);}else{atomic_set(&dev->release, 1);}if (atomic_read(&dev->release)){kill_fasync(&dev->fasync_queue, SIGIO, POLL_IN);}
}

定时器处理函数首先读取按键的当前状态,如果按键被按下,则设置键值;如果按键被释放,则设置释放标志。关键的是最后的kill_fasync调用,它会向所有注册了异步通知的进程发送SIGIO信号,通知它们设备状态已经改变。

3. 应用程序实现

3.1 信号处理函数

static void sigio_signal_func(int num)
{int ret = 0;int keyvalue;ret = read(fd, &keyvalue, sizeof(keyvalue));if (ret < 0){printf("User: fail error\r\n");}else{printf("User: sigio signal, keyvalue is: %d\r\n", keyvalue);}
}

应用程序首先定义了一个SIGIO信号的处理函数sigio_signal_func。当收到SIGIO信号时,这个函数会被调用。在信号处理函数中,应用程序调用read()系统调用来读取按键值,并将其打印出来。

3.2 启用异步通知

signal(SIGIO, sigio_signal_func);int ret = fcntl(fd, F_SETOWN, getpid());
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);

应用程序通过以下三个步骤来启用异步通知:

  1. 使用signal()系统调用注册SIGIO信号的处理函数
  2. 使用fcntl(fd, F_SETOWN, getpid())告诉内核哪个进程应该接收SIGIO信号
  3. 使用fcntl(fd, F_SETFL, flags | FASYNC)设置FASYNC标志,启用异步通知功能

4. 工作流程分析

4.1 初始化阶段

  1. 驱动程序加载时,imx6uirq_init函数被调用
  2. 分配设备号,注册字符设备
  3. 创建设备节点
  4. 初始化按键和定时器

4.2 应用程序打开设备

  1. 应用程序调用open()打开设备文件
  2. 驱动程序的imx6uirq_open函数被调用
  3. 将设备结构体指针保存到filp->private_data中

4.3 启用异步通知

  1. 应用程序调用signal()注册SIGIO信号处理函数
  2. 调用fcntl()设置F_SETOWN,指定信号接收进程
  3. 调用fcntl()设置FASYNC标志
  4. 驱动程序的imx6uirq_fasync函数被调用,将文件描述符添加到异步通知队列

4.4 按键事件处理

  1. 用户按下按键,触发外部中断
  2. 中断处理函数key0_handler被调用
  3. 启动20ms定时器进行按键消抖
  4. 定时器超时后,timer_func函数被调用
  5. 读取按键状态,如果是按键释放,则调用kill_fasync
  6. kill_fasync向应用程序发送SIGIO信号
  7. 应用程序的SIGIO信号处理函数被调用
  8. 在信号处理函数中调用read()读取按键值

5. 设备树配置

5.1 设备节点定义

key{compatible = "alientek,key";pinctrl-names = "default";pinctrl-0 = <&pinctrl_key>;states = "okay";key-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;interrupt-parent = <&gpio1>;interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};

在设备树中定义了按键设备节点,指定了以下信息:

  • compatible:兼容性字符串,用于匹配驱动程序
  • pinctrl-0:引脚控制配置
  • key-gpios:按键连接的GPIO引脚
  • interrupt-parent:中断父节点
  • interrupts:中断号和触发类型

5.2 引脚控制配置

pinctrl_key: keygrp {fsl,pins = <MX6UL_PAD_UART1_CTS_B__GPIO1_IO18    0xF080>;
};

引脚控制配置指定了GPIO1_18引脚的工作模式,将其配置为GPIO输入模式,并设置了适当的电气特性参数。

6. Makefile分析

KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)obj-m := imx6uirq.o
build : kernel_moduleskernel_modules:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modulesclean:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean

Makefile中定义了以下几个关键变量:

  • KERNERDIR:内核源码目录路径
  • CURRENTDIR:当前目录路径
  • obj-m:指定要编译的模块对象

编译时,会进入内核源码目录,通过M参数指定模块源码位置,让内核构建系统来编译模块。

https://gitee.com/dream-cometrue/linux_driver_imx6ull

http://www.dtcms.com/a/357751.html

相关文章:

  • Labview邪修01:贪吃蛇
  • 【完整源码+数据集+部署教程】控制台缺陷检测系统源码和数据集:改进yolo11-repvit
  • IDEA编译报错:Error:(3, 28) java: 程序包com.alibaba.fastjson不存在
  • GPFS性能优化
  • zyplayer-doc:AI 驱动的智能知识库
  • LeetCode力扣-hot100系列(2)
  • MQTT高延迟通信优化指南
  • 解密企业数据安全:服务业加密软件的核心价值
  • POE供电是什么?
  • RAG教程5:多表示索引和ColBERT
  • 不一样的发票管理模式-发票识别+发票查验接口
  • 篮球API接口:技术如何革新体育数据体验
  • FunctionAI 图像生成:简化从灵感到 API 调用的每一步
  • Spring Boot自动装配机制的原理
  • Kafka入门指南:从安装到集群部署
  • 【数据结构与算法-Day 20】从零到一掌握二叉树:定义、性质、特殊形态与存储结构全解析
  • 最新SF授权系统源码全开源无加密v5.2版本
  • 什么是Jmeter? Jmeter工作原理是什么?
  • 平安健康平安芯医AI解析:7×24小时问诊+95%诊断准确率,人文温度短板与医生效能提升引热议
  • 【完整源码+数据集+部署教程】高速公路施工区域物体检测系统源码和数据集:改进yolo11-RepNCSPELAN
  • 手写链路追踪
  • 基于Net海洋生态环境保护系统的设计与实现(代码+数据库+LW)
  • 【面试场景题】怎么做业务领域划分
  • 互联网大厂AI大模型面试解析:从基础技术到场景应用
  • Jetson进行旋转目标检测推理实现大疆无人机飞行控制
  • Python-GEE遥感云大数据分析、可视化与Satellite Embedding应用
  • leetcode算法刷题的第二十一天
  • 阿里云服务器购买流程:四种主要购买方式图文教程详解与选择参考
  • Cherrystudio的搭建和使用
  • Silvaco TCAD | Victory DoE的基本使用方法(三)