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

news2024/10/4 21:28:46

《【北京迅为】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 信号的进程 ID
 int 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_NONBLOCK
	if(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; // 将条件置 1
	wake_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); // 删除 cdev

err_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);
	else
		printf("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 的进程 ID
	fcntl(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");
	else
		printf("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) clean
	rm read write

实验结果

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网卡的 Ring Buffer 详解

1. 网卡处理数据包流程 网卡处理网络数据流程图&#xff1a; 图片来自参考链接1 上图中虚线步骤的解释&#xff1a; 1 DMA 将 NIC 接收的数据包逐个写入 sk_buff &#xff0c;一个数据包可能占用多个 sk_buff , sk_buff 读写顺序遵循FIFO&#xff08;先入先出&#xff09;原…

Redis(四)事务 multi、exec

哈喽&#xff0c;大家好&#xff0c;我是有勇气的牛排&#xff08;全网同名&#xff09;&#x1f42e;&#x1f42e;&#x1f42e; 有问题的小伙伴欢迎在文末评论&#xff0c;点赞、收藏是对我最大的支持&#xff01;&#xff01;&#xff01;。 文章目录1 前言1.1 什么是Redi…

从零开始的Web渗透:信息收集步骤详解

一、域名信息收集 1.获取域名的whois信息是、 什么是Whois Whois是一种传输协议&#xff0c;用于查询域名注册所有者等信息。它可以帮助您查询域名是否已被注册&#xff0c;以及获取有关已注册域名的详细信息&#xff0c;例如域名注册商和域名所有人。 早期的Whois查询通常…

Docker 部署Jira8.1.0

Jira与Confluence一样&#xff0c;都需要用到独立的数据库&#xff0c;对于数据库的安装我们不做介绍&#xff0c;主要介绍如何用Docker部署Jira以及对Jira进行破解的操作。 1、数据库准备 关于数据库官方文档说明&#xff1a;https://confluence.atlassian.com/adminjiraserv…

【Spring6】| Spring对事务的支持

目录 一&#xff1a;Spring对事务的支持 1. 事务概述 2. 引入事务场景 3. Spring对事务的支持 3.1 Spring实现事务的两种方式 3.2 Spring事务管理API 3.3 声明式事务之注解实现方式 3.4 事务属性 3.5 事务传播行为propagation 3.6 事务的隔离级别isolation 3.7 事务…

【Android安全】Soot 静态分析教程

参考教程 https://github.com/noidsirius/SootTutorial Windows Soot 环境配置 下载代码 git 拷贝仓库 git init git clone https://github.com/noidsirius/SootTutorial.git ./gradlew.bat build 报错&#xff1a;Unsupported class file major version 57 ./gradlew.b…

JDK定时/延迟任务实现原理

刚刚好点进去看了,做个笔记 先读 这样子的延迟任务代码很常见,在保持心跳、延迟确认等等场景 从源码的角度看他是怎么实现的 Testpublic void delayTest() throws InterruptedException {Executors.newScheduledThreadPool(1).schedule(() -> {System.out.println("一…

Baumer工业相机堡盟工业相机如何联合BGAPISDK和OpenCVSharp实现图像的拉普拉斯算法增强(C#)

Baumer工业相机堡盟工业相机如何联合BGAPISDK和OpenCVSharp实现图像的拉普拉斯算法增强&#xff08;C#&#xff09;Baumer工业相机Baumer工业相机使用图像算法增加图像的技术背景Baumer工业相机通过BGAPI SDK联合OpenCV使用图像增强算法1.引用合适的类文件2.BGAPI SDK在图像回调…

数学与应用数学有哪些SCI期刊推荐? - 易智编译EaseEditing

以下是数学与应用数学领域的几个知名SCI期刊&#xff1a; Annals of Mathematics&#xff1a; 成立于1884年&#xff0c;是数学领域最古老和最著名的期刊之一&#xff0c;由普林斯顿大学出版。 该期刊发表了许多重要的数学成果&#xff0c;如Gdel不完全定理、费马大定理证明…

景点VR全景虚拟体验系统定制

为深度挖掘行业特色&#xff0c;利用5G、VR&#xff0c;AI&#xff0c;AR等数字化技术&#xff0c;为行业领域量身打造数字化解决方案已成趋势 VR内容定制可包括: VR旅游、VR展馆、VR教育、VR汽车、VR电商、VR地产等等。我们是国内较早从事沉浸式VR内容开发的企业&#xff0c;在…

Python将Excel文件内容写入Word文件

在日常办公中我们经常需要将Excel文件中的数据写入Word中&#xff0c;如果是手动一个一个进行复制粘贴&#xff0c;那将会非常的耗时且繁琐&#xff01; 遇到这种问题我们首先想到就是利用b编程解决&#xff0c;今天我分享一个excel转word的小方法&#xff01; 首先我有一个E…

儿童乙肝的预防和治疗,看这一篇就够了

儿童乙肝治疗应早期进行从1967年发现乙型肝炎&#xff08;以下简称乙型肝炎&#xff09;病毒&#xff0c;1969年开发乙型肝炎疫苗&#xff0c;到乙型肝炎治疗药物不断出现&#xff0c;乙型肝炎的防治取得了显著成效。目前&#xff0c;乙型肝炎的预防已经取得了积极的效果。儿童…

配置FTP/TFTP协议的ASPF

在多通道协议和NAT的应用中&#xff0c;ASPF是重要的辅助功能。通过配置ASPF功能&#xff0c;实现内网正常对外提供FTP和TFTP服务&#xff0c;同时还可避免内网用户在访问外网Web服务器时下载危险控件。 组网需求 如图1所示&#xff0c;FW部署在某公司的出口&#xff0c;公司提…

Jenkins Harbor

Harbor 环境搭建 https://github.com/goharbor/harbor/releases/tag/v2.5.6 点击下载地址安装包 安装 解压安装包 [rootlocalhost ~]# tar -zxvf harbor-offline-installer-v2.5.6.tgz -C /usr/local/修改harbor.yml配置 [rootlocalhost harbor]# cp harbor.yml.tmpl ha…

VMware vSphere中三种磁盘模式:精简置备/厚置备置零/厚置备延迟置零

在VMware vSphere中&#xff0c;不管是以前的5.1版本&#xff0c;或者是现在的6.5版本&#xff0c;创建虚拟机时&#xff0c;在创建磁盘时&#xff0c;都会让选择磁盘的置备类型&#xff0c;如下图所示&#xff0c;分为&#xff1a; Thick ProvisionedLazy Zeroed(厚置备延迟置…

从0开始使用flask搭建WEB前端可视化界面

目录1.download一个模板2.配置flask python文件3.移动模板中文件的相对位置4.修改html中的原路径5.运行与调试1.download一个模板 模板 将其中的html结尾的文件放入template文件夹&#xff0c;其余的放入static文件夹&#xff0c;再创建一个python文件使用flask&#xff0c;…

亚马逊云科技从成本规划,开启云财务管理之旅

亚马逊云科技的云财务管理旨在帮助企业建立一个成功的CFM战略&#xff1a;通过4个云财务管理CFM原则或步骤作为路线图&#xff1a;SEE-查看、SAVE-保存、PLAN-计划和RUN-运行。 对现有工作负载的预测和规划 1、 优化计算资源与架构&#xff1a;与技术业务相关部门合作&#xff…

AI数字人在VR全景中的应用有哪些?有哪些优势?

“十年生死两茫茫&#xff0c;不思量&#xff0c;自难忘”。以往我们对于逝者的怀念只限于看着老照片落泪&#xff0c;现如今&#xff0c;各种科技的发展让我们的思念有了新的承载之地。AI数字人的出现&#xff0c;可以为用户提供更加智能、有趣的社交体验&#xff0c;通过唇形…

为什么要写博客?现身说法

我为什么不想写博客&#xff1f; 大四学生现身说法&#xff0c;其实早在大二&#xff0c;听各种大牛说写博客的重要性&#xff0c;真的也很想开始动手&#xff0c;但两个很现实的顾虑摆在眼前&#xff1a; 啥都不会&#xff0c;写啥啊&#xff1f;大牛是因为有新东西可写&…

Linux学习[6]文件权限深入1

文章目录前言1. 文件的各个字段含义2. 修改文件权限3. 有点意思的东西总结前言 前六个博客是基于树莓派的linux教程书籍写的&#xff0c;因为之前的书籍是以树莓派为基准&#xff0c;所以在linux上没有很详细。这个博客开始记录的是我看书过程中遇到的有意思的&#xff0c;没见…