【IMX6ULL驱动开发学习】04.应用程序和驱动程序数据传输和交互的4种方式:非阻塞、阻塞、POLL、异步通知
一、数据传输

1.1 APP和驱动
APP和驱动之间的数据访问是不能通过直接访问对方的内存地址来操作的,这里涉及Linux系统中的MMU(内存管理单元)。在驱动程序中通过这两个函数来获得APP和传给APP数据:
- copy_to_user
- copy_from_user
简单来讲,应用程序与内核/驱动程序在物理空间上是隔离开的,应用程序和驱动程序是不可能互相访问到的。驱动程序里的copy_from_user得到应用层传来的数据,驱动程序可以使用copy_to_user把数据发给应用程序,即应用程序和驱动程序通过这两个函数交换数据。
1.2 驱动和硬件
- 各个子系统函数
- 通过ioremap映射寄存器地址后,直接访问寄存器
驱动程序操作硬件可以通过子系统的方式(调用函数)来操作硬件;或者用最原始的办法ioremap,映射寄存器的地址(不是直接操作寄存器地址),这样在驱动程序里就可以访问寄存器了。
二、APP使用驱动的4种方式
驱动程序:提供能力,不提供策略(驱动程序提供各种作用的函数,供应用程序抉择并使用)。
2.1 非阻塞(查询)
如果在应用程序里open这个argv[1](设备节点)时,指定了非阻塞,表示读数据时,如果没有数据并且这个文件的flag是非阻塞,则立刻返回一个错误。APP指定了非阻塞方式,驱动程序是否判断它的flag完全由用户决定。
//应用程序
//O_RDWR可读可写,O_NONBLOCK非阻塞方式
fd = open(argv[1], O_RDWR | O_NONBLOCK);
//驱动程序的read
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;int key;if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))return -EAGAIN;wait_event_interruptible(gpio_wait, !is_key_buf_empty());key = get_key();err = copy_to_user(buf, &key, 4); return 4;
}
2.2 阻塞(休眠+唤醒)
如果一开始buf里没有数据,APP调用读函数,驱动程序读函数会进入wait_event_interruptible里休眠(放弃运行,不是死等),等待被唤醒。所以我们经常看到read函数很久没有返回,是因为在驱动程序里休眠了。该事件会记录在gpio_wait队列中。
//应用程序
//O_RDWR可读可写,不设置非阻塞
fd = open(argv[1], O_RDWR);
//驱动程序的read
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;int key;if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))return -EAGAIN;wait_event_interruptible(gpio_wait, !is_key_buf_empty());key = get_key();err = copy_to_user(buf, &key, 4); return 4;
}
通常配合中断+定时器的方式来唤醒该队列里面等待唤醒的进程/线程。
//中断函数
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{struct gpio_desc *gpio_desc = dev_id;printk("gpio_key_isr key %d irq happened\n", gpio_desc->gpio);//定时器 用来消除抖动//修改定时器的超时时间= jiffies(当前时间) + 赫兹/5mod_timer(&gpio_desc->key_timer, jiffies + HZ/5);return IRQ_HANDLED;//成功处理
}
//定时器超时函数
static void key_timer_expire(unsigned long data)
{struct gpio_desc *gpio_desc = (struct gpio_desc *)data;int val;int key;val = gpio_get_value(gpio_desc->gpio);key = (gpio_desc->key) | (val<<8);put_key(key);//按键值放入环形缓冲区//唤醒队列中的进程/线程wake_up_interruptible(&gpio_wait);kill_fasync(&button_fasync, SIGIO, POLL_IN);
}
2.3 POLL(休眠+唤醒+超时时间)
2.3.1 POLL机制流程
使用休眠-唤醒的方式等待某个事件发生时,有一个缺点: 等待的时间可能很久。我们可以加上一个超时时间,这时就可以使用 poll 机制。poll机制流程如下6步:
①APP不知道驱动程序中是否有数据,可以先调用poll函数查询一下,poll函数可以传入超时时间;
②APP进入内核态,调用到驱动程序的poll函数,如果有数据的话立刻返回;
③如果发现没有数据时就休眠一段时间;
④当有数据时,比如当按下按键时,驱动程序的中断服务程序和定时器超时函数被调用,它会记录数据、唤醒APP;
⑤当超时时间到了之后,内核也会唤醒APP;
⑥APP根据poll函数的返回值就可以知道是否有数据,如果有数据就调用read得到数据。
2.3.2 POLL执行流程

图1 poll机制
函数执行流程如上图①~⑧所示,重点从③开始看。假设一开始无按键数据:
③APP调用poll之后,进入内核态;
④在循环中执行程序,致驱动程序的drv_poll被调用;注意,drv_poll要把自己这个线程挂入等待队列 wq 中!,并没有休眠,且无数据返回0,有数据返回POLLIN;
⑤当前没有数据,则在内核态中休眠一会,等待超时内核唤醒或中断+定时器唤醒;
中断+定时器唤醒情况:
⑥过程中,按下了按键,发生了中断+定时器超时函数,在定时器超时函数里记录了按键值,并且从gpio_wait队列中把线程唤醒了;
⑦从休眠中被唤醒,继续执行 for 循环,再次调用 drv_poll,在drv_poll中返回数据状态(POLLIN);
⑧有数据返回到内核态,内核态返回到应用态;
⑨APP调用read函数读数据。
超时内核唤醒情况:接着上面的⑤
⑥在休眠过程中,一直没有按下了按键,超时时间到,内核把这个线程唤醒;
⑦线程从休眠中被唤醒,继续执行 for 循环,再次调用 drv_poll,drv_poll返回数据状态
⑧还是没有数据,但是超时时间到了,那从内核态返回到应用态;
⑨APP不能调用 read 函数读数据。
需要注意一下几点!!!
- drv_poll 要把线程挂入队列gpio_wait,但是并不是在 drv_poll 中进入休眠,而是在调用 drv_poll 之后休眠
- drv_poll 要返回数据状态
- APP 调用一次 poll,有可能会导致 drv_poll 被调用 2 次
- 线程被唤醒的原因有 2个:中断(+定时器)发生了去队列gpio_wait中把它唤醒,超时时间到了内核把它唤醒
- APP 要判断 poll 返回的原因:有数据,还是超时。有数据时再去调用read函数
2.3.3 POLL应用和驱动编程
驱动程序中的poll代码
static unsigned int gpio_drv_poll(struct file *fp, poll_table * wait)
{poll_wait(fp, &gpio_wait, wait);return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}
驱动程序中的中断触发函数:按键消抖+修改了定时器超时时间
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{struct gpio_desc *gpio_desc = dev_id;printk("gpio_key_isr key %d irq happened\n", gpio_desc->gpio);//定时器 用来消除抖动mod_timer(&gpio_desc->key_timer, jiffies + HZ/5);//修改定时器的超时时间= jiffies(当前时间) + 赫兹/5return IRQ_HANDLED;//成功处理
}
定时器超时函数:获取按键值+储存按键值+唤醒线程
static void key_timer_expire(unsigned long data)
{struct gpio_desc *gpio_desc = (struct gpio_desc *)data;int val;int key;val = gpio_get_value(gpio_desc->gpio);key = (gpio_desc->key) | (val<<8);put_key(key);//按键值放入环形缓冲区wake_up_interruptible(&gpio_wait);//唤醒队列里的线程kill_fasync(&button_fasync, SIGIO, POLL_IN);
}
应用程序代码:
struct pollfd fds[1]
int timeout_ms = 5000;
int ret;
int fd;
fds[0].fd = fd; //查询fd这个文件
fds[0].events = POLLIN; //POLLIN表示查询这个文件有没有数据让我读进来 fd = open(argv[1], O_RDWR);
if(fd == -1)
{printf("can not open file %s\n", argv[1]);
}while(1)
{ret = poll(fds, 1, timeout_ms);//ret为1表示fds结构体中有文件满足返回条件,且返回的事件是这个文件有数据让我读进来POLLINif((ret == 1) && (fds[0].revents & POLLIN)){read(fd, &val, 4);printf("get button : 0x%x\n", val);}else{printf("timeout\n");}}
2.4 异步通知
2.4.1 异步通知流程
使用休眠-唤醒、POLL机制时,都需要休眠等待某个事件发生时,它们的差别在于后者可以指定休眠的时长。如果APP不想休眠怎么办?也有类似的方法:驱动程序有数据时主动通知APP,APP收到信号后执行信息处理函数,这就是异步通知。

图2 异步通知的信号流程
重点从②开始:
② APP 给 SIGIO 这个信号注册信号处理函数 func,以后 APP 收到 SIGIO信号时,这个函数会被自动调用;
③ 把 APP 的 PID(进程 ID)告诉驱动程序,这个调用不涉及驱动程序,在内核的文件系统层次记录 PID;
④ 读取驱动程序文件 Flag;
⑤ 设置 Flag 里面的 FASYNC 位为 1:当 FASYNC 位发生变化时,会导致驱动程序的 fasync 被调用;
⑥⑦ 调 用 faync_helper , 它会根据FAYSNC的值决定是否设置button_async->fa_file=驱动文件 filp:驱动文件 filp 结构体里面含有之前设置的 PID。
⑧ APP 可以做其他事;
⑨⑩ 按下按键,发生中断,驱动程序的中断服务程序被调用,里面调用kill_fasync 发信号;
⑪⑫⑬ APP 收到信号后,它的信号处理函数被自动调用,可以在里面调用read 函数读取按键。
2.4.1 异步通知应用和驱动编程
应用程序:信号处理函数+注册信号处理函数+打开驱动+把进程ID告诉驱动+使能驱动的FASYNC功能
static void sig_func(int sig)
{int val;read(fd, &val, 4);printf("get button : 0x%x\n", val);
}signal(SIGIO, sig_func);fd = open(argv[1], O_RDWR);
if(fd == -1)
{printf("can not open file %s\n", argv[1]);
}fcntl(fd, F_SETOWN, getpid()); //告诉驱动程序,要给谁发信号
flags = fcntl(fd, F_GETFL); //获得之前的flags
fcntl(fd, F_SETFL, flags | FASYNC); //这是新的flags并使能驱动的FASYNC功能(使能异步通知)
驱动程序中的fasync被调用:使能异步通知后会调用这个辅助函数来构造结构体,结构体里存放进程id
//构造button_fasync结构体,结构体里存放进程id
static int gpio_drv_fasync(int fd, struct file *file, int on)
{if (fasync_helper(fd, file, on, &button_fasync) >= 0)return 0;elsereturn -EIO;
}
驱动程序中的中断触发函数:按键消抖+修改了定时器超时时间
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{struct gpio_desc *gpio_desc = dev_id;printk("gpio_key_isr key %d irq happened\n", gpio_desc->gpio);//定时器 用来消除抖动mod_timer(&gpio_desc->key_timer, jiffies + HZ/5);//修改定时器的超时时间= jiffies(当前时间) + 赫兹/5return IRQ_HANDLED;//成功处理
}
定时器超时函数:最后一行发送信号SIGIO给进程,button_fasync结构体中有进程信息,发送信号后,应用程序中收到信号会打断while循环并先执行对应的信号处理函数,再回到while循环。
static void key_timer_expire(unsigned long data)
{struct gpio_desc *gpio_desc = (struct gpio_desc *)data;int val;int key;val = gpio_get_value(gpio_desc->gpio);key = (gpio_desc->key) | (val<<8);put_key(key);//按键值放入环形缓冲区wake_up_interruptible(&gpio_wait);//唤醒队列里的线程kill_fasync(&button_fasync, SIGIO, POLL_IN);
}
相关文章:
【IMX6ULL驱动开发学习】04.应用程序和驱动程序数据传输和交互的4种方式:非阻塞、阻塞、POLL、异步通知
一、数据传输 1.1 APP和驱动 APP和驱动之间的数据访问是不能通过直接访问对方的内存地址来操作的,这里涉及Linux系统中的MMU(内存管理单元)。在驱动程序中通过这两个函数来获得APP和传给APP数据: copy_to_usercopy_from_user …...
day-21 代码随想录算法训练营(19)二叉树part07
530.二叉搜索树的最小绝对差 思路一:二叉搜索树的中序遍历必为升序数组,加入数组后计算相邻两个数差值,即可求出最小绝对差 思路二:同样的思路,中序遍历,直接使用指针记录上一个节点,同时更新…...
【Vue3】依赖注入
provide 和 inject 是 Vue.js 中用于实现依赖注入的两个关联功能。它们允许你在祖先组件中提供数据,然后在子孙组件中注入这些数据,实现组件之间的数据共享和传递。 provide:provide 是一个选项,你可以在父组件中通过它来提供数据…...
Vue 引入 Element-UI 组件库
Element-UI 官网地址:https://element.eleme.cn/#/zh-CN 完整引入:会将全部组件打包到项目中,导致项目过大,首次加载时间过长。 下载 Element-UI 一、打开项目,安装 Element-UI 组件库。 使用命令: npm …...
照耀国产的星火,再度上新!
国产之光,星火闪耀 ⭐ 新时代的星火⭐ 多模态能力⭐ 图像生成与虚拟人视频生成⭐ 音频生成与OCR笔记收藏⭐ 助手模式更新⭐ 插件能力⭐ 代码能力⭐ 写在最后 ⭐ 新时代的星火 在这个快速变革的时代,人工智能正迅猛地催生着前所未有的革命。从医疗到金融…...
大语言模型LLM的一些点
LLM发展史 GPT模型是一种自然语言处理模型,使用Transformer来预测下一个单词的概率分布,通过训练在大型文本语料库上学习到的语言模式来生成自然语言文本。 GPT-1(117亿参数),GPT-1有一定的泛化能力。能够用于和监督任务无关的任务中。GPT-2(…...
leetcode810. 黑板异或游戏(博弈论 - java)
黑板异或游戏 lc 810 - 黑板异或游戏题目描述博弈论 动态规划 lc 810 - 黑板异或游戏 难度 - 困难 原题链接 - 黑板异或游戏 题目描述 黑板上写着一个非负整数数组 nums[i] 。 Alice 和 Bob 轮流从黑板上擦掉一个数字,Alice 先手。如果擦除一个数字后,剩…...
算法练习Day48|198.打家劫舍 ● 213.打家劫舍II ● 337.打家劫舍III
LeetCode: 198. 打家劫舍 - 力扣(LeetCode) 1.思路 边界思维,只有一个元素和两个元素的初始化考虑 当元素数大于3个时, 逆向思维,是否偷最后一个元素,倒序得出递推公式dp[i] Math.max(dp[i - 1], dp[i …...
什么是设计模式?常用的设计有哪些?
单例模式工厂模式代理模式(proxy) 一、设计模式 设计模式是前辈们经过无数次实践所总结的一些方法(针对特定问题的特定方法) 这些设计模式中的方法都是经过反复使用过的。 二、常用的设计模式有哪些? 1、单例模式&…...
clickHouse部署
docker仓库地址 https://hub.docker.com/ 1、docker环境搭建 # 1.先安装yml yum install -y yum-utils device-mapper-persistent-data lvm2 # 2.设置阿里云镜像 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo# 3.查…...
Flutter实现倒计时功能,秒数转时分秒,然后倒计时
Flutter实现倒计时功能 发布时间:2023/05/12 本文实例为大家分享了Flutter实现倒计时功能的具体代码,供大家参考,具体内容如下 有一个需求,需要在页面进行显示倒计时,倒计时结束后,做相应的逻辑处理。 实…...
【hadoop】windows上hadoop环境的搭建步骤
文章目录 前言基础环境下载hadoop安装包下载hadoop在windows中的依赖配置环境变量 Hadoop hdfs搭建创建hadfs数据目录修改JAVA依赖修改配置文件初始化hdfs namenode启动hdfs 前言 在大数据开发领域中,不得不说说传统经典的hadoop基础计算框架。一般我们都会将hadoo…...
一周在榜9本计算机专业新书
本周在榜计算机专业新书9本。 1、扩散模型从原理到实战 开启AI绘画新时代!AIGC大模型来临,配套赠送Diffusion视频课程! HuggingFace平台学习实战,常春藤盟校数据科学硕士与算法工程师带你从理论到实战,了解、掌握扩散…...
CSS变形与动画(二):perspctive透视效果 与 preserve-3d 3d效果(奥运五环例子)
文章目录 perspective 3d透视效果preserve-3d 3d嵌套效果例子 奥运五环 backface-visibility 背面效果 perspective 3d透视效果 perspective 指定了观察者与 z0 平面的距离,使具有三维位置变换的元素产生透视效果。z>0 的三维元素比正常大,而 z<0 …...
[论文笔记]Glancing Transformer for Non-Autoregressive Neural Machine Translation
引言 这是论文Glancing Transformer for Non-Autoregressive Neural Machine Translation的笔记。 传统的非自回归文本生成速度较慢,因为需要给定之前的token来预测下一个token。但自回归模型虽然效率高,但性能没那么好。 这篇论文提出了Glancing Transformer,可以只需要一…...
视觉学习(七)---Flask 框架下接口调用及python requests 实现json字符串传输
在项目实施过程中需要与其他系统进行接口联调,将图像检测的结果传递给其他系统接口,进行逻辑调用。这中间的过程可以通过requests库进行实现。 1.安装requests库 pip install requests2.postman 接口测试 我们先通过postman 了解下接口调用࿰…...
unity编写树形结构的文件管理页面
项目中需要实现点击“”按钮展开对应分类下的所有训练科目,再次点击“–”按钮将对应分类下的训练科目隐藏并收起整个面板。对此,编写一个类,将其挂载到树形结构的父类上,代码如下: using UnityEngine; using UnityEn…...
基于单片机的家用智能浇灌系统
1、开发环境 keil5,STM32CubeMX、Altium Designer 2、硬件清单 单片机:STM32F051K8Ux 土壤湿度传感器:TL - 69 温度传感器:DS18B20(数字传感器直接输出数字信号) OLED屏幕:OLED12864、 水…...
Solr的入门使用
Solr是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文搜索服务器。Solr提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展,并对索引、搜索性能进行了优化,被很多需要搜索的网站中广泛使用。…...
css鼠标样式 cursor: pointer
cursor: none; cursor:not-allowed; 禁止选择 user-select: none; pointer-events:none;禁止触发事件, 该样式会阻止默认事件的发生,但鼠标样式会变成箭头...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...
9-Oracle 23 ai Vector Search 特性 知识准备
很多小伙伴是不是参加了 免费认证课程(限时至2025/5/15) Oracle AI Vector Search 1Z0-184-25考试,都顺利拿到certified了没。 各行各业的AI 大模型的到来,传统的数据库中的SQL还能不能打,结构化和非结构的话数据如何和…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...
