一、为什么需要"无需mknod"的驱动?
在 Linux 中,每个设备都有一个唯一的 设备号(device number),由主设备号(major)和次设备号(minor)组成。例如:
cs
/dev/led -> 主设备号: 255, 次设备号: 0
传统字符设备驱动的注册流程如下:
- 使用 register_chrdev_region() 注册设备号。
- 手动调用 mknod /dev/led c 255 0 创建设备节点。
- 用户程序通过 open("/dev/led") 访问设备。
传统方式的问题:
- 必须预先定义主设备号(如 DEV_MAJOR = 255),容易冲突。
- 需要用户手动运行 mknod,不适合自动化部署。
- 若未正确设置权限或路径,无法访问设备。
- 不利于热插拔和动态加载场景。
二、什么是杂项设备(Misc Device)?
misc 是 "miscellaneous" 的缩写,用于处理那些不归属于特定类别的小型、简单设备,如 LED、按键、蜂鸣器、ADC 等。
其核心优势在于:
- 自动分配设备号
- 自动创建 /dev/xxx 节点
- 无需手动执行 mknod
- 轻量级、易于维护
三、misc 杂项设备驱动代码
1. 头文件包含与 设备名称
cs
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#define DEV_NAME "led"
- <linux/kernel.h>:提供内核打印函数 printk()。
- <linux/fs.h>:定义文件操作接口(如 open, read, write)。
- <linux/uaccess.h>:提供用户空间与内核空间数据拷贝函数(如 copy_from_user)。
- 系统会自动创建 /dev/led 节点。
2.全局变量:寄存器映射指针
cs
static volatile unsigned long *iomux_mux_ctl;
static volatile unsigned long *iomux_pad_ctl;
static volatile unsigned long *gpio1_gdir;
static volatile unsigned long *gpio1_dr;
- iomux_mux_ctl:复用功能配置寄存器。
- iomux_pad_ctl:电气特性配置寄存器。
- gpio1_gdir:方向控制寄存器。
- gpio1_dr:数据输出寄存器。
3. 初始化函数:设置 GPIO 功能
cs
static void led_init(void)
{
*iomux_mux_ctl = 0x05;
*iomux_pad_ctl = 0x1080;
*gpio1_gdir |= (1 << 3);
}
- 设置引脚为 GPIO 功能(0x05)。
- 配置电气特性(上拉、速度等)。
- 将 GPIO1_IO03 配置为输出方向(第 3 位设为 1)。
4.控制函数:点亮与熄灭 LED
cs
static void led_on(void)
{
*gpio1_dr &= ~(1 << 3); // 清除第3位,输出低电平
}
static void led_off(void)
{
*gpio1_dr |= (1 << 3); // 设置第3位,输出高电平
}
5. 文件操作函数:open()、write()、read()、close()。
cs
static int open(struct inode *inode, struct file *file)
{
led_init();
printk("led open\n");
return 0;
}
static ssize_t read(struct file *file, char __user *buf, size_t size, loff_t *loff)
{
printk("led read\n");
return 0;
}
static ssize_t write(struct file *file, const char __user *buf, size_t size, loff_t*loff)
{
char data[20] = {0};
long len = copy_from_user(data, buf, size > sizeof(data) ? sizeof(data) : size);
if (!strcmp(data, "led_on"))
led_on();
else if (!strcmp(data, "led_off"))
led_off();
else
return -EINVAL;
printk("led write\n");
return len;
}
static int close(struct inode *inode, struct file *file)
{
led_off();
printk("led close\n");
return 0;
}
6. 文件操作结构体:fops
cs
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.release = close,
};
- .owner:指定所属模块,防止模块被卸载时产生问题。
- .open, .read, .write, .release:分别绑定对应的处理函数。
7.杂项设备结构体:miscdevice
cs
static struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops,
};
- .minor = MISC_DYNAMIC_MINOR:让内核自动分配次设备号(推荐方式)。
- .name = "led":设备在 /dev/ 下的名称。
- .fops = &fops:指向文件操作函数表。
8. 模块初始化函数:led_init()
cs
static int __init led_init(void)
2{
3 int ret = misc_register(&misc_dev);
4 if (ret) {
5 printk("misc_led_init failed ret = %d\n", ret);
6 return ret;
7 }
8
9 iomux_mux_ctl = ioremap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03, 4);
10 iomux_pad_ctl = ioremap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03, 4);
11 gpio1_gdir = ioremap(GPIO1_GDIR, 4);
12 gpio1_dr = ioremap(GPIO1_DR, 4);
13
14 printk("####################### led_init()\n");
15 return 0;
16}
- 调用 misc_register() 注册杂项设备,内核会自动创建 /dev/led 节点。
- 使用 ioremap() 将硬件寄存器映射到内核虚拟地址空间。
- 打印初始化成功信息。
9.模块退出函数:led_exit()与模块入口与出口声明
cs
static void __exit led_exit(void)
{
misc_deregister(&misc_dev);
iounmap(iomux_mux_ctl);
iounmap(iomux_pad_ctl);
iounmap(gpio1_gdir);
iounmap(gpio1_dr);
printk("####################### led_exit()\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
四、关键函数讲解
| 函数 | 功能 |
|---|---|
misc_register() |
注册杂项设备,返回设备号并创建 /dev/<name> 节点 |
misc_deregister() |
卸载时释放资源 |
MISC_DYNAMIC_MINOR |
表示让内核自动选择次设备号(推荐) |
MISC_STATIC_MINOR(n) |
手动指定次设备号(不推荐) |
五、总结
| 特性 | 传统字符设备 | misc 设备 |
|---|---|---|
| 设备号 | 手动分配 | 自动分配 |
| 设备节点 | 需 mknod |
自动生成 |
| 管理复杂度 | 高 | 低 |
| 适用场景 | 复杂设备(如网卡、串口) | 简单设备(LED、按键、蜂鸣器) |
| 是否支持热插拔 | 支持(配合 udev) | 支持(默认支持) |