10_申请字符类设备号
申请字符类设备号
字符设备号简介
Linux 的设备管理是和文件系统紧密结合的, 各种设备都以文件的形式存放在/dev 目录下, 称为设备文件。 应用程序可以打开、 关闭和读写这些设备文件, 完成对设备的操作, 就像操作普通的数据文件一样。
为了管理这些设备, 系统为设备编了号, 每个设备号又分为主设备号和次设备号。 主设备号用来区分不同类型的设备, 而次设备号用来区分同一类型的多个设备。
设备号的组成
一个字符设备或者块设备都有一个主设备号和次设备号。 主设备号和次设备号统称为设备号。 主设备号用来表示一个特定的驱动程序。 次设备号用来表示使用该驱动程序的各个设备。
1.字符设备和杂项设备的区别(复习)
杂项设备的主设备号是固定的,固定为10,那么我们要学习的字符类设备就需要自己或者系统来给我们分配了。
杂项设备可以自动生成设备节点,字符设备需要我们自己生成设备节点。
2.注册字符类设备号的俩个方法。
头文件 #include <linux/fs.h>
静态分配设备号
静态分配设备号, 就是驱动程序开发者通过静态指定一个设备号。 对于一部分常用的设备, 内核开发者已经为其分配了设备号。 这些设备号可以在内核源码 documentation/ devices.txt 文件中找到。 如果只有开发者自己使用这些设备驱动程序, 那么其可以选择一个尚未使用的设备号。 在不添加新硬件的时候, 这种方式不会产生设备号冲突。 但是当添加新硬件时, 则很可能造成设备号冲突, 影响设备的使用。 使用“cat /proc/devices”命令即可查看当前系统中所有已经使用了的主设备号, 如图所示:
设备号的静态申请函数如下所示:
register_chrdev_region(dev_t, unsigned, const char *);
指定一个设备号, 需要明确知道我们的系统里面那些设备号没有用。
Linux 提供了一个名为dev_t 的数据类型表示设备号, dev_t 定义在文件include/linux/types.h 里面, 定义如下:
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
dev_t 是个 32 位的变量, 其中 12 位用来表示主设备号, 20 位用来表示次设备号。 因此 Linux 系统中主设备号范围为 0~4095, 所以大家在选择主设备号的时候一定 不要超过这个范围。 在文件include/linux/kdev_t.h 中提供了几个操作设备号的宏定义, 如下所示:
Linux 提供了几个宏定义来操作设备号:
#define MINORBITS20 //次设备号的位数, 一共是 20 位
#define MINORMASK ((1U << MINORBITS) - 1) //次设备号的掩码
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //在 dev_t 里面获取我们的主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //在 dev_t 里面获取我们的次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //将我们的主设备号和次设备号组成一个 dev_t 类型。第一个参数是主设备号, 第二个参数是次设备号
动态分配 设备号
由于静态分配设备号可能存在冲突问题, 因此建议使用动态分配设备号。 在注册字符设备之前先申请一个设备号, 系统会自动给你一个没有被使用的设备号, 这样就避免了冲突。 卸载驱动的时候释放掉这个设备号即可, 设备号的动态申请函数如下:
alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
3.注销设备号
我们使用的是
unregister_chrdev_region(dev_t, unsigned);
注销字符设备之后要释放掉设备号, 设备号释放函数如下:
示例
chrdev.c
#include <linux/init.h> //包含宏定义的头文件
#include <linux/module.h> //包含初始化加载模块的头文件
#include <linux/kdev_t.h>
#include <linux/fs.h>
#define DEVICE_NUMBER 1 // 定义次设备号的个数
#define DEVICE_SNAME "schrdev" // 定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" // 定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0 // 定义次设备号的起始地址
static int major_num; // 主设备号
static int minor_num; // 次设备号
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);
static int hello_init(void)
{
dev_t dev_num; // 设备号
int ret;
if (major_num)
{
dev_num = MKDEV(major_num, minor_num); // 主设备号和次设备号组成一个 dev_t 设备号
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); //静态分配设备号
if(ret < 0)
{
printk("register_chrdev_region is error\n");
}
printk("register_chrdev_region is ok\n");
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
}
else
{
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME); //动态分配设备号
if(ret < 0)
{
printk("alloc_chrdev_region is error\n");
}
printk("alloc_chrdev_region is ok\n");
major_num = MAJOR(dev_num); //在 dev_t 里面获取我们的主设备号
minor_num = MINOR(dev_num); //在 dev_t 里面获取我们的次设备号
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
}
return 0;
}
static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER); //注销设备号
printk("byby\n"); // 内核模块卸载的时候打印"byb byb
}
module_init(hello_init); // 驱动模块的入口
module_exit(hello_exit); // 驱动模块的出口
MODULE_LICENSE("GPL"); // 声明模块拥有开源许可证
Makefile
obj-m +=chrdev.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!