字符设备驱动基础4——读写接口的操作实践

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

一、细节提要

1、与用户与内核数据交换有关的函数

(1)copy_from_user()函数

该将数据从用户空间复制到内核空间。

如果成功复制则返回0,如果不成功复制则返回尚未成功复制的剩下的字节数。

(2)copy_to_user()函数

将数据从内核空间复制到用户空间。

(3)复制机制与使用mmap的对比

复制时,内核空间和用户空间的地址不一样,效率低。

2、代码逻辑图

 

二、测试过程

1、代码编写

在ubuntu的/home/xjh/iot/embedded_basic/rootfs/tmp中编写代码:app.c与module_test.c,内容见代码附录。

2、代码编译

(1)编译驱动源代码

利用同目录下的Makefile文件编译module_test.c,得到module_test.ko驱动文件。

(2)编译应用层程序

怎样编译app.c?是使用ubuntu的gcc还是交叉编译工具链?因为此应用层程序要在开发板运行,因此需要使用交叉编译工具链中的gcc来编译,而不是 ubuntu 中的gcc。

在已经正确安装了交叉编译工具链的ubuntu系统中,使用如下命令编译app.c得到app.exe。

arm-linux-gcc app.c -o app.exe

因为之前实验中已经将ubuntu的/home/xjh/iot/embedded_basic/rootfs/tmp挂载到开发板/mnt目录,所以开发板完全启动后,可以在/mnt目录中看到刚才编译的文件。

3、代码测试

可以直接在开发板的/mnt目录下进行测试。

(1)装载测试

[root@xjh mnt]# lsmodNot tainted
[root@xjh mnt]# insmod module_test.ko //安装模块
[ 5278.035378] chrdev_init helloworld init
[ 5278.038524] register_chrdev success... mymajor = 250.//自动分配的主设备号为250
[root@xjh mnt]# lsmod //列出已经安装的模块Not tainted
module_test 1823 0 - Live 0xbf006000//这个具体表示什么意思?
[root@xjh mnt]#

装载后查看/proc/devices,是否有驱动源码中所写的驱动名字、自动分配的主设备号。

[root@xjh mnt]# cat /proc/devices
Character devices:1 mem2 pty
//省略……
250 testchar  //这里出现了我们在驱动程序中给驱动取的名字、自动分配的主设备号
//省略……Block devices:1 ramdisk
259 blkext
//省略……
179 mmc
254 device-mapper

(2)创建设备文件

[root@xjh mnt]# cd /dev
[root@xjh dev]# ls
CEC                 ptyr6               sequencer2          ttyq5
HPD                 ptyr7               snd                 ttyq6
adc                 ptyr8               tty                 ttyq7
//省略……这里没有test这个设备文件。
//接下来看执行“mknod /dev/test c 250 126”之后的效果如何
[root@xjh dev]#[root@xjh ]# mknod /dev/test c 250 126
[root@xjh ]# ls /dev
CEC                 ptyr6               sequencer2                    ttyq4
HPD                 ptyr7               snd                           ttyq5
adc                 ptyr8               test //出现了设备文件test      tyq6
alarm               ptyr9               tty                           ttyq7
//省略……
[root@xjh ]# ls -l /dev/test           
crw-r--r--    1 root     root      250,      126     Jan  1 14:02 /dev/test
[root@xjh ]#                    //主设备号  //次设备号  和mknod时的设置一样

(3)操作设备文件

运行应用层程序app.exe,观察运行效果。

[root@xjh mnt]# ./app.exe 
[ 1587.469172] test_chrdev_open
[ 1587.470769] test_chrdev_write
[ 1587.473521] copy_from_user success..
[ 1587.477106] test_chrdev_read
[ 1587.479949] copy_to_user success..
[ 1587.483352] test_chrdev_release
open /dev/test success..  //这是应用层的判断是否open成功的代码,为何那么迟才输出?
璇诲嚭鏉ョ殑鍐呭鏄細helloworld2222. //为何乱码,scrt的缘故?
[root@xjh mnt]# 

说明

1)Linux系统字符编码默认是UTF-8格式的,如果SecureCRT没有设置成UTF-8格式,中文显示会出现乱码。设置SCRT格式的方法见博客:SecureCRT显示乱码的解决办法

2)为何应用层判断是否open成功的代码很迟才输出?(待解决)

(4)卸载模块测试

[root@xjh mnt]# rmmod module_test.ko 
[ 1385.257231] chrdev_exit helloworld exit
[root@xjh mnt]# lsmodNot tainted
[root@xjh mnt]# 

4、总结说明

(1)应用层的代码编译,要使用交叉编译工具链。

(2)驱动源代码的编译,要使用与开发板系统内核版本一致的内核源码进行编译。从博文字符设备驱动基础2——用开发板来调试驱动的步骤中可以看出,Makefile文件里指明进入与开发板系统内核版本一致的内核源码中,然后make modules。这说明执行的是Makefile文件中的一个目标modules。

//在Makefile的1300行
//目标    依赖
modules: $(module-dirs)@$(kecho) '  Building modules, stage 2.';$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost

(3)mymajor = register_chrdev(0, MYNAME, &test_fops);

参数 0 表示让系统自动分配主设备号。

参数MYNAME表示设备(或者说驱动,没有出现驱动模型概念前,两者混为一谈)的名字。

参数&test_fops是指向struct file_operations变量的指针,这个变量代表着驱动的实质内容

返回值mymajor代表系统为设备分配的主设备号。

/proc/devices文件记录着系统中已经注册的块设备和字符设备,我们可以通过cat来查看此文件内容,两列内容分别表示该设备的名字MYNAME及其设备号mymajor。

此函数成功后,主设备号为mymajor的设备与它的驱动内容&test_fops就关联起来了,也就是说一个设备对应了一个驱动内容。那在应用层如何表示这个设备呢?见(4)。

(4)/dev/test是设备文件,是命令行中使用mknod命令创建的。安装好驱动模块后,会得到系统分配的主设备号mymajor(这里是250)。然后利用这个主设备号,手动地创建设备文件,即执行“mknod /dev/test c 250 126”。这样一来,主次设备号为250、126的设备就和设备文件/dev/test关联了,应用层通过API操作/dev/test文件,也就是操作主次设备号为250、126的设备,而应用层操作里的API最终对应着这个设备对应的驱动&test_fops。

再次说明,设备文件不能使用vim打开,可以使用ls -l 命令查看。

综合(3)(4),主次设备号、设备名字、驱动内容、设备文件,这几个概念要清楚。

主次设备号:主设备号是register_chrdev()的返回值mymajor,次设备号在mknod时设定。

设备名字(或者说驱动名字):MYNAME (主设备号、设备名字在/proc/devices文件中)

驱动内容:&test_fops (程序猿在驱动源代码中编写)

设备文件:/dev/test,利用mknod命令手动创建

(5)设备文件是手动创建的,能不能让它自动创建呢?可以的,见博文字符设备驱动高级篇4——自动创建设备文件的函数代码分析。

(6)应用层的open、read等API,与驱动源码的test_chrdev_open、test_chrdev_read等具体操作函数,通过struct file_operations的填充而关联起来。

(7)示例的驱动源码中的test_chrdev_open、test_chrdev_read等具体操作函数,只是为了演示应用层读写操作与驱动层的读写如何关联起来的,因而没有硬件操作细节(比如硬件寄存器操作这些行为)。实际驱动中会操作一些硬件,比如字符设备驱动基础5——驱动如何操控硬件(动静态映射操作LED)。

 

三、附录代码

1、应用层代码:app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//这是应用层#define FILE "/dev/test" 
//“/dev/test”是利用mknod手工创建的设备文件,创建之后,
//这个设备文件就和该设备编号的设备对应上了,操作此设备文件就是操作该设备。//问题是mknod需要输入主设备号,和驱动源码中自动获取的主设备号不相冲突吗?
//不冲突,因为这里就是根据驱动程序安装后得到的主设备号后
//才利用这个主设备号来创建设备文件的。我理解顺序相反了。
//应该驱动程序安装在前,应用程序运行在后。char buf[100];int main(void)
{int fd = -1;fd = open(FILE, O_RDWR);//这里的open,对应的是驱动文件中的.open指定的函数if (fd < 0){printf("open %s error.\n", FILE);return -1;}printf("open %s success..\n", FILE);// 读写文件write(fd, "helloworld2222", 14);read(fd, buf, 100);printf("读出来的内容是:%s.\n", buf);// 关闭文件close(fd);return 0;
}

2、驱动文件:module_test.c

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <asm/uaccess.h>#define MYNAME		"testchar"int mymajor;        //内核自动分配的主设备号
char kbuf[100];		//内核空间(即驱动空间,毕竟内核和驱动属于同一层)的bufstatic int test_chrdev_open(struct inode *inode, struct file *file)
{// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。printk(KERN_INFO "test_chrdev_open\n");return 0;
}static int test_chrdev_release(struct inode *inode, struct file *file)
{printk(KERN_INFO "test_chrdev_release\n");return 0;
}//读函数,即从内核空间(驱动空间)读取数据到用户空间
ssize_t test_chrdev_read(struct file *file, char __user *ubuf, \size_t count, loff_t *ppos)
{int ret = -1;printk(KERN_INFO "test_chrdev_read\n");//将内容从内核空间(驱动空间)读取到用户空间//返回值为0说明读取成功,读取不成功时返回值是剩余没有读取的字节数ret = copy_to_user(ubuf, kbuf, count);if (ret){printk(KERN_ERR "copy_to_user fail\n");return -EINVAL;}printk(KERN_INFO "copy_to_user success..\n");return 0;
}// 写函数
//1、将应用层的数据先复制到内核中
//2、然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,\size_t count, loff_t *ppos)
{                           //此函数前三个参数,对应应用层的write函数的三个参数?int ret = -1;printk(KERN_INFO "test_chrdev_write\n");// 1、使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中//memcpy(kbuf, ubuf); // 不行,因为用户空间和内核空间,两者不在一个地址空间中ret = copy_from_user(kbuf, ubuf, count);if (ret){printk(KERN_ERR "copy_from_user fail\n");return -EINVAL;}printk(KERN_INFO "copy_from_user success..\n");// 2、真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据//去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码return 0;
}// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {.owner		= THIS_MODULE,			// 惯例,直接写即可.open		= test_chrdev_open,		// 将来应用open打开这个设备时实际调用的就是这个.open对应的函数.release	= test_chrdev_release,		.write 		= test_chrdev_write,.read		= test_chrdev_read,
};// 模块安装函数
static int __init chrdev_init(void)
{	printk(KERN_INFO "chrdev_init helloworld init\n");// 在module_init宏调用的函数中去注册字符设备驱动// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号// 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数mymajor = register_chrdev(0, MYNAME, &test_fops);if (mymajor < 0){printk(KERN_ERR "register_chrdev fail\n");return -EINVAL;}printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);return 0;
}// 模块载函数
static void __exit chrdev_exit(void)
{printk(KERN_INFO "chrdev_exit helloworld exit\n");// 在module_exit宏调用的函数中去注销字符设备驱动unregister_chrdev(mymajor, MYNAME);}module_init(chrdev_init);
module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("aston");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息

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

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

相关文章

mysql分区表mycat_MySQL 中间件之Mycat垂直分表配置

垂直分表就是将一个库下的多个表拆分到多个MySQL实例&#xff0c;实现库压力分流。通过GTID模式复制&#xff0c;db01与db02之间不进行任何连接与复制当前环境&#xff1a;mycat --> db01与db02db01 --> db03db02 --> db04当前垂直分表架构&#xff1a;后端数据库创建…

深入浅出mysql gtid_深入理解MySQL GTID

GTID的概念何为GITDGTID(global transaction identifier)是全局事务标识符&#xff0c;在MySQL5.6版本中作为一个超级特性被推出。事务标识不仅对于Master(起源)的服务器来说是惟一的&#xff0c;而且在整个复制拓扑架构来说&#xff0c;也是全局唯一的。1.GTID的格式GTID sou…

winform 64位系统中使用

WINFOR编译成X86的 转载于:https://blog.51cto.com/agilitygod/1419939

long 转为string_面试必问 Redis数据结构底层原理String、List篇

点击关注上方“Java大厂面试官”&#xff0c;第一时间送达技术干货。阅读文本大概需要 8 分钟。前言今天来整理学习下Redis有哪些常用数据结构&#xff0c;都是怎么使用的呢&#xff1f;首先看下全局存储结构。全局存储结构基础你们肯定都知道&#xff0c;redis支持的基础数据结…

wpf 3D学习

最近在看一些关于wpf 3d的效果&#xff0c;研究了一些代码特效&#xff0c;现在和广大博友共享一下. 首先用到的是MeshGeometry3D&#xff0c;msdn上介绍&#xff1a;用于生成三维形状的三角形基元。主要有4个依赖属性&#xff1a;NormalsProperty&#xff0c;PositionsPropert…

unicode字符、python乱码问题

http://www.cnblogs.com/BeginMan/archive/2013/08/08/3246619.html#a1 Python常见常用知识点http://blog.csdn.net/tingsking18/article/details/4033645 Unicode和Python的中文处理如何让Python的Unicode字符串支持中文&#xff1f;要想利用Python的Unicode机制处理字符串&…

win10下如何安装vb6.0sp6_Mac如何安装win10系统?Parallels Desktop 15 Mac安装win10系统教程...

Parallels Desktop 15 mac版是mac上非常强大也非常好用的虚拟机软件&#xff0c;最新版本的parallels desktop mac 15针对最新的Windows 10更新和macOS Catalina&#xff08;10.15&#xff09;进行了优化。今天分享的内容就是Parallels Desktop 15 mac版如何安装win10系统。PD虚…

字符设备驱动高级篇5——静态映射表的建立过程,动态映射结构体方式操作寄存器

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 补充内容&#xff1a;字符设备驱动基础5——驱动如何操控硬件_天糊土的博客-CSDN博客 一、静态映射表的建立过程 关于“静态映射表的建立”这部分内容&#xff0c;有以下三个关键&#xff1a; &…

python 分布图_python数据分布型图表柱形分布图系列带误差线的柱形图

柱形分布图系列柱形分布图系列使用柱形图的方式展示数据的分布规律&#xff1b;可以借助误差线或散点图&#xff1b;带误差线的柱形图就是使用每个类别的均值作为柱形的高度&#xff1b;再根据每个类别的标准差绘制误差线&#xff1b;缺点&#xff1a;无法显示数据的分布情况&a…

[汇编] 002基础知识-CPU和寄存器

CPU是什么 当然这里的内存不仅仅指电脑上的内存&#xff0c;例如&#xff1a;我的金士顿8G内存&#xff0c;七彩虹1G独显&#xff0c;在这里来说&#xff0c;显卡也是有内存的(寄存器) CPU如何控制其它部件的&#xff1f; 问题&#xff1a;CPU是如何和电脑主机中其它芯片有条不…

字符设备驱动高级篇6——内核提供的读写寄存器接口

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 1、访问寄存器的方式 之前对寄存器的操作&#xff0c;都是先定义指向寄存器的指针&#xff0c;然后再解引用来对寄存器进行操作。这是因为ARM体系中&#xff0c;内存和IO是统一编址的。但是其他体系…

java台球游戏设计原理_Java实现简单台球游戏

Java实现简单台球桌问题&#xff0c;供大家参考&#xff0c;具体内容如下需求&#xff1a;使小球可以在桌面上移动&#xff0c;移动到桌面边缘将被弹回&#xff0c;显示小区的移动素材&#xff1a;小球照片桌球照片程序源代码&#xff1a;package 桌球游戏;import java.awt.*;i…

关于java assertion

大部分转载自参考资料&#xff1a;http://www.ibm.com/developerworks/cn/java/l-javaassertion/index.html assertion(断言)在软件开发中是一种常用的调试方式&#xff0c;assertion就是在程序中的一条语句&#xff0c;它对一个boolean表达式进行检查&#xff0c;一个正确程序…

IOC是什么?

2019独角兽企业重金招聘Python工程师标准>>> Inversion of Control&#xff0c;即反转控制&#xff0c;或许说为依赖注入更为合适。IoC就是一种设计模式。 Interface Driven Design接口驱动&#xff0c;接口驱动有很多好处&#xff0c;可以提供不同灵活的子类实现&a…

poj2516Minimum Cost

http://poj.org/problem?id2516 建图的时候 有个地方写错了 卡了半年。。 题意看了N久啊 有N个店主需要K种物品 有M个供应点 每个供应点有K种物品 其实是算K次最小费用 然后叠加 分解开来这题就是求把某种物品从供应点送到店主那里 多个源点-》多个汇点 所以加一个超级源点 和…

myeclipse连接mysql怎么调用_myeclipse连接mysql数据库详细步骤

第一步 打开Database windows-prefenrence-showview-DBbrowser ,此时会在工具底部有个DBbrowser &#xff0c;选中它&#xff0c;再它所控制的页面的任意位置 右击new---跳转到一个配置driver的页面 (选择连接方式)图一打开myeclipse然后点击window窗口 点击Open Perspective…

虚拟内存管理

MMU 现代操作系统普遍采用虚拟内存管理&#xff08;Virtual Memory Management&#xff09;机制&#xff0c;这需要处理器中的MMU&#xff08;Memory Management Unit&#xff0c;内存管理单元&#xff09;提供支持&#xff0c;本节简要介绍MMU的作用。 首先引入两个概念&…

mysql重新用户设置密码_mysql用户密码如何重新设置?

mysql用户密码重新设置停掉MySQL服务&#xff1a;sudo service mysql stop以上命令适用于Ubuntu和Debian。CentOS、Fedora和RHEL下使用mysqld替换mysql。以安全模式启动mysql&#xff1a;sudo mysqld_safe --skip-grant-tables --skip-networking &这样我们就可以直接用roo…

第三章 门电路

1 半导体二极管开关特性 1 二极管的特性可以近似的用3.2.1的PN结方程和图3.2.2伏安特性曲线描述 如下图 二极管近似伏安特性和对应的等效电路 1 a电路表示vcc和r都很小时候二极管正向导通压降和正向电阻都不能忽视 2 b电路表示二极管正向导通电压不可以忽视&#xff0c;但是二…

mysql查询数据库日期_mysql如何查询日期与时间

前言&#xff1a;在项目开发中&#xff0c;一些业务表字段经常使用日期和时间类型&#xff0c;而且后续还会牵涉到这类字段的查询。关于日期及时间的查询等各类需求也很多&#xff0c;本篇文章简单讲讲日期及时间字段的规范化查询方法。1.日期和时间类型概览MySQL支持的日期和时…