8、Linux驱动开发:驱动-读写接口实现(readwrite)

news2024/10/7 16:15:46

目录

🍅点击这里查看所有博文

  随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有一点用处的技巧,用的不多的技巧可能一个星期就忘了。

  想了很久想通过一些手段把这些事情记录下来。也尝试过在书上记笔记,这也只是一时的,书不在手边的时候那些笔记就和没记一样,不是很方便。

  很多时候我们遇到了问题,一般情况下都是选择在搜索引擎检索相关内容,这样来的也更快一点,除非真的找不到才会去选择翻书。后来就想到了写博客,博客作为自己的一个笔记平台倒是挺合适的。随时可以查阅,不用随身携带。

  同时由于写博客是对外的,既然是对外的就不能随便写,任何人都可以看到。经验对于我来说那就只是经验而已,公布出来说不一定我的一些经验可以帮助到其他的人。遇到和我相同问题时可以少走一些弯路。

  既然决定了要写博客,那就只能认真去写。不管写的好不好,尽力就行。千里之行始于足下,一步一个脚印,慢慢来 ,写的多了慢慢也会变好的。权当是记录自己的成长的一个过程,等到以后再往回看时,就会发现自己以前原来这么菜😂。

  本系列博客所述资料均来自互联网,并不是本人原创(只有博客是自己写的)。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,没有为自己谋取私利的想法。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。

系统调用

  在前面讲到的基础上,我们继续学习如何实现读写接口。

  在Linux中,应用程序在打开字符设备文件之后。可通过readwrite对字符设备进行读写。应用程序的readwrite通常定义为系统调用函数。

  对于read操作,应用程序通常会调用read()函数来读取数据。该函数的原型如下:

ssize_t read(int fd, void *buf, size_t count);

  其中,fd是文件描述符,buf是用户空间缓冲区指针,count是要读取的字节数。该函数会将数据从文件中读取到缓冲区中,并返回实际读取的字节数。如果读取失败,则会返回-1,并设置相应的错误码。

  对于write操作,应用程序通常会调用write()函数来写入数据。该函数的原型如下:

ssize_t write(int fd, const void *buf, size_t count);

  其中,fd是文件描述符,buf是用户空间缓冲区指针,count是要写入的字节数。该函数会将数据从用户空间缓冲区中写入到文件中,并返回实际写入的字节数。如果写入失败,则会返回-1,并设置相应的错误码。

  除了read()write()函数外,还有其他一些系统调用函数可以用于文件读写操作,例如pread()pwrite()等。这些函数与read()write()函数类似,但提供了更多的选项和控制。

  这些系统调用函数,最终会通过对应驱动的file_operations 结构体调用。这在5.设备-设备注册章节有详细的描述。file_operations结构体如下,本章节的内容就是实现其中的readwrite方法。

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*iterate) (struct file *, struct dir_context *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

数据隔离

  Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据。因为Linux使用的虚拟内存机制,用户空间的数据可能被换出。当内核空间使用用户空间指针时,对应的数据可能不在内存中。

  正确的做法是在内核空间开辟自己可以操作的内存,将用户空间的数据拷贝到内核空间之后,内核操作自己的这块内存即可,即使进程意外结束,内核也不会崩溃,这种做法的安全性是要比前者的高的。

  我们在驱动中常用copy_from_usercopy_to_user来完成内核空间和用户空间之间的数据交换。

copy_from_user&write

  函数copy_from_user的定义如下,其作用是将数据从用户空间拷贝到内核空间。

/*
功能:将数据从用户空间拷贝到内核空间 (write)
参数:
    @to:内核空间的首地址
 @from:用户空间的首地址
    @n:大小(单位是字节)
返回值:成功返回0,失败返回未拷贝字节的个数
*/
int copy_from_user(void *to, const void __user volatile *from,unsigned long n);

  对应的操作也就是系统调用的的write接口。用户程序调用write接口时,通过系统调用,驱动模块中会根据文件的fd数据,最后调用到file_operations 中的*write函数指针。

/*
参数:
	filp:待操作的设备文件file结构体指针
	buf:待写入所读取数据的用户空间缓冲区指针
	count:待读取数据字节数
	f_pos:待读取数据文件位置,写入完成后根据实际写入字节数重新定位
返回:
	成功实际写入的字节数,失败返回负值
*/
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

输入图片说明

copy_to_user&read

  函数copy_to_user的定义如下,其作用是将数据从内核空间拷贝到用户空间。

/*
功能:将数据从内核空间拷贝到用户空间 (read)
参数:
    @to:用户空间的首地址
 @from:内核空间的首地址
    @n:大小(单位是字节)
返回值:成功返回0,失败返回未拷贝字节的个数     
*/   
int copy_to_user(void __user volatile *to, const void *from, unsigned long n);

  对应的操作也就是系统调用的的read接口。用户程序调用read接口时,通过系统调用,驱动模块中会根据文件的fd数据,最后调用到file_operations 中的*read函数指针。

/*
参数:
	filp: 待操作的设备文件file结构体指针
	buf: 待写入所读取数据的用户空间缓冲区指针
	count:待读取数据字节数
	f_pos:待读取数据文件位置,读取完成后根据实际读取字节数重新定位
	__user :是一个空的宏,主要用来显示的告诉程序员它修饰的指针变量存放的是用户空间的地址。
返回值:
	成功实际读取的字节数,失败返回负值
*/   
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

输入图片说明

驱动编写

  节省篇幅,这里的代码是在上一篇文章设计内容的基础上修改的。驱动代码中首先补充file_operationsreadwrite函数。

static struct file_operations hello_ops = 
{
	.open = hello_open,
	.release = hello_release,
	.read = hello_read,
	.write = hello_write,
};

  读取函数的实现就只是读取一个全局变量的内容,该全局变量的默认值为kernel。使用copy_to_user将内核空间的数据拷贝到用户空间的内存中。

#define KMAX_LEN 32
char kbuf[KMAX_LEN + 1] = "kernel";
//read(fd,buff,40);
static ssize_t hello_read (struct file *filep, char __user *buf, size_t size, loff_t *pos)
{
	int error;
	if(size > KMAX_LEN)
		size = KMAX_LEN;
	if(copy_to_user(buf, kbuf, size)){
		error = -EFAULT;
		return error;
	}
	return size;
}

  写入函数的实现修改全局变量的内容。使用copy_from_user将用户空间的数据拷贝到内核空间的内存中。

//write(fd,buff,40);
static ssize_t hello_write (struct file *filep, const char __user *buf, size_t size, loff_t *pos)
{
	int error;
	if(size > KMAX_LEN)
		size = KMAX_LEN;
	memset(kbuf,0,sizeof(kbuf));
	if(copy_from_user(kbuf, buf, size)){
		error = -EFAULT;
		return error;
	}
	printk("%s\n",kbuf);
	return size;
}

实验结果

  编写用户空间的读写程序。打开驱动文件后,首先读取一遍驱动中的默认数据。然后写入数据device write test,最后在读取一遍驱动的数据。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h> //close
void main(void)
{
	int len = 0;
	int fd = open("/dev/hellodev",O_RDWR);
	if(fd < 0) {
		perror("open fail\n");
		return;
	}
	char buf[64 + 1] = {0};
	len = read(fd, buf, sizeof(buf) - 1);
	buf[len] = '\0';
	printf("read:%s,len = %d\n", buf, len);

	char buf2[64 + 1] = "device write test";
	len = write(fd, buf2, strlen(buf2));
	printf("write ok,len=%d\n", len);

	len = read(fd, buf, sizeof(buf) - 1);
	buf[len] = '\0';
	printf("read:%s,len=%d\n", buf, len);

	close(fd);
	return;
}

  从日志中可以看到,读写的数据都是符合预期的。

root@ubuntu:# insmod ./hello.ko 
root@ubuntu:# gcc ./test.c 
root@ubuntu:# ./a.out 
read:kernel,len = 32
write ok,len=17
read:device write test,len=32
root@ubuntu:# dmesg
[236852.445548] hello_init 
[236866.611393] hello_open()
[236866.611482] device write test
[236866.611495] hello_release()

  那么本篇博客就到此结束了,这里只是记录了一些我个人的学习笔记,其中存在大量我自己的理解。文中所述不一定是完全正确的,可能有的地方我自己也理解错了。如果有些错的地方,欢迎大家批评指正。如有问题直接在对应的博客评论区指出即可,不需要私聊我。我们交流的内容留下来也有助于其他人查看,说不一定也有其他人遇到了同样的问题呢😂。

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

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

相关文章

npm 私服以及使用

在工作中&#xff0c;公司有很多内部的包并不希望发布到npm官网仓库&#xff0c;因为可能涉及到一些私有代码不能暴露。对于前端来讲&#xff0c;这时就可以选择在公司内网搭建npm私有仓库。当前比较主流的几种解决方案&#xff1a;verdaccio、nexus、cnpm。大家可以按照自己的…

MongoDB Helloworld For Window

1. 下载MongoDB Download MongoDB Community Server | MongoDB 2. 安装MongoDB 3. 创建DB. 4. 用java code 连接mongo. 做增删改查操作。 pom.xml <dependency><groupId>org.mongodb</groupId><artifactId>mongodb-driver-sync</artifactId>&…

Frida-Hook-Java层操作大全

附件下载 https://github.com/DERE-ad2001/Frida-Labs 前期准备 使用 jadx 进行逆向工程的基础知识。应具备理解 Java 代码的能力。具备编写小型 JavaScript 代码片段的能力。熟悉 adb。设备已 root。Frida环境配置 Hook&#xff08;Hooking&#xff09;简介 让我们从非常…

IPsec VPN协议框架

IPsec是IETF&#xff08;Internet Engineering Task Force&#xff09;制定的一组开放的网络安全协议。它并不是一个单独的协议&#xff0c;而是一系列为IP网络提供安全性的协议和服务的集合&#xff0c;包括认证头AH&#xff08;Authentication Header&#xff09;和封装安全载…

dolphinscheduler海豚调度(五)seatunnel案例

seatunnel作为新一代流行的数据集成工具&#xff0c;其功能非常强大且简单易用&#xff0c;今天演示一下如何通过dolphinscheduler创建并运行seatunnel任务 本次dolphinscheduler和seatunnel均部署在同一机器上的单机版本 1、环境配置 打开dolphinscheduler安装目录&#xf…

【Python】新手入门(6):变量与数据类型

【Python】新手入门&#xff08;6&#xff09;&#xff1a;变量与数据类型 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448…

Java多线程——synchronized、volatile 保障可见性

目录 引出synchronized、volatile 保障可见性Redis冲冲冲——缓存三兄弟&#xff1a;缓存击穿、穿透、雪崩缓存击穿缓存穿透缓存雪崩 总结 引出 Java多线程——synchronized、volatile 保障可见性 synchronized、volatile 保障可见性 原子性&#xff1a;在一次或者多次操作时…

嵌入式学习-FreeRTOS-Day1

一、重点 1、VCC和GND VCC&#xff1a; 1、电路中为电源&#xff0c;供应电压 2、3.3v-5v 3、数字信号中用1表示GND&#xff1a; 1、表示地线 2、一般为0v 3、数字信号中用0表示2、电容和电阻 电容 存储电荷 存储能量&#xff1a; 电容器可以在其两个导体板&#xff08;极…

03.axios数据提交和错误处理

一.axios常用请求方法和数据提交 1. 想要提交数据&#xff0c;先来了解什么是请求方法 请求方法是一些固定单词的英文&#xff0c;例如&#xff1a;GET&#xff0c;POST&#xff0c;PUT&#xff0c;DELETE&#xff0c;PATCH&#xff08;这些都是http协议规定的&#xff09;&am…

【Web】浅聊JDBC的SPI机制是怎么实现的——DriverManager

目录 前言 分析 前言 【Web】浅浅地聊JDBC java.sql.Driver的SPI后门-CSDN博客 上篇文章我们做到了知其然&#xff0c;知道了JDBC有SPI机制&#xff0c;并且可以利用其Driver后门 这篇文章希望可以做到知其所以然&#xff0c;对JDBC的SPI机制的来源做到心里有数 分析 先是…

开源玩具总动员-本博客的知识关系图

作为一个非全职编程爱好者&#xff0c;基本是把计算机周边当做高档大玩具来玩的&#xff0c;顺便带着有兴趣的学生搞一搞学习。这篇文章作为全站的一个导航篇&#xff0c;把本博客的主干要点汇聚一下。从小学开始一直与计算机结缘。通过各种业余时间&#xff0c;慢慢地把感兴趣…

一篇了解电容的使用

目录 一、电容理论基础 1.电容的本质 2.电容量的大小 &#xff08;1&#xff09;电容的单位 &#xff08;2&#xff09;电容量的决定式 3.电容的特点 4.电容的串并联 5.电容器的类型 6.电容实际的电路模型 二、电容器的选型 1.安装方式 2.电容值 3.电容的类型 4…

备战蓝桥杯————二分搜索(一)

引言 一、二分查找 基本概念 代码框架 二、二分查找 题目描述 解题思路及代码 结果展示 三、寻找左侧边界的二分搜索 使用背景 基本代码 引言 在计算机科学的世界里&#xff0c;二分查找算法无疑是一种经典且强大的工具。它以其高效的性能&#xff0c;在有序数据集中…

Java毕业设计 基于SpringBoot 众筹网

Java毕业设计 基于SpringBoot 众筹网 SpringBoot 众筹网 功能介绍 注册 邮箱验证码 登录 忘记密码 首页 图片轮播 关于我们 项目列表 发布项目 我的添加项目 提交审核 已在募捐 项目详情 项目介绍 项目进展 捐赠列表 评论 新闻列表 发布新闻 新闻详情 评论新闻 联系我们 提交…

Android开发工程师面试题,2024年Android开发陷入饱和

前言 马上快到金三银四都春招阶段了&#xff0c;在这本就是跳槽、找工作的年后黄金时间&#xff0c;大多数求职者都早早做好年后求职的准备&#xff0c;其中不乏有年前早早辞了工作准备年后跳槽的有经验的职场老人们&#xff0c;也有一批即将毕业的应届毕业生的职场新人们。 …

redis使用笔记

redis使用笔记 1、Redis简介1.1 含义1.2 功能1.3 特点 2. 常用的数据结构2.1 HASH 3 redis接口定义3.1 redisReply3.2 redisContext3.3 redisCommand 4 实践操作4.1 遇到问题4.1.1 Get哈希的时候返回error4.1.2 长度一直为0&#xff0c;str没法打印&#xff08;未解决&#xff…

合泰HT66F2390----定时器中断学习笔记

前言 无需多言 直接开始定时器中断 的学习 通过上次的PWM学习&#xff0c;上次用的是周期型TM定时器模块 这次使用标准型TM定时器模块&#xff08;STM&#xff09; 代码 #include <HT66F2390.h>void Timer0_Init(void){_stm0c0 0b00001000;_stm0c1 0b11000001;_stm…

Android岗面试,android内存优化面试题

前言 曾听过很多人说Android学习很简单&#xff0c;做个App就上手了&#xff0c;工作机会多&#xff0c;毕业后也比较容易找工作。这种观点可能是很多Android开发者最开始入行的原因之一。 在工作初期&#xff0c;工作主要是按照业务需求实现App页面的功能&#xff0c;按照设…

22万字大模型面经整理+答案

槽位对齐&#xff08;slot alignment&#xff09; 在text2sql任务中&#xff0c;槽位对齐&#xff08;slot alignment&#xff09;通常指的是将自然语言问题中的关键信息&#xff08;槽位&#xff09;与数据库中的列名或API调用中的参数进行匹配的过程。这个过程中&#xff0c…