Linux 字符设备驱动中 “主次设备号的静态 / 动态分配” 实验

这个实验是Linux 字符设备驱动中 "主次设备号的静态 / 动态分配" 实验,核心是验证 "手动指定设备号" 和 "内核自动分配设备号" 两种方式,步骤如下:

一、环境准备

和之前的信号量实验一致:

  1. 安装对应架构的交叉编译工具链(如aarch64-linux-gnu-)。
  2. 准备开发板对应的Linux 内核源码 ,记录其路径(后续MakefileKDIR需填写)。

二、代码准备

将实验代码保存为 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.cMakefile所在目录执行:

复制代码
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
相关推荐
不爱学习的笨蛋2 小时前
ubuntu安装gitlab
linux·ubuntu·gitlab
QQ__17646198242 小时前
Ubuntu系统克隆Github仓库项目到本地
linux·ubuntu·github
坚持的小马2 小时前
启动NameServer集群
linux·运维·网络
一只大侠的侠2 小时前
Linux实战:动态进度条从零实现,多版本优化与缓冲区原理全解析
linux·运维·服务器
山人在山上2 小时前
ubuntu mysql 5.7安装
linux·mysql·ubuntu
catoop2 小时前
CentOS 7 重置root密码步骤
linux·运维·centos
RisunJan2 小时前
Linux命令-ifcfg命令(临时配置网络接口的IP地址)
linux·运维·tcp/ip
学习嵌入式的王饱饱2 小时前
2.Linux开发板、树莓派、香橙派等安装VNC远程桌面
linux·远程桌面·vnc·开发板远程桌面
我就是你毛毛哥2 小时前
Linux 系统上安装 Nginx以及使用,推荐使用编译安装
linux·运维·nginx