驱动-申请字符设备号

提示:字符设备是指在 I/O 传输过程中以字符为
单位进行传输的设备, 可以使用与普通文件相同的文件操作命令(打开、 关闭、 读、 写等) 对
字符设备进行操作, 是 Linux 驱动中最基本的一类设备驱动, 例如最常见的 LED、 按键、 IIC、
SPI, LCD 等都属于字符设备的范畴。 要想对字符设备进行操作, 需要通过设备号来对相应的设
备进行查找


目的

  • 掌握静态指定设备号的register_chrdev_region方法

  • 掌握动态分配设备号的alloc_chrdev_region方法

  • 理解两种方法的区别及适用场景

备注

字符设备内容包括了:申请-注册-创建节点-杂项设备-内核空间用户空间数据交换-文件私有数据-驱动兼容不同设备-错误处理-点亮LED灯 等系统模块,所以这篇文章内容是开篇,方便后续理解,打基础。

设备号申请

在 Linux 系统中每一个设备都有相应的设备号, 通过该设备号查找对应的设备, 从而进行
之后的文件操作。 设备号有主设备号与次设备号之分, 主设备号用来表示一个特定的驱动, 次
设备号用来管理下面的设备。

在 Linux 驱动中可以使用以下两种方法进行设备号的申请:

  • 通过 register_chrdev_region(dev_t from, unsigned count, const char *name)函数进行静态
    申请设备号。
  • 通 过 alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char
    *name)函数进行动态申请设备号。
    两个函数在“内核源码/include/linux/fs.h”文件中引用

设备号基础概念

设备号由两部分组成:

  • 主设备号:标识设备类型(如 240 代表某个特定驱动)

  • 次设备号:标识同类设备中的具体实例

设备号用 dev_t 类型表示(32位整数,高12位为主设备号,低20位为次设备号)

设备号申请方法

静态申请(指定设备号)


#include <linux/fs.h>

// 指定主设备号和设备名称
int register_chrdev_region(dev_t first, unsigned int count, const char *name);

参数说明:

  • first:要分配的起始设备号(用 MKDEV 宏生成)
  • count:连续设备号的数量
  • name:设备名称(出现在 /proc/devices)
示例申请代码

#define MAJOR_NUM 240   // 选择未被使用的主设备号
#define DEVICE_NAME "my_static_dev"

dev_t dev = MKDEV(MAJOR_NUM, 0);  // 主设备号240,次设备号0

if (register_chrdev_region(dev, 1, DEVICE_NAME) < 0) {
    printk(KERN_ERR "Failed to register device number\n");
    return -EBUSY;  // 设备号可能已被占用
}

这里再次逐字分析:

  • MAJOR_NUM:定义主设备号
  • DEVICE_NAME: 定义字符驱动名称
  • dev_t: 类型
  • MKDEV:宏定义,构造设备号
  • register_chrdev_region :静态申请字符设备

动态申请(自动分配设备号)


int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, 
                      unsigned int count, const char *name);
					  

参数说明:

  • dev:输出参数,保存分配到的设备号
  • firstminor:请求的起始次设备号(通常为0)
  • count:需要的设备号数量
  • name:设备名称
动态示例申请代码

dev_t dev;
int major;

if (alloc_chrdev_region(&dev, 0, 1, "my_dynamic_dev") < 0) {
    printk(KERN_ERR "Failed to allocate device number\n");
    return -EFAULT;
}

major = MAJOR(dev);  // 提取主设备号
printk(KERN_INFO "Allocated major number: %d\n", major);

这里再次逐字分析:

  • dev_t: 类型,输出参数,保存分配到的设备号
  • major:定义一个变量,最后动态申请得到的主设备号
  • alloc_chrdev_region: 动态申请设备号方法
  • MAJOR: 通过方法,提取设备号,参数是dev_t

实验

源码程序

根据上面的 设备号申请 相关介绍,这里给出测试程序


#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>        //申请设备号需要用到的头文件      
#include <linux/kdev_t.h>

static int major;//定义静态加载方式时的主设备号参数major
static int minor;//定义静态加载方式时的次设备号参数major

module_param(major, int, S_IRUGO);//传递int 类型变量name
module_param(minor, int, S_IRUGO);//传递int 类型变量name
static dev_t  dev_num;//定义dev_t类型(32位大小)的变量dev_num


static int __init dev_t_init(void)//驱动入口函数
{
    int ret;//定义int类型的变量ret,用来判断函数返回值

    /*以主设备号进行条件判断,即如果通过驱动传入了major参数则条件成立,进入以下分支*/
    if(major){
       dev_num=MKDEV(major,minor);                 //通过MKDEV函数将驱动传参的主设备号和次设备号转换成dev_t类型的设备号
       printk("major received %d \n",major);
       printk("major received %d \n",minor);
         ret=register_chrdev_region(dev_num,1,"chardev_name");
       if(ret<0){

       printk("register_chrdev_region    is error  \n");

       }
       //printk("register_chrdev_region    is ok    %d  \n" , ret);

    }else{
    /*如果没有通过驱动传入major参数,则条件成立,进入以下分支*/
    ret=alloc_chrdev_region(&dev_num,0,1,"chardev_num"); //通过动态方式进行设备号注册
    
    if(ret < 0){
            printk("alloc_chrdev_region is error\n");
    }   
 printk("alloc_chrdev_region is ok\n");
        major=MAJOR(dev_num);//通过MAJOR()函数进行主设备号获取
        minor=MINOR(dev_num);//通过MINOR()函数进行次设备号获取
        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(KERN_EMERG "parameter_exit\n");
}
module_init(dev_t_init);//注册入口函数
module_exit(dev_t_exit);//注册出口函数
MODULE_LICENSE("GPL v2");//同意GPL开源协议
MODULE_AUTHOR("wang fang chen "); //作者信息

源程序代码分析

再次对上面需要注意到的点,逐字分析说明:

  • major 、minor : 主设备号 测设备号定义变量,int 类型。
  • module_param : 前面的基础知识,驱动传参,这里用到了。
  • dev_num :dev_t的类型变量,就是设备号的定义。
  • MKDEV:将主设备号和此设备号传递的值,转换为 内核认识的dev_t 类型的设备号
  • register_chrdev_region :设备号静态申请方法
  • alloc_chrdev_region :设备号动态申请方法
  • &dev_num : 动态申请设备号的 输出参数,保存分配到的设备号。 这里为什么用 指针类型,这里不多讲了,基础内容,需要注意下。
  • MAJOR :动态申请到设备号后,可以通过这个api进行获取主设备号
  • MINOR :动态申请到设备号后,可以通过这个api进行获取次设备号

MakeFile 编译文件

#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += dev_t.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean


make 进行编译

测试

动传参,传递参数实验-静态申请

insmod  dev_t.ko  major=200   minor=0

获取内核打印信息

 dmesg | tail

在这里插入图片描述

查看系统字符设备驱动

可以看到传入的主设备号和次设备号都被打印了出来, “register_chrdev_region is ok” 也
被成功打印了证明设备注册成功了, 然后使用以下命令进行注册设备号的查看

cat /proc/devices

在这里插入图片描述
我们看到了我们静态申请的设备号了

动传参,传递参数实验-动态申请

先卸载驱动一次

rmmod dev_t.ko

实际结果如下

在这里插入图片描述
进行动态传参实验->动态申请设备号

insmod  dev_t.ko  

获取内核打印信息

 dmesg | tail 

在这里插入图片描述

查看系统字符设备驱动

cat /proc/devices

在这里插入图片描述

动态申请和静态申请区别-场景-对比

静态申请动态申请
类型register_chrdev_regionalloc_chrdev_region
设备号指定方式开发者静态指定内核动态分配
主要参数明确的dev_t设备号输出参数获取分配的设备号
优点设备号固定,便于管理避免冲突,更安全
缺点可能发生设备号冲突每次加载可能获得不同设备号
适用场景需要固定设备号的传统设备大多数现代驱动开发
错误处理需检查设备号是否被占用只需检查分配是否成功

总结

  • 之前的动态传参实际应用
  • 设备号的理解,动态和静态申请设备号熟悉理解,后续必须用到的基础知识
  • 字符设备号在系统位置的熟悉
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野火少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值