字符设备的注册分为两部分:
- 注册设备号
- 注册设备本身
注册 / 释放设备号将基于上一次的模板,主要修改的是入口函数和退出函数,在入口函数内注册设备号,在退出函数中释放设备号。
目录
2、静态分配设备号 —— register_chrdev_region
3、动态分配设备号 —— alloc_chrdev_region
4、释放设备号 —— unregister_chrdev_region
一、认识设备号
Linux中每一个设备都有一个设备号,使用 dev_t 类型表示,本质是一个 32 位的无符号整型。每个设备号包含主设备号和次设备号两部分
- 主设备号:高 12 位。表示某一个具体的驱动
- 次设备号:低 20 位。表示某个驱动的各个设备
可以使用 cat /proc/devices 查看已经使用的主设备号。如果采用静态分配设备号,注意不要和已有主设备号重复。
二、分配 / 释放设备号 API
1、设备号操作(主次设备号获取、构建设备号)
在内核源码的 include/linux/kdev.h 中提供了设备号的操作函数
2、静态分配设备号 —— register_chrdev_region
如果已经存在了主设备号(即之前已经注册过该类型的字符设备),只需要我们自己指定一个次设备号的话,我们推荐使用静态分配。
int register_chrdev_region(dev_t from, \
unsigned count, \
const char *name);
from:要申请的起始设备号(该函数支持一次注册多个设备)
count:要申请的设备号数量
name:设备名称
int major = 200;
int minor = 0;
dev_t devid = MKDEV(major, minor); // 第一个参数是主设备号,第二个参数是次设备号
register_chrdev_region(devid, 1, "test");
3、动态分配设备号 —— alloc_chrdev_region
动态分配设备号时,系统会自动给你一个没有被使用的设备号,这样就避免了冲突,卸载驱动时释放掉这个设备号即可。函数声明如下:
int alloc_chrdev_region(dev_t *dev, \
unsigned baseminor, \
unsigned count, \
const char *name);
dev:输出型参数。保存申请到的设备号
baseminor:次设备号起始地址。alloc_chrdev_region 可以申请一段连续的多个设备号,这 些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递 增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count:要申请的设备号数量
name:设备名字
int major;
int minor;
dev_t devid;
alloc_chrdev_region(&devid, 0, 1, "test");
major = MAJOR(devid); // 获取主设备号
minor = MINOR(devid); // 获取次设备号
4、释放设备号 —— unregister_chrdev_region
注销字符设备之后要释放掉设备号。无论是静态分配还是动态分配,都是使用该函数注销设备号。函数声明如下:
void unregister_chrdev_region(dev_t from, unsigned count);
from:要释放的设备号。(这里指的是主设备号,并非上面次设备号的起始地址)
count:表示从 from 开始,要释放的设备号数量。
三、设备号注册测试
1、驱动模块基本思路
为了方便后续使用,这里将主设备号、次设备号封装成一个结构体类型 chrdev_t,该类型的对象为chrdev。注册设备号的基本思路:
① 如果 chrdev.major 为 0
说明此前没有分配过主设备号,此时便动态分配一个设备号。(若没有分配过主设备号,int 类型的全局变量major默认初始化为 0)
② 如果 chrdev.major 不为 0
说明此前已经分配过主设备号,此时便只需静态分配一个次设备号。
2、测试结果
将驱动模块编译到内核以后,输入 cat /proc/devices 查看主设备号
insmod chrdevbase.ko # 加载驱动模块
cat /proc/devices # 查看主设备号
3、驱动模板代码
#include <linux/module.h> // MODULE_LICENSE、MODULE_AUTHOR
#include <linux/init.h> // module_init、module_exit
#include <linux/printk.h> // printk
#include <linux/kdev_t.h> // MKDEV
#include <linux/fs.h> // register_chrdev_region
#define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */
static struct chrdev_t{
dev_t devid; /* 设备号(由主设备号和次设备号组成) */
int major; /* 主设备号 */
int minor; /* 次设备号 */
} chrdev;
/*
* @description : 驱动入口函数
* @param : 无
* @return : 0 成功;其他 失败
*/
static int __init chrdevbase_init(void)
{
int ret = 0;
printk("chrdevbase init!\n");
/* 1、注册设备号 */
if (!chrdev.major) // 主设备号未曾分配过
{
ret = alloc_chrdev_region(&chrdev.devid, 0, 1, CHRDEVBASE_NAME);
chrdev.major = MAJOR(chrdev.devid);
chrdev.minor = MINOR(chrdev.devid);
}
else // 已经分配过主设备号,只需手动分配次设备号
{
chrdev.devid = MKDEV(chrdev.major, 0);
ret = register_chrdev_region(chrdev.devid, 1, CHRDEVBASE_NAME);
}
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit chrdevbase_exit(void)
{
printk("chrdevbase exit!\n");
/* 释放设备号 */
unregister_chrdev_region(chrdev.devid, 1);
}
/*
* 将上面两个函数指定为驱动的入口和出口函数
*/
module_init(chrdevbase_init); // 注册 ko模块被加载到内核,系统会调用的函数
module_exit(chrdevbase_exit); // 注册 ko模块从内核卸载,系统会调用的函数
/*
* LICENSE和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("author_name");