这个实验是Linux 字符设备驱动中 "主次设备号的静态 / 动态分配" 实验,核心是验证 "手动指定设备号" 和 "内核自动分配设备号" 两种方式,步骤如下:
一、环境准备
和之前的信号量实验一致:
- 安装对应架构的交叉编译工具链(如
aarch64-linux-gnu-)。 - 准备开发板对应的Linux 内核源码 ,记录其路径(后续
Makefile中KDIR需填写)。
二、代码准备
将实验代码保存为 dev_t_test.c(代码与实验一致,仅补全注释):
cpp
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
static int major; //静态分配时的主设备号
static int minor; //静态分配时的次设备号
//向模块传递参数(主/次设备号 权限为用户可读)
module_param(major,int,S_IRUGO);
module_param(minor,int,S_IRUGO);
static dev_t dev_num; //存储设备号,主设备和从设备的组合
//驱动入口函数,分配设备号、
static int __init dev_t_init(void)
{
int ret;
//情况1:手动传入了major参数 静态分配设备号
if(major){
dev_num = MKDEV(major,minor);
printk("major is %d\n",major);
printk("minor is %d\n",minor);
//静态分配设备号
/*int register_chrdev_region(dev_t first, unsigned int count, \
char *name);
参数:
1. dev_t first: 起始的设备号,包括主设备号 (MAJOR,默认最大511) 和次设备号 (MINOR)。
2. unsigned count: 需要分配的连续设备号的数量。
3. const char *name: 注册的设备名称,用于用户态与内核交互时的设备识别。
返回值:
- 返回 0 表示成功。
- 如果失败,返回一个负的错误码。
*/
ret = register_chrdev_region(dev_num,1,"major_minor_name");
if(ret < 0){
printk("register_chrdev_region is error\n");
}
printk("register_chrdev_region is ok\n");
}
//情况2:未传入major参数 动态分配设备号
else{
//动态分配设备号,起始设备号为0
/*
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
参数解析:
1. dev_t *dev:
- 用于返回分配的设备号(包含主设备号和次设备号)。
- 主设备号通过自动分配的方式确定。
- 次设备号起始值通过 baseminor 指定。
2. unsigned baseminor:
- 指定起始的次设备号。
3. unsigned count:
- 要分配的连续设备号的数量。
4. const char *name:
- 设备的名称,用于用户态和内核的设备识别。
返回值:
- 返回 0 表示成功。
- 返回负数表示失败,失败值是一个错误码(通过 PTR_ERR 提供)。
*/
ret = alloc_chrdev_region(&dev_num,0,1,"major_minor_name");
if(ret < 0){
printk("alloc_chrdev_region is error\n");
}
printk("alloc_chrdev_region is ok\n");
major = MAJOR(dev_num);
minor = MINOR(dev_num);
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("guo module exit\n");
}
module_init(dev_t_init);//注册入口函数
module_exit(dev_t_exit);//注册出口函数
MODULE_LICENSE("GPL v2");//遵循GPL协议
三、编写 Makefile(编译驱动模块)
创建Makefile文件(注意替换KDIR为你的内核源码路径):
makefile
cpp
# 1. 补充:导出ARM64架构(必须)
export ARCH=arm64
# 2. 补充:用export导出交叉编译器(必须,子进程才能继承)
export CROSS_COMPILE=/home/alientek/rk3568_linux5.10_sdk/buildroot/output/rockchip_atk_dlrk3568/host/bin/aarch64-buildroot-linux-gnu-
# 内核源码目录(你的路径保持不变)
KERNELDIR := /home/alientek/rk3568_linux5.10_sdk/kernel
# 当前目录(你的定义保持不变)
CURRENT_PATH := $(shell pwd)
# 要编译的模块(你的定义保持不变)
obj-m := dev_t_test.o
# 目标定义(你的形式保持不变)
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
四、编译驱动模块
在dev_t_test.c和Makefile所在目录执行:
make

编译完成后,会生成 dev_t_test.ko(驱动模块文件)。
五、部署与测试(开发板上操作)
将dev_t_test.ko传到开发板,然后分两种情况测试:
情况 1:静态分配设备号(手动指定 major/minor)
加载模块时直接传入主 / 次设备号(比如major=200,minor=0):
insmod dev_t_test.ko major=200 minor=0

执行dmesg查看输出,会显示:
plaintext
major is 200
minor is 0
register_chrdev_region is ok
情况 2:动态分配设备号(不指定参数)
直接加载模块,内核自动分配设备号: 如果手动分配主设备号20000失败

动态分配了3616
rmmod dev_t_test # 先卸载之前的模块
insmod dev_t_test.ko
执行dmesg查看输出,会显示内核分配的主 / 次设备号(比如):
plaintext
alloc_chrdev_region is ok
major is 240
minor is 0
六、收尾
测试完成后,卸载驱动模块:
rmmod dev_t_test
