热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【i.MX6ULL】驱动开发2——新字符设备开发模板

上篇文章介绍了字符设备的开发模板,但那是一种旧版本的驱动开发模式,设备驱动需要手动分配设备号再使用register_chrdev进行注册,

上篇文章介绍了字符设备的开发模板,但那是一种旧版本的驱动开发模式,设备驱动需要手动分配设备号再使用 register_chrdev进行注册,加载成功以后还需要手动使用mknod命令创建设备节点,比较麻烦

目前Linux内核推荐的新字符设备驱动API函数,使得驱动的使用更加自动化,本篇就来一起研究下。

先看目录:

文章目录

  • 1 旧字符设备驱动的弊端
  • 2 新字符设备驱动原理
    • 2.1 分配和释放设备号
    • 2.2 字符设备注册
      • 2.2.1 cdev字符设备结构
      • 2.2.2 cdev_init 函数
      • 2.2.3 cdev_add函数
      • 2.2.4 cdev_del函数
    • 2.3 自动创建设备节点
      • 2.3.1 mdev机制
      • 2.3.2 创建和删除类
      • 2.3.3 创建设备
    • 2.4 设置文件私有数据
  • 3 驱动程序编写
    • 3.1 添加一些定义
    • 3.2 修改open函数
    • 3.3 修改init函数
    • 3.4 修改exit函数
    • 3.5 新旧驱动方式对比
  • 4 编译驱动
  • 5 程序测试
    • 5.1 文件发送到板子
    • 5.2 测试
  • 6 总结


1 旧字符设备驱动的弊端

使用register_chrdev函数注册字符设备,需要指定一个设备号,这就造成:

  • 需要事先确定好哪些主设备号没有使用
  • 会将一个主设备号下的所有次设备号都使用掉,比如主设备号为200,那么 0~1048575(2^20-1)这个区间的次设备号就全部都被占用了

回顾上一篇的操作,先是加载驱动:

加载完,还有手动使用mknod指令来手动创建该设备节点,并且指定驱动程序中写死的设备号:

本篇,就要使用一种新的字符驱动编写方式,实现设备号的自动分配,省去mknod指令操作

2 新字符设备驱动原理

2.1 分配和释放设备号

使用设备号的时候向Linux内核申请,需要几个就申请几个,由Linux内核分配设备可以使用的设备号。

使用如下函数来申请设备号(该函数在上篇提到过):

/*
* dev:保存申请到的设备号
* baseminor:次设备号起始地址,一般baseminor为0 (次设备号以baseminor为起始地址地址开始递)
* count:要申请的设备号数量
* name:设备名字
*/

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:

/*
* from:要申请的起始设备号
* count:要申请的设备号数量
* name:设备名字
*/

int register_chrdev_region(dev_t from, unsigned count, const char *name)

注销字符设备之后要释放设备号,不管是通过alloc_chrdev_region函数的动态分配还是register_chrdev_region函数手动指定的设备号,统一使用(和上篇使用的一样)的释放函数:

/*
* from:要释放的设备号
* count:表示从from开始,要释放的设备号数量
*/

void unregister_chrdev_region(dev_t from, unsigned count)

新字符设备驱动下,设备号分配示例代码如下:

int major; /*主设备号*/
int minor; /*次设备号*/
dev_t devid; /*设备号*/ /*定义了主设备号*/
if (major)
{devid = MKDEV(major, 0); /*大部分驱动次设备号都选择0*/ register_chrdev_region(devid, 1, "test");
}
/*没有定义设备号*/
else
{ alloc_chrdev_region(&devid, 0, 1, "test"); /*申请设备号*/ major = MAJOR(devid); /*获取分配号的主设备号*/ minor = MINOR(devid); /*获取分配号的次设备号*/
}

2.2 字符设备注册


2.2.1 cdev字符设备结构

在Linux中使用cdev结构体表示一个字符设备,其定义在include/linux/cdev.h文件中:

struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; /*文件操作函数集合*/struct list_head list; dev_t dev; /*设备号*/ unsigned int count;
};

2.2.2 cdev_init 函数

定义好cdev变量以后就要使用cdev_init函数对其进行初始化:

/*
* cdev:要初始化的cdev结构体变量
* fops:字符设备文件操作函数集合
*/

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

该函数的使用示例如下:

/*要初始化的cdev结构体*/
struct cdev testcdev; /* 设备操作函数 */
static struct file_operations test_fops = { .owner = THIS_MODULE, /* 其他具体的初始项 */
}; testcdev.owner = THIS_MODULE;/* 初始化cdev*/
cdev_init(&testcdev, &test_fops);

2.2.3 cdev_add函数

该函数用于向Linux系统添加字符设备,即cdev结构体变量:

/*
* cdev:要初始化的cdev结构体变量
* dev:字符设备所使用的设备号
* count:要添加的设备数量
*/

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

2.2.4 cdev_del函数

卸载驱动的时候要使用cdev_del函数从Linux内核中删除字符设备:

/*
* p:要删除的字符设备
*/

void cdev_del(struct cdev *p)

2.3 自动创建设备节点

上篇的Linux驱动实验中,在使用modprobe加载驱动程序以后还需要使用“mknod”命令手动创建设备节点,比较麻烦,这里就来研究一下如何实现自动创建设备节点。

2.3.1 mdev机制

在Linux下通过udev来实现设备文件的自动创建与删除。使用busybox构建根文件系统的时候,busybox会创建一个udev的简化版本mdev

所以,在嵌入式开发中使用mdev来实现设备节点文件的自动创建与删除。Linux系统中的热插拔事件也由mdev 管理,在/etc/init.d/rcS 文件中如下语句:

echo /sbin/mdev > /proc/sys/kernel/hotplug

2.3.2 创建和删除类

自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在cdev_add函数后面添
加自动创建设备节点相关代码。

首先要创建一个class类,其实是个结构体,定义在include/linux/device.h里面。class_create是类创建函数(宏定义):

#define class_create(owner, name) \
({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \
}) struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key)

卸载驱动程序的时候需要使用函数为class_destroy删除掉类:

/*
* cls:要删除的类
*/

void class_destroy(struct class *cls);

2.3.3 创建设备

创建好类以后还不能实现自动创建设备节点,还需要在这个类下创建一个设备。使用device_create函数创建设备:

/*
* class:设备要创建哪个类下面
* parent:父设备, 一般为 NULL
* devt:设备号
* drvdata:设备可能会使用的一些数据,一般为 NULL
* fmt:设备名字
*/

struct device *device_create(struct clas *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)

参数最后的...表示这在是一个可变参数的函数。

2.4 设置文件私有数据

每个硬件设备都有一些属性, 比如主设备号(dev_t),类(class)、设备(device)、开关状态(state)等等,在编写驱动的时候你可以将这些属性全部写成变量的形式:

dev_t devid; /*设备号*/
struct cdev cdev; /*cdev*/
struct class *class; /*类*/
struct device *device; /*设备*/
int major; /*主设备号*/
int minor; /*次设备号*/

可以将所有属性信封装到结构体中, 并在编写驱动open函数的时候将其作为私有数据添加到设备文件中:

/*设备结构体*/
struct test_dev{ dev_t devid; /*设备号*/ struct cdev cdev; /*cdev*/ struct class *class; /*类*/ struct device *device; /*设备*/ int major; /*主设备号*/ int minor; /*次设备号*/
}; struct test_dev testdev; /*open函数*/
static int test_open(struct inode *inode, struct file *filp)
{ filp->private_data = &testdev; /*设置私有数据*/ return 0;
}

3 驱动程序编写

在上篇的基础上进行修改,因为只是更换的驱动程序的编写方式,与应用程序无关,因此只修改驱动程序即可。

3.1 添加一些定义

因为上篇文章的代码中使用的是chrdevbase这个名称,为了减少修改量,这里仅把结构体类型定义为带有new标志的newchr_dev,变量名仍使用chrdevbase这个名称。

#define CHRDEVBASE_CNT 1 /* 设备号个数 */
#define CHRDEVBASE_NAME "chrdevbase" /* 名字 *//*newchr设备结构体 */
struct newchr_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */
};struct newchr_dev chrdevbase; /* 自定义字符设备 */

3.2 修改open函数

在上篇程序的基础上增加了一条“设置私有数据”

static int chrdevbase_open(struct inode *inode, struct file *filp)
{printk("chrdevbase open!\r\n");filp->private_data = &chrdevbase; /* 设置私有数据 */return 0;
}

3.3 修改init函数

这个修改比较大,因为要在init函数中使用设备号的自动分配

static int __init chrdevbase_init(void)
{/* 注册字符设备驱动 *//* 1、创建设备号 */if (chrdevbase.major) /* 定义了设备号 */{chrdevbase.devid = MKDEV(chrdevbase.major, 0);register_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT, CHRDEVBASE_NAME);} else /* 没有定义设备号 */{alloc_chrdev_region(&chrdevbase.devid, 0, CHRDEVBASE_CNT, CHRDEVBASE_NAME); /* 申请设备号 */chrdevbase.major = MAJOR(chrdevbase.devid); /* 获取分配号的主设备号 */chrdevbase.minor = MINOR(chrdevbase.devid); /* 获取分配号的次设备号 */}printk("chrdevbase major=%d,minor=%d\r\n",chrdevbase.major, chrdevbase.minor); /* 2、初始化cdev */chrdevbase.cdev.owner = THIS_MODULE;cdev_init(&chrdevbase.cdev, &chrdevbase_fops);/* 3、添加一个cdev */cdev_add(&chrdevbase.cdev, chrdevbase.devid, CHRDEVBASE_CNT);/* 4、创建类 */chrdevbase.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);if (IS_ERR(chrdevbase.class)) {return PTR_ERR(chrdevbase.class);}/* 5、创建设备 */chrdevbase.device = device_create(chrdevbase.class, NULL, chrdevbase.devid, NULL, CHRDEVBASE_NAME);if (IS_ERR(chrdevbase.device)) {return PTR_ERR(chrdevbase.device);}printk("chrdevbase init done!\r\n");return 0;
}

3.4 修改exit函数

因为init修改较大,对应的exit也要进行大的修改:

static void __exit chrdevbase_exit(void)
{/* 注销字符设备驱动 */cdev_del(&chrdevbase.cdev);/* 删除cdev */unregister_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT); /* 注销设备号 */device_destroy(chrdevbase.class, chrdevbase.devid);class_destroy(chrdevbase.class);printk("chrdevbase exit done!\r\n");
}

至此,修改完毕,其它的与之前的一样。

3.5 新旧驱动方式对比

通过一张图来对比新旧两种驱动编写方式的区别:

  • 旧方式编写驱动的流程

  • 新方式编写驱动的流程

可以看出主要区别在驱动的加载和卸载。

4 编译驱动

和上次编译驱动的方式一样,使用makefile,因为驱动的c文件名由chrdevbase.c改为了newchrdevbase.c,因此makefile文件中也要把名字改掉。

编译完之后,将编译出的ko文件先复制到ubuntu虚拟机的tftpboot目录中,为后面的测序做准备。

复制后,看一下tftpboot目录:

5 程序测试

5.1 文件发送到板子

和上篇一样,使用tftp传输,将ubuntu虚拟机编译出的ko文件发送到linux板子中

再来看下tftp传输的硬件环境示意图:

然后是传输指令以及传输结果,可以看到newchrdevbase.ko已经从ubuntu虚拟机的tftpboot目录传输到了linux板子的/lib/modules/4.1.15目录中了。

5.2 测试

输入如下两条指令加载 newchrdevbase.ko 驱动模块:

depmod //第一次加载驱动的时候需要运行此命令
modprobe newchrdevbase.ko //加载驱动

驱动加载成功后,可以看到自动申请到的主设备号和次设备号,如下图,主设备号为249。

再输入ls /dev/chrdevbase -l指令验证/dev/chrdevbase 这个设备节点文件是否存在,如下图,可以看到设备存在,注意和上篇旧驱动方式操作上的不同之处,旧的驱动方式需要额外使用mknod指令来手动创建该设备节点

驱动已经加载成功,再来测试APP程序,理论上和上篇的效果一样,实测也是:

OK,测试完毕,测试完使用rmmod指令卸载驱动。

6 总结

此篇文章针对上篇文章使用旧字符驱动编写方式存在的不足,介绍了一种新的字符驱动编写方式,对比两种方式编写的主要区别,在上篇驱动代码的基础上进行修改,并测试通过,和上篇实现一样的效果,但驱动的加载更加方便,不再需要人为指定设备号。


推荐阅读
  • 在Android平台上利用FFmpeg的Swscale组件实现YUV与RGB格式互转
    本文探讨了在Android平台上利用FFmpeg的Swscale组件实现YUV与RGB格式互转的技术细节。通过详细分析Swscale的工作原理和实际应用,展示了如何在Android环境中高效地进行图像格式转换。此外,还介绍了FFmpeg的全平台编译过程,包括x264和fdk-aac的集成,并在Ubuntu系统中配置Nginx和Nginx-RTMP-Module以支持直播推流服务。这些技术的结合为音视频处理提供了强大的支持。 ... [详细]
  • 在进行网络编程时,准确获取本地主机的IP地址是一项基本但重要的任务。Winsock作为20世纪90年代初由Microsoft与多家公司共同制定的Windows平台网络编程接口,为开发者提供了一套高效且易用的工具。通过Winsock,开发者可以轻松实现网络通信功能,并准确获取本地主机的IP地址,从而确保应用程序在网络环境中的稳定运行。此外,了解Winsock的工作原理及其API函数的使用方法,有助于提高开发效率和代码质量。 ... [详细]
  • 使用cpphttplib构建HTTP服务器以处理带有查询参数的URL请求 ... [详细]
  • 在Ubuntu系统中,由于预装了MySQL,因此无需额外安装。通过命令行登录MySQL时,可使用 `mysql -u root -p` 命令,并按提示输入密码。常见问题包括:1. 错误 1045 (28000):访问被拒绝,这通常是由于用户名或密码错误导致。为确保顺利连接,建议检查MySQL服务是否已启动,并确认用户名和密码的正确性。此外,还可以通过配置文件调整权限设置,以增强安全性。 ... [详细]
  • C++ 进阶:类的内存布局与虚函数类的实现细节
    C++ 进阶:类的内存布局与虚函数类的实现细节 ... [详细]
  • 本文详细探讨了C语言中`extern`关键字的简易编译方法,并深入解析了预编译、`static`和`extern`的综合应用。通过具体的代码示例,介绍了如何在不同的文件之间共享变量和函数声明,以及这些关键字在编译过程中的作用和影响。文章还讨论了预编译过程中宏定义的使用,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • Linux 信号处理全面解析(第六篇)
    本文深入探讨了信号及其来源。信号本质上是对中断机制的软件层面模拟,从原理上看,进程接收到信号与处理器接收到中断请求类似。信号具有异步特性,能够在进程执行过程中随时触发,从而中断当前操作并执行相应的处理程序。文章详细分析了信号的生成、传递和处理机制,并讨论了常见的信号类型及其应用场景。此外,还介绍了如何在 Linux 系统中使用信号进行进程间通信和错误处理,为开发者提供了实用的技术指导。 ... [详细]
  • 在Linux系统中,`inet_pton` 和 `inet_ntop` 是两个重要的IP地址转换函数,它们能够实现IP地址在“点分十进制”和“整数”格式之间的相互转换。特别是 `inet_pton`,它不仅支持IPv4,还支持IPv6地址的转换,广泛应用于网络编程中,确保了不同格式IP地址的高效处理和兼容性。本文将详细探讨这两个函数的内部实现机制及其在网络编程中的具体应用。 ... [详细]
  • BZOJ4240 Gym 102082G:贪心算法与树状数组的综合应用
    BZOJ4240 Gym 102082G 题目 "有趣的家庭菜园" 结合了贪心算法和树状数组的应用,旨在解决在有限时间和内存限制下高效处理复杂数据结构的问题。通过巧妙地运用贪心策略和树状数组,该题目能够在 10 秒的时间限制和 256MB 的内存限制内,有效处理大量输入数据,实现高性能的解决方案。提交次数为 756 次,成功解决次数为 349 次,体现了该题目的挑战性和实际应用价值。 ... [详细]
  • 在《PHP应用性能优化实战指南:从理论到实践的全面解析》一文中,作者分享了一次实际的PHP应用优化经验。文章回顾了先前进行的一次优化项目,指出即使系统运行时间较长后出现的各种问题和性能瓶颈,通过采用一些通用的优化策略仍然能够有效解决。文中不仅详细阐述了优化的具体步骤和方法,还结合实例分析了优化前后的性能对比,为读者提供了宝贵的参考和借鉴。 ... [详细]
  • 结语 | 《探索二进制世界:软件安全与逆向分析》读书笔记:深入理解二进制代码的逆向工程方法
    结语 | 《探索二进制世界:软件安全与逆向分析》读书笔记:深入理解二进制代码的逆向工程方法 ... [详细]
  • 在稀疏直接法视觉里程计中,通过优化特征点并采用基于光度误差最小化的灰度图像线性插值技术,提高了定位精度。该方法通过对空间点的非齐次和齐次表示进行处理,利用RGB-D传感器获取的3D坐标信息,在两帧图像之间实现精确匹配,有效减少了光度误差,提升了系统的鲁棒性和稳定性。 ... [详细]
  • MySQL性能优化与调参指南【数据库管理】
    本文详细探讨了MySQL数据库的性能优化与参数调整技巧,旨在帮助数据库管理员和开发人员提升系统的运行效率。内容涵盖索引优化、查询优化、配置参数调整等方面,结合实际案例进行深入分析,提供实用的操作建议。此外,还介绍了常见的性能监控工具和方法,助力读者全面掌握MySQL性能优化的核心技能。 ... [详细]
  • 题目描述:小K不幸被LL邪教洗脑,洗脑程度之深使他决定彻底脱离这个邪教。在最终离开前,他计划再进行一次亚瑟王游戏。作为最后一战,他希望这次游戏能够尽善尽美。众所周知,亚瑟王游戏的结果很大程度上取决于运气,但通过合理的策略和算法优化,可以提高获胜的概率。本文将详细解析洛谷P3239 [HNOI2015] 亚瑟王问题,并提供具体的算法实现方法,帮助读者更好地理解和应用相关技术。 ... [详细]
  • 利用PaddleSharp模块在C#中实现图像文字识别功能测试
    PaddleSharp 是 PaddleInferenceCAPI 的 C# 封装库,适用于 Windows (x64)、NVIDIA GPU 和 Linux (Ubuntu 20.04) 等平台。本文详细介绍了如何使用 PaddleSharp 在 C# 环境中实现图像文字识别功能,并进行了全面的功能测试,验证了其在多种硬件配置下的稳定性和准确性。 ... [详细]
author-avatar
wen260693700
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有