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

RK3399内核驱动实战:获取设备号控制LED的四种方法(由浅入深、代码注释详尽)

RK3399 内核驱动实战:获取设备号控制 LED 的四种方法(由浅入深、代码注释详尽)

在 Linux 字符设备驱动开发中,设备号(major + minor)是内核与用户空间沟通的桥梁。文章围绕设备号这一条线展开,从基础原理,到四种实现方式,并结合完整代码与注释,讲明每一步为什么要这样做、容易出错的地方以及如何纠正。

目录

  1. 设备号基础(dev_tMAJOR/MINOR/MKDEV
  2. 方法一:register_chrdev()(最简单,快速上手)
  3. 方法二:alloc_chrdev_region() + cdev(工程化、推荐)
  4. 方法三:多设备(多个 minor) + iminor()/imajor()(同一驱动管理多实例)
  5. 方法四:kfifo+ 多 minor(虚拟设备示例)
  6. 编译、加载、测试(Makefile、insmod、用户态程序)
  7. 常见坑与调试清单
  8. 总结与建议

1. 设备号基础:dev_t 与三个宏

  • dev_t:内核用来表示设备号的类型(一般是 32 位数,包含 major 和 minor)。
  • MAJOR(dev_t d):取主设备号(major)。
  • MINOR(dev_t d):取次设备号(minor)。
  • MKDEV(major, minor):通过主次号生成 dev_t

例:dev_t dev = MKDEV(100, 0); 对应主设备号 100,次设备号 0。用户空间设备节点 /dev/xxx 会包含这个 dev_t,内核 VFS 据此找到驱动并调用 file_operations

2. 方法一:register_chrdev() —— 最简单、快速(单设备适用)

要点

  • 调用 register_chrdev(0, name, &fops) 时传入 0 表示动态分配主设备号,返回值即为分到的主设备号。
  • 适用于简单单设备驱动示例或教学场景。不使用 cdev,扩展性有限。
  • 注意:GPIO 在模块卸载时再释放,不要在 init 里立即释放。

完整内核模块示例(基于提供代码,做出必要修正与注释)

// file: led_register.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/gpio.h>struct rk_led {unsigned int major;      // 主设备号dev_t devno;             // dev_t(major/minor)struct class *cls;       // 用于 device_createstruct device *dev;int gpio_led;            // GPIO 编号(示例:GPIO1_C7 -> 55)
};static struct rk_led *led_device;/* open: 打开设备(用户态 open("/dev/led") 会触发) */
static int led_open(struct inode *inode, struct file *filp)
{// 打开时点亮 LEDgpio_direction_output(led_device->gpio_led, 1);pr_info("led: open -> ON (gpio=%d)\n", led_device->gpio_led);return 0;
}/* release: 关闭设备(用户态 close() 会触发) */
static int led_release(struct inode *inode, struct file *filp)
{// 关闭时熄灭 LEDgpio_direction_output(led_device->gpio_led, 0);pr_info("led: release -> OFF (gpio=%d)\n", led_device->gpio_led);return 0;
}/* 仅实现 open/release,示例简单 */
static const struct file_operations fops = {.owner   = THIS_MODULE,.open    = led_open,.release = led_release,
};static int __init led_init(void)
{int ret;// 1. 申请驱动结构体内存led_device = kzalloc(sizeof(*led_device), GFP_KERNEL);if (!led_device)return -ENOMEM;// 2. 动态注册主设备号(major=0 表示动态分配)ret = register_chrdev(0, "led_drv", &fops);if (ret < 0) {pr_err("register_chrdev failed: %d\n", ret);goto err_register;}led_device->major = ret;led_device->devno = MKDEV(led_device->major, 0);pr_info("led: registered major=%u\n", led_device->major);// 3. 创建设备类与 /dev 节点(需要 udev 支持)led_device->cls = class_create(THIS_MODULE, "led_cls");if (IS_ERR(led_device->cls)) {ret = PTR_ERR(led_device->cls);pr_err("class_create failed: %d\n", ret);goto err_class;}led_device->dev = device_create(led_device->cls, NULL,led_device->devno, NULL, "led");if (IS_ERR(led_device->dev)) {ret = PTR_ERR(led_device->dev);pr_err("device_create failed: %d\n", ret);goto err_device;}// 4. 初始化硬件:申请 GPIO 并设为输出(这里不要立即 free)led_device->gpio_led = 55; // 示例:GPIO1_C7ret = gpio_request(led_device->gpio_led, "led_pin");if (ret) {pr_err("gpio_request failed: %d\n", ret);goto err_gpio;}gpio_direction_output(led_device->gpio_led, 0); // 默认熄灭pr_info("led: init ok (register_chrdev path)\n");return 0;err_gpio:device_destroy(led_device->cls, led_device->devno);
err_device:class_destroy(led_device->cls);
err_class:unregister_chrdev(led_device->major, "led_drv");
err_register:kfree(led_device);return ret;
}static void __exit led_exit(void)
{// 释放 GPIOgpio_set_value(led_device->gpio_led, 0);gpio_free(led_device->gpio_led);// 销毁设备节点与类device_destroy(led_device->cls, led_device->devno);class_destroy(led_device->cls);// 注销动态分配的 major(与 register_chrdev 配套)unregister_chrdev(led_device->major, "led_drv");kfree(led_device);pr_info("led: exit ok (register_chrdev path)\n");
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("example");
MODULE_DESCRIPTION("LED driver using register_chrdev()");

用户态测试程序(简单打开 /dev/led)

// test_open.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>int main(void)
{int fd = open("/dev/led", O_RDONLY);if (fd < 0) {perror("open /dev/led");return 1;}sleep(3); // 打开 3 秒close(fd);return 0;
}

3. 方法二:alloc_chrdev_region() + cdev —— 工程化、推荐(支持扩展)

要点

  • alloc_chrdev_region(&devno, firstminor, count, name):动态分配一段连续设备号,firstminor 表示请求的起始次设备号(通常设为 0),count 表示要申请的数量。
  • 使用 struct cdev 绑定 fops,通过 cdev_add() 将该 cdev 注册到内核并与设备号关联。
  • 推荐在正式驱动中使用该方法,扩展性好、规范性强。

完整内核模块示例(含 write/copy_from_user)

// file: led_cdev.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>struct rk_led {dev_t devno;           // 分配到的起始 devunsigned int major;struct class *cls;struct device *dev;struct cdev cdev;      // 嵌入式 cdev(不需要 cdev_alloc)int gpio_led;int on;
};static struct rk_led *led_device;/* open - 不对 gpio 做太多操作,点亮/熄灭放到 write */
static int led_open(struct inode *inode, struct file *filp)
{pr_info("led_cdev: open\n");return 0;
}/* release - 确保设备关闭时熄灭 */
static int led_release(struct inode *inode, struct file *filp)
{gpio_direction_output(led_device->gpio_led, 0);pr_info("led_cdev: release -> OFF\n");return 0;
}/* write - 从用户空间读入整数(0/1),控制 LED */
static ssize_t led_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{int on = 0;if (count < sizeof(on))return -EINVAL;if (copy_from_user(&on, buf, sizeof(on)))return -EFAULT;led_device->on = on ? 1 : 0;gpio_direction_output(led_device->gpio_led, led_device->on);pr_info("led_cdev: write on=%d\n", led_device->on);return sizeof(on);
}static const struct file_operations fops = {.owner   = THIS_MODULE,.open    = led_open,.write   = led_write,.release = led_release,
};static int __init led_init(void)
{int ret;led_device = kzalloc(sizeof(*led_device), GFP_KERNEL);if (!led_device)return -ENOMEM;// 1) 动态申请设备号(申请 1 个,从 minor 0 开始)ret = alloc_chrdev_region(&led_device->devno, 0, 1, "led_drv");if (ret) {pr_err("alloc_chrdev_region failed: %d\n", ret);goto err_alloc;}led_device->major = MAJOR(led_device->devno);pr_info("led_cdev: got major=%u minor=%u\n",led_device->major, MINOR(led_device->devno));// 2) 初始化并注册 cdev(嵌入式 cdev)cdev_init(&led_device->cdev, &fops);led_device->cdev.owner = THIS_MODULE;ret = cdev_add(&led_device->cdev, led_device->devno, 1);if (ret) {pr_err("cdev_add failed: %d\n", ret);goto err_cdev_add;}// 3) 创建设备类与 /dev 节点led_device->cls = class_create(THIS_MODULE, "led_cls");if (IS_ERR(led_device->cls)) {ret = PTR_ERR(led_device->cls);pr_err("class_create failed: %d\n", ret);goto err_class;}led_device->dev = device_create(led_device->cls, NULL,led_device->devno, NULL, "led");if (IS_ERR(led_device->dev)) {ret = PTR_ERR(led_device->dev);pr_err("device_create failed: %d\n", ret);goto err_device;}// 4) 申请并配置 GPIO(不要立即 free)led_device->gpio_led = 13; // 示例:GPIO0_B5ret = gpio_request(led_device->gpio_led, "led_pin");if (ret) {pr_err("gpio_request failed: %d\n", ret);goto err_gpio;}gpio_direction_output(led_device->gpio_led, 0);pr_info("led_cdev: init ok\n");return 0;err_gpio:device_destroy(led_device->cls, led_device->devno);
err_device:class_destroy(led_device->cls);
err_class:cdev_del(&led_device->cdev);
err_cdev_add:unregister_chrdev_region(led_device->devno, 1);
err_alloc:kfree(led_device);return ret;
}static void __exit led_exit(void)
{// 释放 GPIOgpio_set_value(led_device->gpio_led, 0);gpio_free(led_device->gpio_led);// 销毁设备与类device_destroy(led_device->cls, led_device->devno);class_destroy(led_device->cls);// 删除 cdev 并释放设备号cdev_del(&led_device->cdev);unregister_chrdev_region(led_device->devno, 1);kfree(led_device);pr_info("led_cdev: exit ok\n");
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("example");
MODULE_DESCRIPTION("LED driver using alloc_chrdev_region + cdev");

用户态测试程序(写 0/1 控制 LED)

// test_write.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>int main(void)
{int fd = open("/dev/led", O_WRONLY);if (fd < 0) {perror("open /dev/led");return 1;}int on = 1;write(fd, &on, sizeof(on));sleep(1);on = 0;write(fd, &on, sizeof(on));close(fd);return 0;
}

4. 方法三:多设备(多个 minor) + iminor()/imajor() —— 一个驱动管多实例

要点

  • 使用 alloc_chrdev_region(&dev, firstminor, count, name) 申请一段连续的 count 个设备号(从 firstminor 开始)。
  • 使用 cdev_add(&cdev, dev0, count) 将这段连续 minor 全部注册给同一个 fops
  • open() 中用 iminor(inode) 获取次设备号,根据次设备号选择具体硬件(不同 gpio)。
  • 通过 device_create() 为每个 minor 创建一个 /dev/ledX 设备节点,用户空间分别操作各个实例。

完整示例(管理 4 个 LED)

// file: led_multi.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>#define LED_CNT   4      // 管理 LED 设备数量
#define BASE_MINOR 0     // 起始 minor,可以设为 0// 示例的 GPIO 表:请根据实际板卡修改
static int gpio_table[LED_CNT] = {55, 13, 54, 56};struct rk_led_ctrl {dev_t base_dev;      // alloc_chrdev_region 返回的 devunsigned int major;struct class *cls;struct cdev cdev;    // 嵌入式 cdev
};static struct rk_led_ctrl *g_ctrl;static int led_open(struct inode *inode, struct file *filp)
{int minor = iminor(inode);      // 真实 minor(如 0、1、2、3)int idx = minor - BASE_MINOR;   // 转换为 0..LED_CNT-1 索引if (idx < 0 || idx >= LED_CNT)return -ENODEV;// 将 idx 存到 filp->private_data 中,方便 write/release 使用filp->private_data = (void *)(long)idx;pr_info("led_multi: open minor=%d idx=%d gpio=%d\n",minor, idx, gpio_table[idx]);return 0;
}static int led_release(struct inode *inode, struct file *filp)
{int idx = (int)(long)filp->private_data;// 关闭设备时把对应 LED 关掉gpio_direction_output(gpio_table[idx], 0);pr_info("led_multi: release idx=%d -> OFF\n", idx);return 0;
}static ssize_t led_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{int idx = (int)(long)filp->private_data;int on = 0;if (count < sizeof(on))return -EINVAL;if (copy_from_user(&on, buf, sizeof(on)))return -EFAULT;gpio_direction_output(gpio_table[idx], on ? 1 : 0);pr_info("led_multi: write idx=%d on=%d\n", idx, on);return sizeof(on);
}static const struct file_operations fops = {.owner   = THIS_MODULE,.open    = led_open,.write   = led_write,.release = led_release,
};static int __init led_multi_init(void)
{int ret, i;dev_t dev0;g_ctrl = kzalloc(sizeof(*g_ctrl), GFP_KERNEL);if (!g_ctrl)return -ENOMEM;// 1) 申请 LED_CNT 个连续设备号(从 BASE_MINOR 开始)ret = alloc_chrdev_region(&g_ctrl->base_dev, BASE_MINOR, LED_CNT, "led_multi");if (ret) {pr_err("alloc_chrdev_region failed: %d\n", ret);goto err_alloc;}g_ctrl->major = MAJOR(g_ctrl->base_dev);dev0 = MKDEV(g_ctrl->major, BASE_MINOR);pr_info("led_multi: major=%d minors=%d..%d\n", g_ctrl->major, BASE_MINOR, BASE_MINOR + LED_CNT - 1);// 2) 初始化并添加 cdev (一次性绑定整个区间)cdev_init(&g_ctrl->cdev, &fops);g_ctrl->cdev.owner = THIS_MODULE;ret = cdev_add(&g_ctrl->cdev, dev0, LED_CNT);if (ret) {pr_err("cdev_add failed: %d\n", ret);goto err_cdev;}// 3) 创建设备类与多个 /dev/ledX 节点g_ctrl->cls = class_create(THIS_MODULE, "led_cls");if (IS_ERR(g_ctrl->cls)) {ret = PTR_ERR(g_ctrl->cls);pr_err("class_create failed: %d\n", ret);goto err_class;}for (i = 0; i < LED_CNT; i++) {device_create(g_ctrl->cls, NULL,MKDEV(g_ctrl->major, BASE_MINOR + i),NULL, "led%d", i);}// 4) 申请并设置所有 GPIO(按表格)for (i = 0; i < LED_CNT; i++) {ret = gpio_request(gpio_table[i], "led_pin");if (ret) {pr_err("gpio_request[%d]=%d failed\n", i, ret);goto err_gpio;}gpio_direction_output(gpio_table[i], 0);}pr_info("led_multi: init ok\n");return 0;err_gpio:while (--i >= 0)gpio_free(gpio_table[i]);for (i = 0; i < LED_CNT; i++)device_destroy(g_ctrl->cls, MKDEV(g_ctrl->major, BASE_MINOR + i));class_destroy(g_ctrl->cls);
err_class:cdev_del(&g_ctrl->cdev);
err_cdev:unregister_chrdev_region(g_ctrl->base_dev, LED_CNT);
err_alloc:kfree(g_ctrl);return ret;
}static void __exit led_multi_exit(void)
{int i;for (i = 0; i < LED_CNT; i++) {gpio_set_value(gpio_table[i], 0);gpio_free(gpio_table[i]);}for (i = 0; i < LED_CNT; i++)device_destroy(g_ctrl->cls, MKDEV(g_ctrl->major, BASE_MINOR + i));class_destroy(g_ctrl->cls);cdev_del(&g_ctrl->cdev);unregister_chrdev_region(g_ctrl->base_dev, LED_CNT);kfree(g_ctrl);pr_info("led_multi: exit ok\n");
}module_init(led_multi_init);
module_exit(led_multi_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("example");
MODULE_DESCRIPTION("LED multi-device driver (multiple minors)");

用户态测试(分别对 /dev/led0、/dev/led1 写 0/1)

// test_multi.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>int main(void)
{int fd0 = open("/dev/led0", O_WRONLY);if (fd0 < 0) { perror("open led0"); return 1; }int on = 1;write(fd0, &on, sizeof(on));sleep(1);on = 0;write(fd0, &on, sizeof(on));close(fd0);return 0;
}

“如果想在没有硬件的情况下练习设备号与多实例驱动,可以用 方法四:kfifo + 多 minor。它和方法三结构几乎一样,只是实例换成了内存 FIFO。”

5. 方法四:kfifo + 多个 minor —— 虚拟字符设备(无硬件依赖)

要点

  • 和方法三一样,使用 alloc_chrdev_region() 分配一段连续设备号;
  • 每个 minor 对应一个 独立的 kfifo 缓冲区,用来存储用户态写入的数据;
  • 用户态对不同的 /dev/virtdevX 节点进行读写,就像操作多个独立的管道;
  • 特别适合用来做 驱动开发练习调试 IPC 通道教学示例,因为它不依赖真实硬件。

完整内核模块示例(每个 minor 一个 kfifo)

// file: virt_kfifo.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/kfifo.h>#define DEV_CNT   4        // 创建多少个虚拟设备
#define BASE_MINOR 0       // 起始 minor
#define FIFO_SIZE 128      // 每个 kfifo 的大小(字节)struct virt_dev {struct kfifo fifo;     // 每个设备独立的 FIFO 缓冲区struct device *dev;    // 对应 /dev 节点
};struct virt_ctrl {dev_t base_dev;        // 起始设备号unsigned int major;struct class *cls;struct cdev cdev;      // 一个 cdev 管理多个 minorstruct virt_dev *vdevs;
};static struct virt_ctrl *g_ctrl;/* open - 保存当前 minor 到 filp->private_data */
static int virt_open(struct inode *inode, struct file *filp)
{int minor = iminor(inode);    // 获取当前打开的次设备号int idx = minor - BASE_MINOR;if (idx < 0 || idx >= DEV_CNT)return -ENODEV;filp->private_data = &g_ctrl->vdevs[idx];pr_info("virt_kfifo: open minor=%d\n", minor);return 0;
}/* release - 无需特别操作 */
static int virt_release(struct inode *inode, struct file *filp)
{pr_info("virt_kfifo: release\n"); //这里只做演示,实际可能需要清空 FIFOreturn 0;
}/* write - 将用户态数据写入 kfifo */
static ssize_t virt_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{struct virt_dev *vdev = filp->private_data;unsigned int copied;int ret;if (kfifo_is_full(&vdev->fifo)) {pr_warn("virt_kfifo: fifo full, dropping data\n");return -ENOSPC;}ret = kfifo_from_user(&vdev->fifo, buf, count, &copied);if (ret)return ret;pr_info("virt_kfifo: write %u bytes\n", copied);return copied;
}/* read - 从 kfifo 中取数据返回给用户态 */
static ssize_t virt_read(struct file *filp, char __user *buf,size_t count, loff_t *ppos)
{struct virt_dev *vdev = filp->private_data;unsigned int copied;int ret;if (kfifo_is_empty(&vdev->fifo)) {pr_info("virt_kfifo: fifo empty\n");return 0; // EOF}ret = kfifo_to_user(&vdev->fifo, buf, count, &copied);if (ret)return ret;pr_info("virt_kfifo: read %u bytes\n", copied);return copied;
}static const struct file_operations fops = {.owner   = THIS_MODULE,.open    = virt_open,.release = virt_release,.read    = virt_read,.write   = virt_write,
};/* 模块加载 */
static int __init virt_init(void)
{int ret, i;dev_t dev0;g_ctrl = kzalloc(sizeof(*g_ctrl), GFP_KERNEL);if (!g_ctrl)return -ENOMEM;/* 1) 分配设备号 */ret = alloc_chrdev_region(&g_ctrl->base_dev, BASE_MINOR, DEV_CNT, "virt_kfifo");if (ret)goto err_alloc;g_ctrl->major = MAJOR(g_ctrl->base_dev);dev0 = MKDEV(g_ctrl->major, BASE_MINOR);pr_info("virt_kfifo: major=%d minors=%d..%d\n", g_ctrl->major,BASE_MINOR, BASE_MINOR + DEV_CNT - 1);/* 2) 注册 cdev */cdev_init(&g_ctrl->cdev, &fops);g_ctrl->cdev.owner = THIS_MODULE;ret = cdev_add(&g_ctrl->cdev, dev0, DEV_CNT);if (ret)goto err_cdev;/* 3) 创建设备类 */g_ctrl->cls = class_create(THIS_MODULE, "virt_cls");if (IS_ERR(g_ctrl->cls)) {ret = PTR_ERR(g_ctrl->cls);goto err_class;}/* 4) 初始化每个设备的 kfifo 与 /dev 节点 */g_ctrl->vdevs = kcalloc(DEV_CNT, sizeof(struct virt_dev), GFP_KERNEL);if (!g_ctrl->vdevs) {ret = -ENOMEM;goto err_vdevs;}for (i = 0; i < DEV_CNT; i++) {ret = kfifo_alloc(&g_ctrl->vdevs[i].fifo, FIFO_SIZE, GFP_KERNEL);if (ret) {pr_err("virt_kfifo: kfifo_alloc[%d] failed\n", i);goto err_fifo;}g_ctrl->vdevs[i].dev = device_create(g_ctrl->cls, NULL,MKDEV(g_ctrl->major, BASE_MINOR + i),NULL, "virtdev%d", i);}pr_info("virt_kfifo: init ok\n");return 0;err_fifo:while (--i >= 0)kfifo_free(&g_ctrl->vdevs[i].fifo);kfree(g_ctrl->vdevs);
err_vdevs:class_destroy(g_ctrl->cls);
err_class:cdev_del(&g_ctrl->cdev);
err_cdev:unregister_chrdev_region(g_ctrl->base_dev, DEV_CNT);
err_alloc:kfree(g_ctrl);return ret;
}/* 模块卸载 */
static void __exit virt_exit(void)
{int i;for (i = 0; i < DEV_CNT; i++) {device_destroy(g_ctrl->cls, MKDEV(g_ctrl->major, BASE_MINOR + i));kfifo_free(&g_ctrl->vdevs[i].fifo);}kfree(g_ctrl->vdevs);class_destroy(g_ctrl->cls);cdev_del(&g_ctrl->cdev);unregister_chrdev_region(g_ctrl->base_dev, DEV_CNT);kfree(g_ctrl);pr_info("virt_kfifo: exit ok\n");
}module_init(virt_init);
module_exit(virt_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("example");
MODULE_DESCRIPTION("Virtual multi-device driver using kfifo");

用户态测试程序

// test_kfifo.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main(void)
{int fd0 = open("/dev/virtdev0", O_RDWR);int fd1 = open("/dev/virtdev1", O_RDWR);if (fd0 < 0 || fd1 < 0) {perror("open virtdev");return 1;}const char *msg = "hello fifo!";char buf[64] = {0};// 写入 virtdev0write(fd0, msg, strlen(msg));// 从 virtdev0 读回read(fd0, buf, sizeof(buf));printf("read from virtdev0: %s\n", buf);// 写入 virtdev1write(fd1, "abc123", 6);memset(buf, 0, sizeof(buf));read(fd1, buf, sizeof(buf));printf("read from virtdev1: %s\n", buf);close(fd0);close(fd1);return 0;
}
  • 方法四本质上还是 方法三(多 minor 管理多个实例) 的扩展;
  • 区别在于:这里的“实例”不是实际硬件,而是 kfifo 缓冲区
  • 这样,/dev/virtdev0/dev/virtdev1 … 就像多个独立的管道,读写互不干扰;

6. 编译、加载、测试(Makefile 与基本命令)

Makefile(通用内核模块编译)

# Makefile
obj-m := led_register.o  # 或 led_cdev.o / led_multi.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)all:$(MAKE) -C $(KDIR) M=$(PWD) modulesclean:$(MAKE) -C $(KDIR) M=$(PWD) clean

加载模块与调试

# 编译
make# 以 root 加载模块
sudo insmod led_cdev.ko     # 或 led_register.ko / led_multi.ko# 看 dmesg 输出,确认 major/minor 及初始化信息
dmesg | tail -n 20# 查看 /dev 是否生成节点(若无 udev,需手动 mknod)
ls -l /dev/led*
# 若未生成,可手动:
# sudo mknod /dev/led c <major> <minor>
# sudo chmod 666 /dev/led# 测试(用户态程序)
gcc -o test_write test_write.c
./test_write# 卸载模块
sudo rmmod led_cdev
dmesg | tail -n 20

7. 常见坑与调试清单(务必注意)

  1. 在 init 中立刻 gpio_free():会导致后续 open/write 访问 GPIO 时没有权限或行为异常。应在模块卸载(exit)中释放 GPIO。
  2. 忘了设置 fops.owner = THIS_MODULE:会导致模块在被打开时引用计数不正确,可能阻止卸载。
  3. copy_from_user 参数和长度write() 中应严格制约拷贝长度,避免把用户缓冲区全部拷入内核。
  4. device_create() 后没有 /dev 节点:通常是因为 udev 未运行或规则延迟。可用 mknod 手动创建。
  5. 资源回滚要严格init 中任一步失败,都必须回退前面成功分配的资源,避免内核资源泄漏(GPIO、cdev、device/class、devno、kmalloc)。代码示例中给出了清理路径。
  6. GPIO 编号与设备树不一致:生产驱动建议通过设备树(DT)或 gpiod API 获取 GPIO,避免魔法数字。
  7. 并发保护:若多个进程/CPU 同时写 LED,视需求加锁(spinlock / mutex)以保证状态一致;但对简单 LED 写操作通常足够轻量。
  8. LED 极性:板上 LED 有可能是低电平点亮(active-low),测试时注意电平与电路关系。

8. 总结与推荐

方法接口是否用 cdev是否支持多实例适用场景
方法一register_chrdev简单实验、快速验证
方法二alloc_chrdev_region + cdev支持(扩展)正式驱动,推荐
方法三多 minor + imajor/iminor一个驱动管多个硬件
方法四kfifo + 多 minor虚拟设备、IPC、练习
  • register_chrdev():实现极简、适合单设备验证,但不推荐用于生产驱动。
  • alloc_chrdev_region() + cdev:规范、工程化,支持扩展,推荐作为首选实现方式。
  • 多实例(多个 minor) + iminor():当一个驱动管理多个硬件实例(如多个 LED、多个传感器)时,使用连续 minor 并在 open() 中区分是最佳实践。

实现驱动时,务必保证资源申请与释放路径对称、避免在 init 中提前释放硬件资源、并在 fops 中正确使用 THIS_MODULE。生产环境下建议结合设备树(或 platform data / of_device)来确定 GPIO 与设备号配置,减少魔法数字与板卡差异带来的问题。


(完)

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

相关文章:

  • 【CMake】Ctest,Cpack
  • 电子电气架构 --- 智能电动车EEA电子电气架构(上)
  • Linux | 走进网络世界:MAC、IP 与通信的那些事
  • 【macOS】垃圾箱中文件无法清理的--特殊方法
  • 深度学习跨领域应用探索:从技术落地到行业变革
  • 华为eNSP防火墙综合网络结构训练.docx
  • npm 打包上传命令,撤销错误版本
  • 山东省信息技术应用创新开展进程(一)
  • 设计模式13-迭代器模式
  • OS+MySQL+(其他)八股小记
  • 【lucene】 中的impactsenum与impactsdisi有啥区别?
  • 开源npm引导guide组件
  • 基于.NET Framework 4.0的FTP文件传输类
  • 基于Hadoop的可视化城市宜居指数分析(代码+数据库+LW)
  • 【macOS】垃圾箱中文件无法清理的常规方法
  • Mac上如何安装mysql
  • MIT 6.5840 (Spring, 2024) 通关指南——Lab 2: Key/Value Server
  • 【Docker】Docker容器和镜像管理常用命令
  • Spring Bean 生命周期中的 @PostConstruct 注解
  • TCP实现线程池竞争任务
  • LeetCode Hot 100 Python (31~40)
  • 运动规划实战案例 | 基于行人社交模型的移动机器人动态避障(附ROS C++仿真)
  • Linux Tun/Tap 多队列技术
  • 【STM32】贪吃蛇 [阶段2](嵌入式进阶方向)
  • 【含文档+PPT+源码】基于SpringBoot+微信小程序的饮水健康之净水器保养管理系统设计与实现【包运行成功】
  • 【Linux】模拟实现Shell(下)
  • 打开模板打印
  • Ajax笔记(下)
  • 《探索C++11:现代C++语法的性能革新(上篇)》
  • 医疗AI时代的生物医学Go编程:高性能计算与精准医疗的案例分析(八)