正点原子嵌入式linux驱动开发——新字符设备驱动实验

news/2024/11/8 18:48:25/

经过之前两篇笔记的实战操作,已经掌握了Linux字符设备驱动开发的基本步骤,字符设备驱动开发重点是使用register_chrdev函数注册字符设备,当不再使用设备的时候就使用unregister_chrdev函数注销字符设备,驱动模块加载成功以后还需要手动使用mknod命令创建设备节点。register_chrdev和unregister_chrdev这两个函数是老版本驱动使用的函数,现在新的字符设备驱动已经不再使用这两个函数,而是使用Linux内核推荐的新字符设备驱动 API函数。本节就学习一下如何编写新字符设备驱动,并且在驱动模块加载的时候自动创建设备节点文件

新字符设备驱动原理

使用register_chrdev函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会带来问题:需要实现确定主设备号的使用情况,且会将该主设备号下的所有此设备号都使用掉。

解决这两个问题最好的方法就是在使用设备号的时候向Linux内核申请,需要几个就申请几个,由Linux内核分配设备可以使用的设备号。如果没有指定设备号就可以这样申请:

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

如果给定了主设备号和次设备号可以这样申请:

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

from就是申请的其实设备号,即给定设备号;count就是申请数量,name即设备名。

注销字符设备后要释放设备号,可统一使用如下函数:

void unregister_chrdev_region(dev_t from, unsigned count)

可以如此来分配设备号:

1 int major; /* 主设备号 */ 
2 int minor; /* 次设备号 */ 
3 dev_t devid; /* 设备号 */ 
4 
5 if (major) { /* 定义了主设备号 */ 
6     devid = MKDEV(major, 0); /* 大部分驱动次设备号都选择0 */ 
7     register_chrdev_region(devid, 1, "test"); 
8 } else { /* 没有定义设备号 */ 
9     alloc_chrdev_region(&devid, 0, 1, "test"); /* 申请设备号 */ 
10     major = MAJOR(devid); /* 获取分配号的主设备号 */ 
11     minor = MINOR(devid); /* 获取分配号的次设备号 */ 
12 }
13 
14 unregister_chrdev_region(devid, 1); /* 注销设备号 */

新的字符设备注册方法

字符设备结构

可以在Linux中使用cdev结构体表示字符设备,定义在include/linux/cdev.h中:

1 struct cdev { 
2     struct kobject kobj; 
3     struct module *owner; 
4     const struct file_operations *ops; 
5     struct list_head list; 
6     dev_t dev; 
7     unsigned int count;
8 } __randomize_layout

这其中,重要的是ops和dev,即字符设备文件操作函数集合file_operations以及设备号dev_t。使用如下:

struct cdev test_cdev

cdev_init函数

定义好cdev变量之后要调用cdev_init函数来初始化:

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

cdev就是要初始化的cdev结构体变量,fops就是字符设备文件操作函数集合。

cdev_add函数

用于像Linux系统添加字符设备。首先cdev初始化,之后使用cdev_add来向Linux系统添加,原型如下:

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

p指向要添加的字符设备(cdev结构体变量),dev就是设备号,count是设备数量。

cdev_del函数

卸载驱动就是调用这个函数从Linux内核删除相应字符设备,原型如下:

void cdev_del(struct cdev *p)

p就是要删除的设备。

cdev_del和unregister_chedev_region合起来的功能相当于unregister_chedev函数。

自动创建设备节点

在前面的Linux驱动实验中,使用modprobe加载驱动程序以后还需要使用命令“mknod”手动创建设备节点。本节讲解一下如何实现自动创建设备节点,在驱动中实现自动创建设备节点的功能以后,使用modprobe加载驱动模块成功的话就会自动在/dev目录下创建对应的设备文件

mdev机制

udev是一个用户程序,在Linux下通过udev来实现设备文件的创建与删除,udev可以检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。在使用buildroot构建根文件系统的时候选择了udev的简化版本mdev,所以在嵌入式Linux中用mdev来实现设备节点文件的自动创建与删除,Linux系统中的热插拔事件也由mdev管理,如果使用busybox构建根文件系统,会在/etc/init.d/rcS文件中如下语句:

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

上述命令设置热插拔事件由mdev来管理。 buildroot构建的根文件系统已经全部处理好mdev了,不需要在修改什么文件。

创建和删除类

自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在cdev_add函数后面添加自动创建设备节点相关代码。首先要创建一个 class类,class是个结构体,定义在文件include/linux/device.h里面。class_create是类创建函数,class_create是个宏定义。最后将宏class_create展开后如下:

struct class *class_create (struct module *owner, const char *name)

owner一般为THIS_MODULE,name是类名字,返回值是指向结构体class的指针。

卸载驱动程序也需要删除类,函数为class_destroy,原型如下:

void class_destroy(struct class *cls);

cls就是要删除的类。

创建设备

创建好类之后,还需要在这个类下创建一个设备,使用函数device_create,原型如下:

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

device_create是个可变参数函数,cls就是设备要创建哪个类下面;parent是父设备,一般为NULL,也就是没有父设备;devt是设备号;drvdata是设备可能会使用的一些数据,一般为NULL;fmt是设备名字,如果设置fmt=xxx的话,就会生成 /dev/xxx这个设备文件,返回值就是创建好的设备。

同样,卸载驱动的时候需要删除创建的设备,函数为device_destroy,原型如下:

void device_destroy(struct class *cls, dev_t devt)

class是要删除设备所处类,devt是删除的设备号。

参考示例

struct class *class; /* 类 */ 
struct device *device; /* 设备 */ 
dev_t devid; /* 设备号 */ /* 驱动入口函数 */ 
static int __init xxx_init(void) 
{ /* 创建类 */ class = class_create(THIS_MODULE, "xxx"); /* 创建设备 */ device = device_create(class, NULL, devid, NULL, "xxx"); return 0; 1
} /* 驱动出口函数 */ 
static void __exit led_exit(void) 
{/* 删除设备 */device_destroy(newchrled.class, newchrled.devid); /* 删除类 */ class_destroy(newchrled.class); 
} module_init(led_init); 
module_exit(led_exit);

设备私有数据

每个硬件设备都有一些属性,比如主设备号(dev_t),类(class)、设备(device)、开关状(state)等等,在编写驱动的时候你可以将这些属性全部写成变量的形式,但最好的做法试讲所有属性信息做成结构体。编写驱动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; 
}

设置好后,在write、read、close函数中直接读取private_data即可获得设备结构体。

实验程序编写

与之前LED的实验相比,重点就是使用了新的字符设备驱动,设置了文件四有数据,添加了自动创建设备节点相关内容。

LED灯驱动程序编写

区别点在于,申请好__iomem*的映射后虚拟地址指针,就申请一个newchrled_dev的设备结构体newchrdev;然后在led_open中设置私有数据private_data指向newchrdev;最后在led_init中申请设备号、添加字符设备、创建类和设备,并在led_exit中注销字符新设备、删除类和设备。

编写测试APP

直接使用LED实验的APP即可。

运行测试

编译驱动程序和测试APP

把Makefile中obj-m的值改为newchrled.o即可,“make”之后就会有“newchrled.ko”驱动模块文件。

ledAPP.c则通过下述命令编译:

arm-none-linux-gnueabihf-gcc ledApp.c -o ledApp

运行测试

将两个程序拷贝到rootfs/lib/modules/5.4.31目录中,重启开发板并进入到目录lib/modules/5.4.31中,输入如下命令加载newchrled.ko:

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

加载成功后会输出申请到的主设备号和次设备号,如下图所示:
申请到的设备号
驱动加载成功后会自动在/dev目录下创建设备节点文件/dev/newchrdev,输入如下命令查看:

ls /dev/newchrled -l

驱动节点创建成功后就可以使用ledApp软件来测试,测试命令是一样的:

./ledApp /dev/newchrled 1 //打开 LED灯
./ledApp /dev/newchrled 0 //关闭 LED灯

卸载也是一样:

rmmod newchrled

总结

本篇还是在LED驱动的基础上,完成了自动创建驱动节点的代码编写,在led_open将私有数据private_data指向事先声明的结构体来管理设备文件;驱动open函数中添加类和设备。如此在加载好驱动之后,可以直接通过测试APP来测试,不需要自己设定驱动节点了


http://www.ppmy.cn/news/1158937.html

相关文章

如何让你的桌面干净得像一张白纸(详细教程)

文章目录 固定到任务栏固定到快速访问固定到“开始”屏幕添加桌面右键菜单最终效果展示程序员专属工具箱 ✍创作者:全栈弄潮儿 🏡 个人主页: 全栈弄潮儿的个人主页 🏙️ 个人社区,欢迎你的加入:全栈弄潮儿的…

el-table的formatter属性的使用方法

一、formatter是什么? formatter是el-table-column的一个属性,用来格式化内容。(比如后台给你返0或1,你需要展示成“否”和“是”) 二、详细使用 1.知道formatter之前: 代码如下(示例&#…

webrtc安全性 加密方式

媒体加密与通信安全 有各种不同的做法会让实时通信软件暴露在安全隐患中。其中需要特别值得注意的是在信息传输的过程中截取未加密的媒体或者数据。这可以发生在浏览器到浏览器之间或者浏览器到服务器之间的通信过程中,第三方可以窃取到所有发送的数据。但是在数据加…

香港空间在http重定向https出现400状态码

在互联网的发展过程中,随着网络安全意识的提高,越来越多的网站开始采用HTTPS协议来保护用户的数据安全。而为了确保所有的HTTP访问都能重定向到HTTPS站点,一些问题也随之而来。 当我们访问一个使用HTTP协议的网站时,很多浏览器默认…

卡券促销活动如何裂变用户?如何设计吸引人的卡券机制?

​卡券促销活动是市场营销中常见的一种策略,它能够有效地促进产品销售。为了满足不同类型的营销需求,需要根据具体情况配置不同的卡券活动落地手段,以更好地抓住消费者的心理,达到增加收入的目的。 在用户拉新方面,可以…

C语言-程序环境和预处理(2)--带副作用的宏参数,宏与函数的对比,#undef,条件编译,文件包含

前言 上一篇文章–《C语言-程序环境和预处理(1)》讲述了程序的翻译环境和执行环境,编译、连接,预定义符号,#define,#符号和##符号的相关知识。 链接: 《C语言-程序环境和预处理(1)》…

移动一个const对象会发生什么?

考虑下面的代码 struct Test {Test() { std::cout << "Test::Test\n"; }Test(const Test& rhl) : val_{rhl.val_} {std::cout << "Test(const Test&) called" << std::endl;}Test(Test&& rhl) : val_{rhl.val_}{std::…

CSS阶详细解析一

CSS进阶 目标&#xff1a;掌握复合选择器作用和写法&#xff1b;使用background属性添加背景效果 01-复合选择器 定义&#xff1a;由两个或多个基础选择器&#xff0c;通过不同的方式组合而成。 作用&#xff1a;更准确、更高效的选择目标元素&#xff08;标签&#xff09;。…