itop-3568开发板驱动学习笔记(9)高级字符设备(三)信号驱动 IO

news/2024/5/21 1:16:01/文章来源:https://blog.csdn.net/weixin_43772810/article/details/130004652

《【北京迅为】itop-3568开发板驱动开发指南.pdf》 学习笔记

文章目录

  • 应用层信号机制
  • 应用层开启异步通知
  • 驱动层异步通知接口
  • 实验代码

信号驱动 IO 不需要像 poll 一样查询设备的状态,一旦设备有目标事件发生,就会触发 SIGIO 信号,然后处理信号函数,完成相应数据处理。

上面这一操作又叫异步通知,该机制与中断相似,事件发生时不立刻处理,而是发送一个信号,然后系统自动运行信号处理函数。

应用层信号机制

在计算机科学中,信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。

——百度百科

信号的相关系统调用包括 signal()、kill()、pause()、alarm() 和 setitimer(),这里只讲 signal() 函数。

signal() 函数原型为:

#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);

signum 为需要处理的信号(Linux 的所有信号可以通过 kill -l 查看)
handler 处理信号的回调函数

信号处理函数原型为:

typedef void (*sighandler_t)(int);

应用层开启异步通知

设备必须被开启异步通知后,才可以使用信号驱动 IO,具体操作为:

 fcntl(fd, SETOWN, getpid());   // 设置接收 SIGIO 和 SIGURG 信号的进程 IDint tmp = fcntl(fd, F_GETFD);  // 获取文件描述符标志fcntl(fd, F_SETFL, tmp | FASYNC); // 设置文件描述符标志

fcntl 函数可以改变已经打开的文件描述符的属性,其原型如下:

#include 
int fcntl(int fd,int cmd, ...)

fd 为要操作的文件描述符,cmd 为操作文件描述符的命令,… 表示该函数的参数是可变长参数。
成功返回的值会根据 cmd 不同而不同,错误时返回 -1,同时设置 errno。

cmd 可取值包括:

命令描述
F_DUPFD复制文件描述符
F_GETFD获取文件描述符标志
F_SETFD设置文件描述符标志
F_GETFL获取文件状态标志
F_SETFL设置文件状态标志
F_GETLK获取文件锁
F_SETLK设置文件锁
F_SETLKW与 F_SETLK 类似,但会等待返回
F_GETOWN获取当前接收 SIGIO 和 SIGURG 信号的进程 ID 和 进程组 ID
F_SETOWN设置当前接收 SIGIO 和 SIGURG 信号的进程 ID 和进程组 ID

驱动层异步通知接口

内核中,异步通知需要用到 fasync 方法,fasync 和 poll、read 等函数类似,都是文件操作集合(file_operations 结构体)的成员函数,fasync 原型如下:

int (*fasync) (int fd,struct file *filp,int on);

在驱动中,fasync 函数调用 fasync_helper() 来操作 fasync_struct 结构体,fasync_helper() 函数原型如下:

int fasync_helper(int fd,struct file *filp,int on,struct fasync_struct **fapp)

该函数的前三个参数都使用 fasnc() 的形参,只有 struct fasync_struct 类型变量是新的参数,该结构体定义如下:

struct fasync_struct {int magic;int fa_fd;struct fasync_struct *fa_next; /* singly linked list */struct file *fa_file;
};

该结构体就是异步队列的基本元素,当收到信号,内核就会在这个异步队列里寻找相应文件描述符(fa_fd),然后根据 fa_file->owner 找到对应线程 PID,最后调用 sig_handler 完成异步 IO 操作。

上面提到了信号的接收,但是这个信号从哪里发出呢?这时就要用到驱动层的 kill_fasync() 函数了,该函数发送信号,通知应用程序,原型如下:

void kill_fasync(struct fasync_struct **fp,int sig,int band);

第一个参数是上面提到的 fasync_struct 结构体变量,sig 代表要发送的信号,band 一般填 POLL_IN 或 POLL_OUT,表示驱动端有数据读或写。

实验代码

驱动核心代码

在上一份驱动代码的基础上添加 chrdev_fasync() 函数,当驱动运行该函数时,调用 fasync_helper() 函数,将异步通知的信号添加到异步处理队列中。

// fasync
static int chrdev_fasync(int fd, struct file *file, int on)
{struct my_device *tmp_dev = (struct my_device*)file->private_data;// 将异步通知的信号添加到异步处理列表return fasync_helper(fd, file, on, &tmp_dev->fasync);
}static struct file_operations chrdev_fops = {.owner = THIS_MODULE, //将 owner 成员指向本模块,可以避免在模块的操作正在被使用时卸载该模块.open = chrdev_open,  //将 open 成员指向 chrdev_open()函数.read = chrdev_read,  //将 read 成员指向 chrdev_read()函数.write = chrdev_write,//将 write 字段指向 chrdev_write()函数.release = chrdev_release,//将 release 字段指向 chrdev_release()函数.poll = chrdev_poll,  //将 poll 字段指向 chrdev_poll()函数.fasync = chrdev_fasync, //将 fasync 字段指向 chrdev_fasync()函数
};

完整驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/signal.h>
#include <linux/fcntl.h>// 定义并初始化等待队列头
DECLARE_WAIT_QUEUE_HEAD(my_wait_queue); // 定义一个私有数据结构体
struct my_device
{dev_t dev_num; // 设备号int major;     // 主设备号int minor;     // 次设备号struct cdev st_cdev;struct class *st_class;struct device *st_device;char kbuf[32];int condition; // 条件标志struct fasync_struct *fasync;
};// 定义一个全局私有数据结构体
struct my_device dev1;// open()
static int chrdev_open(struct inode *inode , struct file *file )
{file->private_data = &dev1; // 设置私有数据printk("chrdev_open.\n");return 0;
}// close()
static int chrdev_release(struct inode *inode, struct file *file)
{struct my_device *tmp_dev = (struct my_device*)file->private_data;// 卸载异步通知fasync_helper(0, file, 0, &tmp_dev->fasync);printk("chrdev_release.\n");return 0;
}// read()
static  ssize_t chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{struct my_device *tmp_dev = (struct my_device*)file->private_data;int ret = 0;// 如果 open() 的 flags 参数带 O_NONBLOCKif(file->f_flags& O_NONBLOCK){// 如果数据没就绪,直接退出 read()if(tmp_dev->condition == 0)return -EAGAIN;}// 可中断的阻塞等待,进程进入休眠状态wait_event_interruptible(my_wait_queue, tmp_dev->condition); tmp_dev->condition = 0; // 条件标志复位// 向应用空间拷贝数据ret = copy_to_user(buf, tmp_dev->kbuf, strlen(tmp_dev->kbuf)); if(ret != 0){printk("copy_to_user error.\r\n");return -1;}printk("chrdev_read.\n");return 0;
}// write()
static ssize_t chrdev_write(struct file *file , const char __user *buf, size_t size, loff_t *off)
{struct my_device *tmp_dev = (struct my_device*)file->private_data;int ret = copy_from_user(tmp_dev->kbuf, buf, size); // 从应用空间读取数据if(ret != 0){printk("copy_from_user error.\r\n");return -1;}tmp_dev->condition = 1; // 将条件置 1wake_up_interruptible(&my_wait_queue); // 唤醒等待队列中的休眠进程kill_fasync(&tmp_dev->fasync, SIGIO, POLL_IN); // 发送 SIGIO 信号return 0;
}// poll
static unsigned int chrdev_poll(struct file *file, struct poll_table_struct *wait)
{struct my_device *tmp_dev = (struct my_device*)file->private_data;poll_wait(file, &my_wait_queue, wait); // 阻塞if(tmp_dev->condition == 1){return POLLIN; // 返回事件类型}return 0;
}// fasync
static int chrdev_fasync(int fd, struct file *file, int on)
{struct my_device *tmp_dev = (struct my_device*)file->private_data;// 将异步通知的信号添加到异步处理列表return fasync_helper(fd, file, on, &tmp_dev->fasync);
}static struct file_operations chrdev_fops = {.owner = THIS_MODULE, //将 owner 成员指向本模块,可以避免在模块的操作正在被使用时卸载该模块.open = chrdev_open,  //将 open 成员指向 chrdev_open()函数.read = chrdev_read,  //将 read 成员指向 chrdev_read()函数.write = chrdev_write,//将 write 字段指向 chrdev_write()函数.release = chrdev_release,//将 release 字段指向 chrdev_release()函数.poll = chrdev_poll,  //将 poll 字段指向 chrdev_poll()函数.fasync = chrdev_fasync, //将 fasync 字段指向 chrdev_fasync()函数
};
// 驱动入口函数
static int __init chrdev_init(void)
{int ret;// 自动获取设备号(只申请一个,次设备号从 0 开始)ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "chrdev_test");if(ret < 0){goto err_alloc;}printk("alloc chrdev region successfully.\n");dev1.major = MAJOR(dev1.dev_num); // 获取主设备号dev1.minor = MINOR(dev1.dev_num); // 获取次设备号printk("major is %d.\nminor is %d\n", dev1.major, dev1.minor);dev1.st_cdev.owner = THIS_MODULE; // 将 owner 成员指向本模块,可以避免模块 st_cdev 被使用时卸载模块cdev_init(&dev1.st_cdev, &chrdev_fops); // 初始化字符设备	ret = cdev_add(&dev1.st_cdev, dev1.dev_num, 1); // 将字符设备添加到系统if(ret < 0){goto err_cdev_add;}printk("cdev add successfully.\n");dev1.st_class = class_create(THIS_MODULE, "chrdev_class"); // 创建设备类if(IS_ERR(dev1.st_class)){ret = PTR_ERR(dev1.st_class); // 返回错误码goto err_class_create;}dev1.st_device = device_create(dev1.st_class, NULL, dev1.dev_num, NULL, "chrdev_device"); // 创建设备if(IS_ERR(dev1.st_device)){ret = PTR_ERR(dev1.st_device); // 返回错误码goto err_device_create;}return 0;err_device_create:class_destroy(dev1.st_class); // 删除类err_class_create:cdev_del(&dev1.st_cdev); // 删除 cdeverr_cdev_add:unregister_chrdev_region(dev1.dev_num, 1); // 注销设备号err_alloc:return ret; // 返回错误号}// 驱动出口函数
static void __exit chrdev_exit(void)
{device_destroy(dev1.st_class, dev1.dev_num); // 删除设备class_destroy(dev1.st_class); //删除设备类cdev_del(&dev1.st_cdev); // 删除字符设备unregister_chrdev_region(dev1.dev_num, 1); // 注销设备号printk("chrdev_exit.\n");
}module_init(chrdev_init);  //注册入口函数
module_exit(chrdev_exit);  //注册出口函数
MODULE_LICENSE("GPL v2");  //同意GPL协议
MODULE_AUTHOR("xiaohui");  //作者信息

“读”程序代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>#define DEV_FILE "/dev/chrdev_device"int fd;
char buf[32] = {0};// SIGIO 信号处理函数
static void sig_handler(int signum)
{int ret = 0;ret = read(fd, buf, sizeof(buf)); // 从设备文件读数据if(ret == 0)printf("app read data successfully\ndata: %s\n", buf);elseprintf("app read data failed.\n");}int main(int argc, char** argv)
{int tmp;int ret = 0;// 打开设备文件fd = open(DEV_FILE, O_RDWR);if(fd < 0){printf("%s open failed.\n", DEV_FILE);return 0;}printf("%s open successfully.\n", DEV_FILE);// 注册 SIGIO 信号的信号处理函数signal(SIGIO, sig_handler);// 操作文件描述符,设置接收 SIGIO 的进程 IDfcntl(fd, F_SETOWN, getpid());// 获取文件描述符标志tmp = fcntl(fd, F_GETFD);// 设置文件描述符状态标志,增加 FASYNC 标志fcntl(fd, F_SETFL, tmp | FASYNC);// 死循环while(1);// 关闭设备文件close(fd);return 0;
}

“写”程序代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define DEV_FILE "/dev/chrdev_device"int main(int argc, char** argv)
{int fd, tmp;int ret = 0;char buf[32] = "fasync test";// 打开设备文件fd = open(DEV_FILE, O_RDWR);if(fd < 0){printf("%s open failed.\n", DEV_FILE);return 0;}printf("%s open successfully.\n", DEV_FILE);// 读数据printf("app will write data.\n");ret = write(fd, buf, sizeof(buf)); // 向设备文件写数据if(ret == 0)printf("app write data successfully\n");elseprintf("app write data failed.\n");// 关闭设备文件close(fd);return 0;
}

Makefile 文件

由于我打算在 X86 平台测试,所我屏蔽了交叉编译和平台的平台的环境变量,内核目录改为本机内核地址,

#目标文件,与驱动源文件同名,编译成模块
obj-m := chrdev_test.o#架构平台选择
#export ARCH=arm64#编译器选择
#export CROSS_COMPILE=aarch64-linux-gnu-#内核目录
#KDIR := /home/topeet/Linux/rk356x_linux/kernel/
KDIR := /lib/modules/$(shell uname -r)/build#编译模块
all:make -C $(KDIR) M=$(shell pwd) modules$(CROSS_COMPILE)gcc read.c -o read$(CROSS_COMPILE)gcc write.c -o write#清除编译文件
clean:make -C $(KDIR) M=$(shell pwd) cleanrm read write

实验结果

先运行“读”程序,read 运行后开启信号驱动 IO,当运行 write “写”程序时,驱动中数据准备就绪,驱动向应用层发送 SIGIO 信号,读程序中信号处理函数被执行,将读到的数据打印到终端。

在这里插入图片描述

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

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

相关文章

Eyeshot .NET 2023.1 Crack

概述 Eyeshot 是.NET 的 CAD 控件。它本身支持Windows Forms和Windows Presentation Foundation。它随附四个不同的Visual Studio工具箱项目&#xff1a;用于 2D 和 3D 几何创建或编辑的设计、用于自动生成 2D 视图的 绘图、使用线性静态分析进行几何验证的模拟以及用于CNC刀具…

MySQL学习笔记(十八)—— 事务基本知识

1. 数据库事务概述 存储引擎支持请况 SHOW ENGINES; # 命令来查看当前 MySQL 支持的存储引擎都有哪些&#xff0c;以及这些存储引擎是否支持事务。能看出在 MySQL 中&#xff0c;只有InnoDB 是支持事务的。 基本概念 事务&#xff1a;一组逻辑操作单元&#xff0c;使数据从一…

4.8 Beijing Rust Meetup | Call For Presenters

如果你有兴趣参与探讨Rust作为一种强调性能、安全和并发性的编程语言的各种应用、实践和无限可能性的头脑风暴&#xff0c;就一定不能错过这场来自达坦科技、南京大学、CloudWeGo、华为等技术专家带来的关于Rust编程语言相关应用的线下Meetup。我们的主题是Rust &#x1f4a1;X…

stable diffusion成为生产力工具(一):制作购物车图标icon

S&#xff1a;你安装stable diffusion就是为了看小姐姐么&#xff1f; I &#xff1a;当然不是&#xff0c;当然是为了公司的发展谋出路~~ 预先学习&#xff1a; 安装webui《Windows安装Stable Diffusion WebUI及问题解决记录》。运行使用时问题《Windows使用Stable Diffusion时…

nodejs微服务:微服务集群

搭建Grpc微服务集群 某单一的微服务&#xff0c;比如&#xff1a;micro_a, 部署在一台机器上挂掉后, 不管是微服务机器挂掉还是consul_client挂掉&#xff0c;都会导致整个微服务不可访问&#xff0c;这时候我们就需要进行微服务的集群也就是 micro_a 的微服务不能部署到一台机…

基于html+css的盒子旋转

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

Java多线程编程—wait/notify机制

文章目录1. 不使用wait/notify机制通信的缺点2. 什么是wait/notify机制3. wait/notify机制原理4. wait/notify方法的基本用法5. 线程状态的切换6. interrupt()遇到方法wait()7. notify/notifyAll方法8. wait(long)介绍9. 生产者/消费者模式10. 管道机制11. 利用wait/notify实现…

Bert的MLM任务loss原理

bert预训练有MLM和NSP两个任务&#xff0c;其中MLM是类似于“完形填空”的方式&#xff0c;对一个句子里的15%的词进行mask&#xff0c;通过双向transformerfeedforwardrediual_addlayer_norm完成对每个词的embedding编码&#xff0c;然后对mask的这个词进行预测&#xff0c;预…

【记录Bug】IDEA提示“Error:java: 错误: 不支持发行版本 17”

项目场景&#xff1a; 开发工具&#xff1a;IDea 后端框架&#xff1a;SpringBoot 问题描述 在rebuild或运行项目时提示“Error:java: 错误: 不支持发行版本 17”。 这个错误表明你的IDEA版本不支持使用Java 17。你需要将项目编译运行环境设置为更低版本的Java&#xff0c;或…

总结MySQL、Redis的优化措施与使用 mysql_upgrade升级数据结构

目录 一.MySQL数据库优化 二.Redis优化 三.MySQL创建测试账号报错 一.MySQL数据库优化 遵循MySQL层优化的五个原则: 减少数据访问&#xff0c;返回更少的数据&#xff0c;减少交互次数减少服务器CPU开销&#xff0c;利用更多资源。理解SQL优化原理并进行SQL优化&#xff0c…

力扣:字符串中的第一个唯一字符(C++实现)

题目部分&#xff1a; 解题思路&#xff1a; 方案一&#xff1a; 首先认真审题的小伙伴们一定会发现就是题目给了提示只包含小写字母&#xff0c;也就是说我们的排查范围是小写的26个字母。为了怕有的友友们一时短路想不起来&#xff0c;我就其按照顺序列出来吧。 即&#x…

[架构之路-157]-《软考-系统分析师》- 9-信息系统规划-2-少量人力进行项目初步调研(系统分析师的首要任务)与可行性研究报告

目录 9 . 3 初步调查 1. 初步调查的目标 9.4可行性研究 9.4.1可行性评价准则 1 . 经济可行性&#xff08;钱的可行性&#xff09; 2 . 技术可行性&#xff08;能力可行性&#xff09; 3 . 法律可行性&#xff08;社会&#xff09; 4 . 用户使用可行性&#xff08;用户&…

洛谷P2822:组合数问题 ←(帕斯卡法则+取模+前缀和)

【题目来源】https://www.luogu.com.cn/problem/P2822【题目描述】 组合数​表示的是从n个物品中选出m个物品的方案数。举个例子&#xff1a;从(1,2,3)三个物品中选择两个物品可以有(1,2)&#xff0c;(1,3)&#xff0c;(2,3) 这三种选择方法。根据组合数的定义&#xff0c;我们…

属性配置的宏(修改宏IntDir)

项目属性页码 拷贝下 常见宏列表 下表描述了可用宏的常用子集&#xff1b;还有很多没有在这里列出。 转到“宏”对话框&#xff0c;查看项目中的所有属性及其当前值。 有关如何创建 MSBuild 属性定义以及如何在 .props、.targets 和 .vcxproj 文件中将其用作宏的详细信息&am…

面向对象编程(进阶)7:面向对象特征三:多态性

一千个读者眼中有一千个哈姆雷特。 目录 7.1 多态的形式和体现 7.1.1 对象的多态性 举例&#xff1a; 7.1.2 多态的理解 7.1.3 举例 1、方法内局部变量的赋值体现多态 2、方法的形参声明体现多态 3、方法返回值类型体现多态 7.2 为什么需要多态性(polymorphism)&#x…

登录认证功能的实现

1.登录校验分析 什么是登录校验&#xff1f; 所谓登录校验&#xff0c;指的是我们在服务器端接收到浏览器发送过来的请求之后&#xff0c;首先我们要对请求进行校验。先要校验一下用户登录了没有&#xff0c;如果用户已经登录了&#xff0c;就直接执行对应的业务操作就可以了…

C语言头文件路径相关问题总结说明

聊聊系统路径位置&#xff0c;绝对路径与相对路径&#xff0c;正斜杠 / 与 反斜杠 \ 使用说明 ...... by 矜辰所致目录前言一、C语言中的头文件引用二、KEIL 中的头文件路径2.1 IncudePaths 指定的路径绝对路径和相对路径正斜杠 / 与 反斜杠 \ 与双斜杠2.2 include < >…

VN5620以太网测试——环境搭建篇

文章目录 前言一、新建以太网工程二、Port Configuration三、Link up四 Trace界面五、添加Ethernet Packet Builder六、添加ARP Packet七、添加Ethernet IG总结前言 CANoe(CAN open environment)VN5620 :是一个紧凑而强大的接口,用于以太网网络的分析、仿真、测试和验证。 …

页面预加载优化实践

概述在客户端开发中&#xff0c;列表类型页面大多都依赖网络请求&#xff0c;需要等网络数据请求下来后再刷新页面。但遇到网络请求慢的场景&#xff0c;就会导致页面加载很慢甚至加载失败。我负责会员的商品列表页面&#xff0c;在业务场景中&#xff0c;页面元素比较复杂&…

初学对象存储OSS---学习笔记

文章目录前言一、OSS是什么&#xff1f;下面以一个小故事介绍OSS的作用&#xff1a;二、怎么使用OSS1.进入 -----> [阿里云官网](https://www.aliyun.com/)2.点击进入OSS控制台3.获取accessKeyId 和 accessKeySecret4.拿到了accessKeyId 和 accessKeySecret &#xff0c;就可…