【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(当前时间) + 赫兹/5
	mod_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(当前时间) + 赫兹/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);
}

应用程序代码:

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结构体中有文件满足返回条件,且返回的事件是这个文件有数据让我读进来POLLIN
    if((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;
	else
		return -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(当前时间) + 赫兹/5
	return 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);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/66329.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电脑自动关机是什么原因?1分钟弄懂!

“好奇怪啊&#xff0c;我在使用电脑时&#xff0c;电脑总是莫名其妙就会自动关机&#xff0c;有时候我文件都来不及保存。这是为什么呢&#xff1f;有什么解决方法吗&#xff1f;” 电脑自动关机是一个令人头疼的问题&#xff0c;可能会对我们的工作和生活带来影响。电脑自动关…

数组相关练习

数组练习 将数组转化成字符串数组拷贝求数组元素的平均值查找数组中指定元素(顺序查找)二分查找冒泡排序数组逆序 将数组转化成字符串 import java.util.Arrays;public class Text1 {public static void main(String[] args) {int[] arr {5, 6, 4, 2};System.out.println(Arr…

电商数据获取:网络爬虫还是付费数据接口?

随着电商行业的迅速发展&#xff0c;对电商数据的需求也越来越大。在获取电商数据时&#xff0c;常常面临一个选择&#xff1a;是自己编写网络爬虫进行数据爬取&#xff0c;还是使用现有的付费数据接口呢&#xff1f;本文将从成本、可靠性、数据质量等多个角度进行分析&#xf…

【果树农药喷洒机器人】Part1:研究现状分析以及技术路线介绍

本专栏介绍&#xff1a;付费专栏&#xff0c;持续更新机器人实战项目&#xff0c;欢迎各位订阅关注。 关注我&#xff0c;带你了解更多关于机器人、嵌入式、人工智能等方面的优质文章&#xff01; 文章目录 一、项目背景二、国内外研究现状2.1 国内研究现状2.2 国外研究现状 三…

C语言内嵌汇编

反编译&#xff08;二进制文件或者so库&#xff09; objdump --help objdump -M intel -j .text -ld -C -S out > out.txt #显示源代码同时显示行号, 代码段反汇编-M intel 英特尔语法-M x86-64-C:将C符号名逆向解析-S 反汇编的同时&#xff0c;将反汇编代码和源代码交替显…

Python自动化测试用例:如何优雅的完成Json格式数据断言

目录 前言 直接使用 优化 封装 小结 进阶 总结 资料获取方法 前言 记录Json断言在工作中的应用进阶。 直接使用 很早以前写过一篇博客&#xff0c;记录当时获取一个多级json中指定key的数据&#xff1a; #! /usr/bin/python # coding:utf-8 """ aut…

3分钟创建新生分班查询二维码,无需技术、0成本

作为教师&#xff0c;我们深知分班是一项极其重要的任务&#xff0c;需要综合考虑学生的性格、能力和兴趣等多个方面&#xff0c;以确保每个学生都能够获得最佳的学习环境和成绩。在本文中&#xff0c;我将分享一种便捷的方式来告知家长有关分班录取情况的方法。 通常&#xf…

2. 软件需求 面向对象分析

目录 1. 软件需求 1.1 需求分类 1.2 需求获取 1.3 需求分析 2. 面向对象分析&#xff08;OOA&#xff09; 2.1 统一建模语言 UML 2.2 用例模型 2.2.1 用例图的元素 2.2.2 识别参与者 2.2.3 合并需求获得用例 2.2.4 细化用例描述 2.3 分析模型 2.3.1 定义概念类 …

深入JVM - JIT分层编译技术与日志详解

深入JVM - JIT分层编译技术与日志详解 文章目录 深入JVM - JIT分层编译技术与日志详解1. 背景简介2. JIT 编译器2.1. 客户端版本的编译器: C12.2. 服务端版本的编译器: C22.3. Graal JIT 编译器 3. 分层编译技术(Tiered Compilation)3.1. 汇聚两种编译器的优点3.2. 精准优化(Ac…

Redis内网主从节点搭建

Redis内网主从节点搭建 1、文件上传2、服务安装3、服务启动4、配置主从复制 1、文件上传 内网环境手动上传gcc-c、redis.tar文件 2、服务安装 # 解压 unzip gcc-c.zip unzip gcc_rpm.zip tar -zxvf redis-6.2.13.tar.gz# 安装 cd gcc_rpm/ rpm -ivh *.rpm --nodeps --force…

gitlab 503 错误的解决方案

首先使用 sudo gitlab-ctl status 命令查看哪些服务没用启动 sudo gitlab-ctl status 再用 gitlab-rake gitlab:check 命令检查 gitlab。根据发生的错误一步一步纠正。 gitlab-rake gitlab:check 查看日志 tail /var/log/gitlab/gitaly/current删除gitaly.pid rm /var/opt…

Webpack怎么使用?

Webpack 使用 前几篇文章中已经介绍了如何初始化包管理器 package.json 这里不再重复介绍&#xff0c;如有需要请查看 搭建工程化项目。 安装 :::warning 注意 请确保你已经安装了 yarn&#xff0c;如有需要请查看 搭建工程化开发环境。 ::: 通过命令 yarn add webpack web…

三天吃透Java面试八股文(2023最新整理),面试通过率高达90%

什么样的求职者能够获得面试官的青睐&#xff1f;求职者需要准备哪些内容来面对形形色色的面试官&#xff1f;这两份资料是我在几十场面试中被面试官问到的问题&#xff0c;比其他复制粘贴的面试题强一百倍&#xff0c;堪称全网最强&#xff08;我不太喜欢“全网最强”这样的字…

mybatis打印sql语句出现多余的limit关键字

1、事情起因 在项目中使用了PageHelper分页插件&#xff0c;由于需求特殊&#xff0c;需要自定义分页&#xff0c;代码编写完成后&#xff0c;事故出现了。 前端传参: {pageNum: 1,pageSize: 10, }已知表中数据10条&#xff0c;但是每次分页查询只有10条数据&#xff0c;排查…

【C语言】初识C语言+进阶篇导读

✨个人主页&#xff1a; Anmia.&#x1f389;所属专栏&#xff1a; C Language &#x1f383;操作环境&#xff1a; Visual Studio 2019 版本 本篇目的是面向编程新手&#xff0c;没接触过编程的人。以及C进阶的导读。 内容是C语言重要知识点的简单解释&#xff0c;不做详解。给…

uniapp echarts 点击失效

这个问题网上搜了一堆&#xff0c;有的让你降版本&#xff0c;有的让你改源码。。。都不太符合预期&#xff0c;目前我的方法可以用最新的echarts。 这个方法就是由npm安装转为CDN&#xff0c;当然你可能会质疑用CDN这样会不稳定&#xff0c;那如果CDN的地址是本地呢&#xff1…

【MySQL系列】表约束的学习

「前言」文章内容大致是MySQL的表的约束。 「归属专栏」MySQL 「主页链接」个人主页 「笔者」枫叶先生(fy) 目录 一、MySQL表的约束1.1 空属性1.2 默认值&#xff08;default&#xff09;1.3 列描述&#xff08;comment&#xff09;1.4 zerofill1.5 主键&#xff08;primary ke…

简化AD管理减少IT工作负担

管理和保护混合 AD 环境 IT 管理员几乎每天都要创建和管理多个 AD 对象&#xff0c;利用本机AD工具&#xff08;如Active Directory用户和计算机控制台以及PowerShell脚本&#xff09;来执行这些任务并不理想&#xff0c;因为它们必须在多个控制台之间切换才能执行这些任务&am…

CTF PWN之精确覆盖变量数据

刚开始接触pwn的朋友在做pwn练习时可能会有这样的疑问&#xff0c;怎么做到精确覆盖变量数据呢&#xff1f; 我们做pwn练习之前需要先知道&#xff1a;命令行参数C语言的main函数拥有两个参数&#xff0c;为int类型的argc参数&#xff0c;以及char**类型argv参数。其中argc参数…

npm install时出现的问题Failed at the node-sass@4.14.1 postinstall script

从阿里云上拉取下来项目后&#xff0c;首先使用npm install 命令进行安装所需依赖&#xff0c;意想不到的事情发生了&#xff0c;报出了Failed at the node-sass4.14.1 postinstall script&#xff0c;这个问题&#xff0c;顿时一脸懵逼&#xff1b;询问前端大佬&#xff0c;给…