当前位置: 首页 > news >正文

深入解析Linux MISC驱动框架

好的,我们来全面、深入地解析 Linux 内核中的 MISC 驱动框架,也称为 杂项设备驱动

1. 原理与定位

设计初衷: 解决“一个驱动对应一个设备文件”的简单字符设备场景,避免重复造轮子。

在 Linux 中,字符设备种类繁多。其中有一大类设备,它们功能简单,通常一个驱动只对应一个设备文件(比如 /dev/my_device),并且主设备号(Major Number)并不固定。为每一个这样的设备都去申请一个主设备号、编写一整套字符设备注册和注销的逻辑,会非常冗余和低效。

MISC 框架的解决方案:
MISC 驱动框架是内核提供的一个轻量级字符设备驱动框架。它的核心思想是:

  • “共享”一个主设备号:所有使用 MISC 框架注册的设备,都共享同一个主设备号 10
  • “独占”一个次设备号:内核为每个 MISC 设备动态分配一个唯一的次设备号(Minor Number),从而在全局主设备号 10 下区分不同的设备。

这样,MISC 设备就成为了主设备号为 10 的字符设备家族中的一员。/dev 目录下很多常见的设备都属于此类,例如:

  • /dev/kmsg (内核日志)
  • /dev/random, /dev/urandom (随机数发生器)
  • /dev/rtc (实时时钟)
  • /dev/uioX (用户空间 I/O)
  • 很多 SoC 的特定功能模块,如 PWM, ADC 等。

2. 核心数据结构与框架组成

理解 MISC 框架,最关键的是掌握其核心数据结构。

2.1 struct miscdevice

这个结构体用于描述一个 MISC 设备。驱动开发者需要填充这个结构体的实例。

// 位于 <linux/miscdevice.h>struct miscdevice {int minor;              // 请求的次设备号const char *name;       // 设备名称,用于创建 /dev/下的设备节点const struct file_operations *fops; // 文件操作集合,这是驱动的主体struct list_head list;  // 内核链表,用于将设备链接到全局链表struct device *parent;  // 父设备(通常指向一个 platform_device 的 dev)struct device *this_device; // 指向自己创建的 device 结构体const struct attribute_group **groups; // 可选属性组,用于 sysfsconst char *nodename;   // 设备节点名称(默认为 name)umode_t mode;           // 设备节点的访问权限(如 0666)
};

关键成员详解:

  1. minor

    • 你可以指定一个具体的次设备号(如 255)。
    • 更常见的做法是设置为 MISC_DYNAMIC_MINOR,这表示你请求内核为你动态分配一个可用的次设备号。这是推荐的做法,可以避免冲突。
  2. name

    • 设备的名字。当设备注册后,会在 /dev 下生成一个名为 name 的设备文件。
    • 例如,如果 name = "my_misc_dev",则会生成 /dev/my_misc_dev
  3. fops

    • 这是驱动开发者的核心工作区
    • 它是一个指向 struct file_operations 的指针,里面包含了驱动实现的各类回调函数,如 open, release, read, write, unlocked_ioctl 等。
    • MISC 框架的本质,就是帮你完成了字符设备注册的通用部分,让你能专注于实现这个 fops
  4. mode

    • 指定设备文件的访问权限。它使用标准的 Unix 权限位,例如:
      • 0666: 所有用户可读可写。
      • 0644: 所有者可读写,组用户和其他用户只读。
2.2 MISC 框架的核心内部机制

内核在 drivers/char/misc.c 中实现了 MISC 框架的核心逻辑:

  1. 主设备号固定:内核在初始化时,使用 register_chrdev 注册了一个主设备号为 10 的字符设备。
  2. 全局链表:内核维护了一个全局的 MISC 设备链表 misc_list
  3. 设备注册:当你的驱动调用 misc_register 时:
    • 如果 minorMISC_DYNAMIC_MINOR,内核会遍历已分配的次设备号,为你找到一个空闲的。
    • 它会创建一个 struct device(这是 Linux 设备模型的核心结构),并将其父设备设置为 misc_device->parent(如果提供了)。
    • 它将你的 miscdevice 结构体添加到全局链表 misc_list 中。
    • 通过 device_create/dev 下创建设备节点。
  4. 设备注销:当调用 misc_deregister 时,过程相反:删除设备节点、从链表中移除、释放资源。

3. 如何使用:编写一个 MISC 驱动

下面我们通过一个完整的示例来演示如何编写、编译、加载和测试一个 MISC 驱动。

步骤 1:编写驱动代码 my_misc_driver.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h> // for copy_to_user/copy_from_user#define DRIVER_NAME "my_misc_dev"// 假设我们有一个简单的“内存”作为设备数据
static char device_buffer[1024];static int my_dev_open(struct inode *inode, struct file *file)
{pr_info("%s is called\n", __func__);return 0;
}static int my_dev_close(struct inode *inode, struct file *file)
{pr_info("%s is called\n", __func__);return 0;
}static ssize_t my_dev_read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{ssize_t retval = 0;// 检查是否已经读到末尾if (*offset >= sizeof(device_buffer)) {goto out;}// 确保不会越界if (len > sizeof(device_buffer) - *offset) {len = sizeof(device_buffer) - *offset;}// 将内核空间的数据拷贝到用户空间if (copy_to_user(buf, device_buffer + *offset, len)) {retval = -EFAULT;goto out;}*offset += len;retval = len;out:pr_info("%s: read %zu bytes from offset %lld\n", __func__, len, *offset);return retval;
}static ssize_t my_dev_write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{ssize_t retval = 0;if (*offset >= sizeof(device_buffer)) {goto out;}if (len > sizeof(device_buffer) - *offset) {len = sizeof(device_buffer) - *offset;}// 将用户空间的数据拷贝到内核空间if (copy_from_user(device_buffer + *offset, buf, len)) {retval = -EFAULT;goto out;}*offset += len;retval = len;out:pr_info("%s: wrote %zu bytes to offset %lld\n", __func__, len, *offset);return retval;
}// 定义文件操作集合
static const struct file_operations my_dev_fops = {.owner = THIS_MODULE,.open = my_dev_open,.release = my_dev_close,.read = my_dev_read,.write = my_dev_write,
};// 定义并初始化 MISC 设备结构体
static struct miscdevice my_misc_device = {.minor = MISC_DYNAMIC_MINOR, // 动态分配次设备号.name = DRIVER_NAME,         // 设备节点名为 "/dev/my_misc_dev".fops = &my_dev_fops,        // 关联文件操作函数集.mode = 0666,                // 所有用户可读可写
};static int __init my_misc_init(void)
{int ret;// 注册 MISC 设备ret = misc_register(&my_misc_device);if (ret) {pr_err("Failed to register misc device: %d\n", ret);return ret;}pr_info("MISC driver loaded successfully. Device node: /dev/%s\n", DRIVER_NAME);return 0;
}static void __init my_misc_exit(void)
{// 注销 MISC 设备misc_deregister(&my_misc_device);pr_info("MISC driver unloaded\n");
}module_init(my_misc_init);
module_exit(my_misc_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple MISC driver example");
MODULE_VERSION("1.0");
步骤 2:编写对应的 Makefile
obj-m += my_misc_driver.oKDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)all:$(MAKE) -C $(KDIR) M=$(PWD) modulesclean:$(MAKE) -C $(KDIR) M=$(PWD) cleaninstall:$(MAKE) -C $(KDIR) M=$(PWD) modules_install
步骤 3:编译和加载驱动
# 编译
make# 加载驱动
sudo insmod my_misc_driver.ko# 检查是否加载成功
dmesg | tail
# 你应该看到类似:"MISC driver loaded successfully. Device node: /dev/my_misc_dev"# 查看已注册的 MISC 设备
cat /proc/misc
# 或者在 /dev 下查看
ls -l /dev/my_misc_dev
# 输出类似: crw-rw-rw- 1 root root 10, 123 May 28 10:00 /dev/my_misc_dev
# 注意主设备号是 10,次设备号是动态分配的(例如 123)
步骤 4:测试驱动
# 写入数据到设备
echo "Hello from userspace!" > /dev/my_misc_dev# 从设备读取数据
cat /dev/my_misc_dev
# 输出: Hello from userspace!# 查看内核日志,观察驱动的 printk 输出
dmesg | tail
步骤 5:卸载驱动
sudo rmmod my_misc_driver
dmesg | tail
# 你应该看到:"MISC driver unloaded"

4. 配置:与设备树(Device Tree)的关联

在实际的嵌入式开发中,硬件信息通常通过设备树(DTS)来描述。MISC 驱动如何与设备树关联呢?

答案是:通过 miscdevice.parent 成员和 platform_driver 机制

这是一种非常常见的模式:“MISC 驱动作为 Platform 驱动的子驱动”

步骤 1:在设备树(.dts)中定义节点
// 在设备树中定义一个节点
my_misc_device: my-misc-dev@12340000 {compatible = "my-company,my-misc-device";reg = <0x12340000 0x1000>; // 设备的物理地址和大小status = "okay";
};
步骤 2:修改驱动代码,使其成为 Platform 驱动
#include <linux/platform_device.h>
#include <linux/of.h>// ... 之前的 file_operations 和 miscdevice 定义保持不变 ...static int my_misc_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev;// 1. 获取设备树中的资源(如寄存器地址)struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);void __iomem *base = devm_ioremap_resource(dev, res);if (IS_ERR(base)) {return PTR_ERR(base);}// 2. (可选)将获取到的资源保存到私有数据中// 3. 关键一步:将 MISC 设备的父设备设置为 platform_device 的 devicemy_misc_device.parent = dev;// 4. 注册 MISC 设备return misc_register(&my_misc_device);
}static int my_misc_remove(struct platform_device *pdev)
{misc_deregister(&my_misc_device);return 0;
}// 定义设备 ID 表,与设备树中的 ‘compatible’ 属性匹配
static const struct of_device_id my_misc_of_match[] = {{ .compatible = "my-company,my-misc-device" },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_misc_of_match);static struct platform_driver my_misc_platform_driver = {.probe = my_misc_probe,.remove = my_misc_remove,.driver = {.name = "my-misc-device",.of_match_table = my_misc_of_match,.owner = THIS_MODULE,},
};module_platform_driver(my_misc_platform_driver);

这样做的优势:

  1. 设备与驱动分离:设备信息在 DTS 中,驱动代码在内核模块中,符合 Linux 设备模型。
  2. 自动探测:当内核启动或模块加载时,平台总线核心会根据 compatible 属性自动调用你的 probe 函数。
  3. 资源管理:可以方便地使用 devm_* (Managed Device Resource)系列 API 来自动管理资源(如内存映射、中断申请),防止资源泄漏。
  4. 清晰的设备层次:在 /sys/class/misc/my_misc_dev/device/ 下,你可以看到该 MISC 设备链接到其父设备(即 platform_device),清晰地展示了设备的硬件拓扑关系。

总结

方面MISC 驱动框架
原理共享主设备号 10,动态分配次设备号,简化简单字符设备驱动开发。
核心结构struct miscdevice,重点是 minor, name, fops, mode, parent
注册/注销misc_register(), misc_deregister()
优势代码简洁,无需关心主设备号申请和字符设备注册细节。
适用场景功能简单、单一设备文件的字符设备,如 PWM, ADC, 看门狗,以及各种 SoC 外设。
高级配置platform_driver 结合,通过设备树描述硬件,实现驱动与设备的解耦和自动探测。

MISC 框架是 Linux 驱动工程师工具箱中一个非常实用且强大的工具,它完美地体现了内核“不重复造轮子”的设计哲学,极大地提高了开发效率。

http://www.dtcms.com/a/540294.html

相关文章:

  • 【Linux学习笔记】线程安全问题之单例模式和死锁
  • 百日挑战-单词版(第六天)
  • 专为WebGIS开发者打造的用户端---GISBox
  • std::set、std::multiset 和 std::unordered_set的异同
  • iO 拆解:从熟悉的密码模块构建
  • 2026年AEI SCI1区TOP,混合近端策略粒子群算法+公路线形优化,深度解析+性能实测
  • 英文的购物网站泉州网站建设方案详细
  • 如何建公司网站的步骤ppt做的最好的网站
  • 网站建设比较好的多少钱建企业网站需要哪些资料
  • 深圳住房和城乡建设部网站大学网站 作风建设专题
  • 电商网站建设需要活动 网站 源码
  • 如何使用Spring Cloud Gateway实现动态路由?
  • Linux Wlan 无线协议栈开发-传输层详解
  • 前端基础之《React(4)—webpack简介-编译打包优化》
  • F039 python五种算法美食推荐可视化大数据系统vue+flask前后端分离架构
  • 网站开发框架参考文献京东官方网上商城
  • Spring OXM:轻松实现Java-XML互转
  • 功能测试总结
  • 小白来学习 LVDS 差分原理及应用
  • 【Linux】网络层协议IP
  • 《Muduo网络库:TcpConnection类》
  • 网站详情页怎么做的好看的网页设计作品欣赏
  • 线扫相机上位机开发——如何提高问题排查效率
  • 计算机网络自顶向下方法10——应用层 HTTP/2 成帧 响应报文优先次序和服务器推
  • 孝感网站的建设网页设计一般一个月工资多少
  • 什么是持续集成(CI)和持续交付(CD)?测试在其中扮演什么角色?
  • 利用机器学习优化CPU调度的一些思路案例
  • Kafka 消息顺序消费深度解析:原理、实现方案与全局有序可行性分析
  • 数据结构初识,与算法复杂度
  • 网站色彩搭配中国纪检监察报社官网