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
相关推荐
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ43 分钟前
Linux 查询某进程文件所在路径 命令
linux·运维·服务器
安当加密2 小时前
无需改 PAM!轻量级 RADIUS + ASP身份认证系统 实现 Linux 登录双因子认证
linux·运维·服务器
内卷焦虑人士2 小时前
Windows安装WSL2+Ubuntu 22.04
linux·windows·ubuntu
dddddppppp1234 小时前
qemu模拟的一个内核驱动 io口中断
linux
程序员老赵4 小时前
超全 Docker 镜像源配置指南|Windows/Mac/Linux一键搞定,拉镜像再也不卡顿
linux·后端·容器
门豪杰5 小时前
Ubuntu下安装Claude Code
linux·运维·ubuntu·claude·claude code
总要冲动一次5 小时前
离线安装 percona-xtrabackup-24
linux·数据库·mysql·centos
桌面运维家5 小时前
Windows/Linux双启动:BIOS/UEFI多配置桌面创建指南
linux·运维·windows
xlp666hub5 小时前
【Linux驱动实战】:字符设备驱动之内核态与用户态数据交互
linux·面试
久绊A5 小时前
服务器新硬盘初始化与挂载
linux·挂载