第9章 申请字符设备号实验

news/2024/7/24 4:38:28 标签: 驱动开发

第9章 申请字符设备号实验

经过前面章节的学习,相信大家已经对驱动模块的基本框架、驱动模块传参等知识有了自己的认识,本章节开始就要进入字符设备的世界了。 字符设备是指在I/O传输过程中以字符为单位进行传输的设备,可以使用与普通文件相同的文件操作命令(打开、关闭、读、写等)对字符设备进行操作,是Linux驱动中最基本的一类设备驱动,例如最常见的LED、按键、IIC、SPI,LCD等都属于字符设备的范畴。要想对字符设备进行操作,需要通过设备号来对相应的设备进行查找,在本章节将对设备号相关知识进行讲解。

9.1 申请驱动设备号

9.1.1 设备号申请

在Linux系统中每一个设备都有相应的设备号,通过该设备号查找对应的设备,从而进行之后的文件操作。设备号有主设备号与次设备号之分,主设备号用来表示一个特定的驱动,次设备号用来管理下面的设备。

在Linux驱动中可以使用以下两种方法进行设备号的申请:

1.通过register_chrdev_region(dev_t from, unsigned count, const char *name)函数进行静态申请设备号。

2.通过alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)函数进行动态申请设备号。

两个函数在“内核源码/include/linux/fs.h”文件中引用(在编写驱动程序的时候要加入该文件的引用),如下(图9-1)所示:

extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
extern int register_chrdev_region(dev_t, unsigned, const char *);   

图9-1

  1. 静态申请设备号:

函数原型

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

函数作用

​ 静态申请设备号,对指定好的设备号进行申请。

参数含义

​ from: 自定义的dev_t类型设备号

​ count: 申请设备的数量

​ name: 申请的设备名称

函数返回值

​ 申请成功返回0,申请失败返回负数

2.动态申请设备号:

函数原型

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

函数作用

​ 动态申请设备号,内核会自动分配一个未使用的设备号,相较于静态申请设备号,动态申请会避免注册设备号相同引发冲突的问题。

参数含义

​ dev *: 会将申请完成的设备号保存在dev变量中

​ baseminor: 次设备号可申请的最小值

​ count: 申请设备的数量

​ name: 申请的设备名称

函数返回值

​ 申请成功返回0,申请失败返回负数

对于申请设备号所用到的函数就讲解完成了,会在之后的测试小节对两个函数进行实际运用。

9.1.2 设备号类型

申请的设备号类型为dev_t ,在“内核源码/include/linux/types.h” 文件中定义如下(图9-2)所示:

typedef u32 __kernel_dev_t;
....
typedef __kernel_dev_t      dev_t;

图 9-2

dev_t为u32类型,而u32 定义在文件 “内核源码/include/uapi/asm-generic/int-ll64.h”文件中,定义如下(图9-3):

typedef unsigned int __u32;

图 9-3

__u32为unsigned int类型,所以dev_t是一个无符号的32位整形类型。其中高12位表示主设备号,低20位表示次设备号。在“内核源码/include/linux/kdev_t.h”中提供了设备号相关的宏定义,如下(图9-4)所示:

#define MINORBITS   20   /*次设备号位数*/  
#define MINORMASK   ((1U << MINORBITS) - 1)  /*次设备号掩码*/  

#define MAJOR(dev)  ((unsigned int) ((dev) >> MINORBITS))/*dev右移20位得到主设备号*/  
#define MINOR(dev)  ((unsigned int) ((dev) & MINORMASK))  /*与次设备掩码与,得到次设备号*/ 
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))/*MKDEV宏将主设备号(ma)左移20位,然后与次设备号(mi)相与,得到设备号*/

图 9-4

在稍后的实验中不论是静态申请设备号还是动态申请设备号都会用到上述宏,例如在静态申请设备号时需要将指定的主设备号和从设备号通过MKDEV(ma,mi)宏进行设备号的转换,在动态申请设备号时可以用MAJOR(dev) 和MINOR(dev)宏将动态申请的设备号转化为主设备号和从设备号。

至此,关于设备号相关的知识就结束了,在下一小节中将对申请设备号实验代码进行编写。

9.2 实验程序的编写

本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\04。

本章节实验将编写Linux下申请字符设备号实例代码,如果在进行驱动模块加载时传入了major主设备号,则通过静态的方式进行设备号的申请,如果不传入任何参数进行驱动模块加载,则通过动态的方式进行设备号申请。

编写完成的dev_t.c代码如下(图9-5)所示

#include <linux/init.h>                                                                                                                                
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
static int major;//定义静态加载方式时的主设备号参数major
static int minor;//定义静态加载方式时的次设备号参数minor
module_param(major,int,S_IRUGO);//通过驱动模块传参的方式传递主设备号参数major
module_param(minor,int,S_IRUGO);//通过驱动模块传参的方式传递次设备号参数minor
static dev_t dev_num;//定义dev_t类型(32位大小)的变量dev_num

static int __init dev_t_init(void)//驱动入口函数
{
    int ret;//定义int类型的变量ret,用来判断函数返回值
    /*以主设备号进行条件判断,即如果通过驱动传入了major参数则条件成立,进入以下分支*/
    if(major){
        dev_num = MKDEV(major,minor);//通过MKDEV函数将驱动传参的主设备号和次设备号转换成dev_t类型的设备号
        printk("major is %d\n",major);
        printk("minor is %d\n",minor);
        ret = register_chrdev_region(dev_num,1,"chrdev_name");//通过静态方式进行设备号册
        if(ret < 0){
            printk("register_chrdev_region is error\n");
        }
        printk("register_chrdev_region is ok\n");
    }
    /*如果没有通过驱动传入major参数,则条件成立,进入以下分支*/
    else{
        ret = alloc_chrdev_region(&dev_num,0,1,"chrdev_num");//通过动态方式进行设备号注册
        if(ret < 0){
            printk("alloc_chrdev_region is error\n");
        }                                                                                                                                              
        printk("alloc_chrdev_region is ok\n");
        major=MAJOR(dev_num);//通过MAJOR()函数进行主设备号获取
        minor=MINOR(dev_num);//通过MINOR()函数进行次设备号获取
        printk("major is %d\n",major);
        printk("minor is %d\n",minor);
    }
    return 0;
}

static void __exit dev_t_exit(void)//驱动出口函数
{
    unregister_chrdev_region(dev_num,1);//释放字符驱动设备号 
    printk("module exit \n");
}

module_init(dev_t_init);//注册入口函数
module_exit(dev_t_exit);//注册出口函数
MODULE_LICENSE("GPL v2");//同意GPL开源协议
MODULE_AUTHOR("topeet");  //作者信息  

图 9-5

以上代码通过对传入参数的判断,从而进行设备号申请方式的选择,会在下一小节进行相应的驱动加载测试。

9.3 运行测试

9.3.1 编译驱动程序

在上一小节中的dev_t.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下(图9-6)所示:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += dev_c.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
    make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
    make -C $(KDIR) M=$(PWD) clean    #make clean操作

图 9-6

对于Makefile的内容注释已在上图添加,保存退出之后,来到存放dev_t.c和Makefile文件目录下,如下图(图9-7)所示:

img

图 9-7

然后使用命令“make”进行驱动的编译,编译完成如下图(图9-8)所示:

img

图 9-8

编译完生成dev_t.ko目标文件,如下图(图9-9)所示:

img

图 9-9

至此我们的驱动模块就编译成功了,下面对驱动进行加载测试。

9.3.2 运行测试

开发板上电启动之后,使用以下命令加载dev_t.ko驱动,加载完成之后的打印信息如下图图(9-10)所示:

insmod dev_t.ko major=200 minor=0

img

图 9-10

可以看到传入的主设备号和次设备号都被打印了出来,“register_chrdev_region is ok”也被成功打印了证明设备注册成功了,然后使用以下命令进行注册设备号的查看,如下图(图9-11)所示:

 cat /proc/devices

img

图 9-11

可以看到主设备号200的设备名为chrdev_name,和驱动程序中设置的相同,证明我们的设备号注册成功了,然后使用以下命令进行驱动的卸载,如下图(图9-12)所示:

rmmod dev_t.ko

img

图 9-12

下面进行动态申请设备号实验,使用以下命令进行驱动模块的加载,如下图(图9-13)所示:

insmod dev_t.ko

img

图 9-13

可以看到动态申请设备号成功了,主设备号为236,次设备号为0,然后使用以下命令进行注册设备号的查看,如下图(图9-14)所示:

 cat /proc/devices

img

图 9-14

可以看到主设备号236的设备名为chrdev_name,和驱动程序中设置的相同,证明我们的设备号注册成功了,最后可以输入以下命令对驱动进行卸载,卸载完成如下图(图9-15)所示:

rmmod dev_t.ko

img

图 9-15

【最新驱动资料(文档+例程)】

链接 https://pan.baidu.com/s/1M4smUG2vw_hnn0Hye-tkog

提取码:hbh6

【B 站配套视频】

https://b23.tv/XqYa6Hm

【RK3568 购买链接】

https://item.taobao.com/item.htm?spm=a1z10.5-c-s.w4002-2245

~
rmmod dev_t.ko


[外链图片转存中...(img-S0lsB76z-1694140368377)] 

图 9-15









【最新驱动资料(文档+例程)】

链接 https://pan.baidu.com/s/1M4smUG2vw_hnn0Hye-tkog

提取码:hbh6

【B 站配套视频】

https://b23.tv/XqYa6Hm

【RK3568 购买链接】

https://item.taobao.com/item.htm?spm=a1z10.5-c-s.w4002-2245

2452613.11.2fec74a6elWNeA&id=669939423234

http://www.niftyadmin.cn/n/5010207.html

相关文章

【postgresql 基础入门】基础架构和命名空间层次,查看数据库对象再也不迷路

postgresql 基础架构 ​专栏内容&#xff1a; postgresql内核源码分析手写数据库toadb并发编程 ​开源贡献&#xff1a; toadb开源库 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&…

蓝桥杯官网练习题(算式900)

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 小明的作业本上有道思考题&#xff1a; 看下面的算式&#xff1a; (□□□□-□□□□)*□□900其中的小方块代表 0 ~ 9 的数字&#xff0c;这 10 个方块刚好包含了…

深入探索KVM虚拟化技术:全面掌握虚拟机的创建与管理

文章目录 安装KVM开启cpu虚拟化安装KVM检查环境是否正常 KVM图形化创建虚拟机上传ISO创建虚拟机加载镜像配置内存添加磁盘能否手工指定存储路径呢&#xff1f;创建成功安装完成查看虚拟机 KVM命令行创建虚拟机创建磁盘通过命令行创建虚拟机手动安装虚拟机 KVM命令行创建虚拟机-…

PostgreSQL PG15 新功能 PG_WALINSPECT

开头还是介绍一下群&#xff0c;如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis &#xff0c;Oracle ,Oceanbase 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请加微信号 liuaustin3 &#xff08;…

一见“氢”心-康士柏氢能产业链蓄力待发

9月6日&#xff0c;“欧洲CE、IECQ 合规规范与推进氢能产业链发展会议”在广东康士柏丹灶产业园顺利举行。来自20余家氢能企业、政府部门等近50名代表参会。 本次会议邀请了全球权威认证机构德国德凯DEKRA培训讲师现场培训讲解&#xff0c;分别以欧盟新立法架构(CE合规&#x…

Java 和 PHP GC 的差异和差异出现的原因

JAVA 的 GC 处理 判断草死掉的两种方式&#xff1a;引用计数和可达性分析 可达性分析对 JAVA 比较好用的原因是 JAVA遵守这面向对象的严格要求&#xff0c;每个变量都被对象包裹&#xff0c;所以每个变量都能通过对象来进行遍历找到&#xff0c;最终判断他们的是否被引用&…

nginx服务和uwsgi服务如何设置开机自启动

上次学到了在云服务器下如何部署Django项目&#xff0c;用到了nginx服务和uwsgi服务&#xff0c;需要手工启动这2个服务的命令。 现在考虑如何设置开机自启动&#xff0c;为什么要这样考虑&#xff1f;因为服务器万一出问题&#xff0c;意外重启了&#xff0c;那我们部署的Dja…

python爬虫,多线程与生产者消费者模式

使用队列完成生产者消费者模式使用类创建多线程提高爬虫速度 https://sc.chinaz.com/tupian/index.html https://sc.chinaz.com/tupian/index_2.html https://sc.chinaz.com/tupian/index_3.html from threading import Thread from queue import Queue import requests from b…