定义
Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,称为设备文件。
- 应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。
- 系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同类型的设备,而次设备号用来区分同一类型的多个设备。
设备号的组成
一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。
- 主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各个设备。
Linux提供了一个名为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- 操作设备号的宏定义:
#define MINORBITS 20 // 次设备号的位数,一共是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类型。第一个参数是主设备号,第二个参数是次设备号设备号的分配
静态设备号分配
驱动程序开发者通过静态指定一个设备号。
- 对于一部分常用的设备,内核开发者已经为其分配了设备号。
- 这些设备号可以在内核源码
documentation/ devices.txt文件中找到。 - 使用
cat/proc/devices可以查看当前系统已经使用的主设备号 - 设备号的静态申请函数:
#include <linux/fs.h>
/**
* \brief 注册一个字符设备区域
*
* 此函数用于在内核中注册一个字符设备区域,分配一组连续的主次设备号,
* 并将这些设备号与指定的名称关联起来。
*
* \param dev 指向设备号的指针,函数将分配的设备号存储在此处
* \param count 要分配的设备号数量
* \param name 设备的名称
*
* \return 如果成功,返回 0;否则返回一个负的错误代码
*
* \note
* - 成功注册后,设备号将被分配给设备,并且可以在后续的操作中使用。
* - 如果注册失败,通常是因为设备号已经被占用或者无法分配足够的设备号。
*/
int register_chrdev_region(dev_t *dev, unsigned count, const char *name);动态设备号分配
静态设备号容易导致设备冲突
- 系统会自动给你一个没有被使用的设备号,这样就避免了冲突。
- 卸载驱动的时候释放掉这个设备号即可。
- 设备号的动态申请函数:
#include <linux/fs.h>
/**
* \brief 分配并注册一组连续的字符设备号
*
* 此函数用于在内核中分配一组连续的字符设备号,并将这些设备号与指定的名称关联起来。
*
* \param dev 指向设备号的指针,函数将分配的设备号存储在此处
* \param baseminor 基础次设备号,从这个次设备号开始分配
* \param count 要分配的设备号数量
* \param name 设备的名称
*
* \return 如果成功,返回 0;否则返回一个负的错误代码
*
* \note
* - 成功注册后,设备号将被分配给设备,并且可以在后续的操作中使用。
* - 如果注册失败,通常是因为设备号已经被占用或者无法分配足够的设备号。
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);注销字符设备之后要释放掉设备号
- 注销设备号:
#include <linux/fs.h>
/**
* \brief 释放一组连续的字符设备号
*
* 此函数用于释放之前通过 `alloc_chrdev_region` 或其他类似函数分配的一组连续的字符设备号。
*
* \param from 起始设备号
* \param count 要释放的设备号数量
*
* \note
* - 释放设备号后,这些设备号可以被重新分配给其他设备。
* - 在模块卸载时通常会调用此函数来释放设备号。
*/
void unregister_chrdev_region(dev_t from, unsigned count);示例
编写驱动
- 完整代码
/*
* @Descripttion: 动态或者静态申请字符设备号
* @version:
* @Author: topeet
*/
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/fs.h> //包含了文件操作相关struct的定义,例如大名鼎鼎的struct file_operations
#include <linux/kdev_t.h> //
#define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
static int major_num, minor_num; //定义主设备号和次设备号
module_param(major_num, int, S_IRUSR); //驱动模块传入普通参数major_num
module_param(minor_num, int, S_IRUSR); //驱动模块传入普通参数minor_num
static int hello_init(void)
{
dev_t dev_num;
int ret; //函数返回值
if (major_num)
{
/*静态注册设备号*/
printk("major_num = %d\n", major_num); //打印传入进来的主设备号
printk("minor_num = %d\n", minor_num); //打印传入进来的次设备号
dev_num = MKDEV(major_num, minor_num); //MKDEV将主设备号和次设备号合并为一个设备号
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); //注册设备号
if (ret < 0)
{
printk("register_chrdev_region error\n");
}
//静态注册设备号成功,则打印。
printk("register_chrdev_region ok\n");
}
else
{
/*动态注册设备号*/
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, 1, DEVICE_ANAME);
if (ret < 0)
{
printk("alloc_chrdev_region error\n");
}
//动态注册设备号成功,则打印
printk("alloc_chrdev_region ok\n");
major_num = MAJOR(dev_num); //将主设备号取出来
minor_num = MINOR(dev_num); //将次设备号取出来
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("a = %d \n",a);
printk("gooodbye! \n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");编译驱动
obj-m += chrdev.o
KDIR:=/home/topeet/linux/linux-imx
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules ARCH=arm64
clean:
make -C $(KDIR) M=$(PWD) clean静态设备号测试
- 首先使用
cat/proc/devices查看已使用的设备号 - 设置一个主设备号
9
insmod chrdev.ko major_num=9- 再次查看设备号可发现设备号
9 schrdev - 使用
remod chrdev卸载驱动后再次查看设备号发现设备号已消失
动态设备号测试
- 使用
insmod chrdev.ko动态注册方式加载设备号 - 使用
cat/proc/devices查看新生成的设备号 - 使用
remod chrdev卸载驱动后再次查看设备号发现设备号已消失