电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
【野火i.MX6ULL ARM Linux开发板连载】字符驱动的API操作
分 享
扫描二维码分享
【野火i.MX6ULL ARM Linux开发板连载】字符驱动的API操作
野火,驱动开发
瑟寒凌风
关注
发布时间: 2021-01-18
丨
阅读: 741
前面我们实现了最简单的驱动模版,这次我们继续探究驱动程序的编写。 驱动代码核心的两句,是注册模块加载函数module_init()和注册模块卸载函数module_exit()。 本次我们要提到的是设备号的申请,字符设备的注册和节点的创建。 # 申请设备号 申请设备号通常有两种申请方式:静态申请和动态申请。 ## 静态申请设备号 静态申请设备号的函数原型 ```c int register_chrdev_region(dev_t from, unsigned count, const char *name) //参数dev_t from: dev_t类型的变量,用于指定字符设备的起始设备号,如果要注册的设备号已经被其他的设备注册了,那么就会导致注册失败。 //参数unsigned count:指定要申请的设备号个数,count的值不可以太大,否则会与下一个主设备号重叠。 //参数const char *name:表示设备名称,我们可以在/proc/devices中看到该设备。 //返回值0表示申请成功,返回其它表示失败,并且失败的原因可以通过错误码获取。 ``` 使用静态申请设备号时,都需要去查阅内核哪些设备号被使用,一旦有重复的将导致设备号申请失败,对应的设备注册不成功,使用十分不便。 ## 动态申请设备号 动态申请设备号的函数原型 ```c int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) //参数dev_t *dev:指向dev_t类型数据的指针变量,用于存放分配到的设备编号的起始值; //参数unsigned baseminor:次设备号的起始值,通常情况下,设置为0; //参数unsigned count:同register_chrdev_region类型,用于指定需要分配的设备编号的个数以及设备的名称。 //返回值0表示申请成功,返回其它表示失败,并且失败的原因可以通过错误码获取。 ``` 该函数函数,内核会自动分配给我们一个尚未使用的主设备号,不要认为操作,使用比静态方便多了,在司机程序开发中,特别是在3.x的内核中使用十分普遍。 ## 释放设备号 无论是以上哪种方式申请设备号,都需要使用unregister_chrdev_region()函数来释放设备号,释放设备号的函数在删除设备时被调用,以供设备重新注册时提供可用的设备号。如果删除时不释放,在设备重新添加时将报错,导致新设备不可用,进而影响驱动的稳定。 函数原型 ```c void unregister_chrdev_region(dev_t from, unsigned count) //参数dev_t from:指定需要注销的字符设备的设备编号起始值,我们一般将定义的dev_t变量作为实参。 //参数unsigned count:指定需要注销的字符设备编号的个数,该值应与申请函数的count值相等,通常采用宏定义进行管理。 ``` ## 动静态综合方式申请设备号 在内核中提供了一个函数既可以用于动态申请,也可以用于静态申请,原型如下 ```c static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); } //参数unsigned int major:该参数用于指定要申请的主设备号,相当于静态申请,当该值设为0时,由系统自动分配,相当于动态申请。 //参数const char *name:指定设备的名称。 //参数const struct file_operations *fops:设备的函数接口指针。 ``` 该函数的释放设备号函数原型如下 ```c 该函数的释放设备号函数原型如下 static inline void unregister_chrdev(unsigned int major, const char *name) { __unregister_chrdev(major, 0, 256, name); } ``` 至于该部分,贴出我的代码 ```c printk(KERN_EMERG "[ KERN_EMERG ] Hello Module Init\n"); ret = alloc_chrdev_region(&devno, 0,1, "HelloWorld"); if(ret == 0) printk(KERN_EMERG "[ KERN_EMERG ] baseminor ok\n"); else return 0; major = MAJOR(devno); minor = MINOR(devno); ``` # cdev操作 ## 初始化 我们使用驱动的API与系统的API进行关联,主要是要实现file_operations结构体,而这个结构体需要使用cdev_init()函数来操作。 ```c void cdev_init(struct cdev *cdev, const struct file_operations *fops) //cdev:struct cdev类型的指针变量,指向需要关联的字符设备结构体; //fops:file_operations类型的结构体指针变量,一般将实现操作该设备的结构体file_operations结构体作为实参。 ``` ## 注册 注册使用如下函数 ```c int cdev_add(struct cdev *p, dev_t dev, unsigned count) //p:struct cdev类型的指针,用于指定需要添加的字符设备; //dev:dev_t类型变量,用于指定设备的起始编号; //count:指定注册多少个设备。 ``` 只有注册了才能通过后续操作来创建设备节点。 ## 注销 ```c void cdev_del(struct cdev *p) //p:struct cdev类型的指针,用于指定需要添加的字符设备。 //注册和注销是配对的,注销函数实在删除设备时执行的。 ``` 该部分我的代码 ```c cdev_init(&cdev,&fops); ret = cdev_add(&cdev, devno, 1); if(ret < 0) { printk(KERN_EMERG "[ KERN_EMERG ] cdev_add error\n"); } ``` # file_operations结构体 该结构体是api的主要实现地方,该结构体原型很庞大,我就截个图上来吧 ![](https://cf01.ickimg.com/bbsimages/202101/b3b5c11ac734e63990a683c33d57c608.jpg) 这只是其中一部分,本文实现的函数仅仅是open(),release(),write(),read(),这是个函数将通过应用层的api一一调用演示。 该部分我的代码如下 ```c ssize_t hello_read(struct file *file, char __user *data, size_t length, loff_t *loff) { printk(KERN_EMERG "[ KERN_EMERG ] Hello Module hello_read\n"); return 0; } ssize_t hello_write(struct file *file, const char __user *data, size_t length, loff_t *loff) { printk(KERN_EMERG "[ KERN_EMERG ] Hello Module hello_write\n"); return 0; } int hello_open(struct inode *inode, struct file *file) { printk(KERN_EMERG "[ KERN_EMERG ] Hello Module hello_open\n"); return 0; } int hello_release(struct inode *inode, struct file *file) { printk(KERN_EMERG "[ KERN_EMERG ] Hello Module hello_release\n"); return 0; } struct file_operations fops = { .owner = THIS_MODULE, .open = hello_open, .release = hello_release, .write = hello_write, .read = hello_read, }; ``` # 设备节点 ## 设备节点的创建 节点的创建就是要讲设备创建并且注册到文件系统中,其函数原型如下 ```c struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt) //struct class *class:指向这个设备应该注册到的struct类的指针; //struct device *parent:指向此新设备的父结构设备(如果有)的指针,通常是NULL; //dev_t devt:要添加的字符设备的设备号; //void *drvdata:要添加到设备进行回调的数据; //const char *fmt:输入设备名称。 ``` 创建成功时返回设备结构体指针,失败时返回错误码。 ## 设备节点的删除 删除设备节点原型函数如下 ```c void device_destroy(struct class *class, dev_t devt) //struct class *class:指向注册此设备的struct类的指针; //dev_t devt:要添加的字符设备的设备号。 ``` 设备号的删除是在设备删除时执行的。 该部分我的代码如下,代码很简单,只要理解到了,很容易就写出来 ```c hello_class = class_create(THIS_MODULE,"HelloWorld"); device_create(hello_class,NULL,devno,NULL,"HelloWorld");//创建设备节点 ``` # 代码 Hellomodule.c文件如下 ```c #include
#include
#include
#include
#include
#include "linux/cdev.h" #include
#include
int major = 0; int minor = 0; dev_t devno = 0; struct cdev cdev; static struct class *hello_class; ssize_t hello_read(struct file *file, char __user *data, size_t length, loff_t *loff) { printk(KERN_EMERG "[ KERN_EMERG ] Hello Module hello_read\n"); return 0; } ssize_t hello_write(struct file *file, const char __user *data, size_t length, loff_t *loff) { printk(KERN_EMERG "[ KERN_EMERG ] Hello Module hello_write\n"); return 0; } int hello_open(struct inode *inode, struct file *file) { printk(KERN_EMERG "[ KERN_EMERG ] Hello Module hello_open\n"); return 0; } int hello_release(struct inode *inode, struct file *file) { printk(KERN_EMERG "[ KERN_EMERG ] Hello Module hello_release\n"); return 0; } struct file_operations fops = { .owner = THIS_MODULE, .open = hello_open, .release = hello_release, .write = hello_write, .read = hello_read, }; static int __init hello_init(void) { int ret = 0; printk(KERN_EMERG "[ KERN_EMERG ] Hello Module Init\n"); ret = alloc_chrdev_region(&devno, 0,1, "HelloWorld"); if(ret == 0) printk(KERN_EMERG "[ KERN_EMERG ] baseminor ok\n"); else return 0; major = MAJOR(devno); minor = MINOR(devno); cdev_init(&cdev,&fops); ret = cdev_add(&cdev, devno, 1); if(ret < 0) { printk(KERN_EMERG "[ KERN_EMERG ] cdev_add error\n"); } hello_class = class_create(THIS_MODULE,"HelloWorld"); device_create(hello_class,NULL,devno,NULL,"HelloWorld");//创建设备节点 return 0; } static void __exit hello_exit(void) { device_destroy(hello_class, devno); class_destroy(hello_class); cdev_del(&cdev); unregister_chrdev_region(devno, 1); printk(KERN_EMERG "[ KERN_EMERG ] Hello Module Exit\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("chen"); ``` Helloworld.c代码如下 ```c #include "stdio.h" #include "unistd.h" #include
#include
#include
int main() { char buf[4] = {0}; char *hello = "/dev/HelloWorld"; int fd = open(hello,O_RDWR|O_NDELAY); if(fd < 0) { printf("open error\n"); return 0; } write(fd,"0",1); read(fd,buf,1); close(fd); return 0; } ``` Helloworld.c代码是一个应用层的测试代码,编译时使用如下命令 ```shell arm-linux-gnueabihf-gcc helloworld.c -o helloworld ``` # 效果 输入安装命令,可以看到打印了模块初始化语句 ![](https://cf01.ickimg.com/bbsimages/202101/fff9d205a310c0aee264627af0eeee32.jpg) 执行sudo ./helloworld可以看到我们程序中调用的4个函数都分别执行了一遍 ![](https://cf01.ickimg.com/bbsimages/202101/c8df32efe7a24a78d249634f683d8769.jpg) 同时使用命令ls /dev/H*能够看到我们自己的设备名称 ![](https://cf01.ickimg.com/bbsimages/202101/600b95f9066450eb0ddedd5951e05ae1.jpg) 通过卸载命令,可以看到卸载函数的打印信息 ![](https://cf01.ickimg.com/bbsimages/202101/f1725157ae261944a675a03ebb8bfb47.jpg) 本篇完。
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
瑟寒凌风
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交