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.pswp.cn/diannao/97575.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/97575.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/97575.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

2025年AI智能體開源技術棧全面解析:從基礎框架到垂直應用

2025年&#xff0c;開源AI智能體技術正以前所未有的速度重塑人工智能領域&#xff0c;從單一任務處理到復雜多智能體協作&#xff0c;開源生態已成為技術創新的核心驅動力。一、開源AI智能體生態概述 1.1 技術演進與發展歷程 AI智能體技術經歷了從規則式智能體&#xff08;2015…

Empire: LupinOne靶場滲透

Empire: LupinOne 來自 <https://www.vulnhub.com/entry/empire-lupinone,750/#top> 1&#xff0c;將兩臺虛擬機網絡連接都改為NAT模式 2&#xff0c;攻擊機上做namp局域網掃描發現靶機 nmap -sn 192.168.23.0/24 那么攻擊機IP為192.168.23.128&#xff0c;靶場IP192.16…

飛騰2000+/64核 PCIE掃描異常問題排查

1、背景介紹近期項目中采用全國產飛騰計算模塊搭配一塊FPGA模塊&#xff08;FPGA為復旦微的VU9P&#xff09;&#xff0c;實現業務數據的收發。FPGA中采用了Xilinx的XDMA IP核&#xff0c;飛騰計算模塊中的FT2000/64核處理器通過PEU1的一路 PCIE3.0x8與VU9P相連接&#xff0c;發…

證明與激勵:Walrus 可編程數據如何通過激勵可用性證明獲得安全性

Walrus 的可用性證明&#xff08;Proof of Availability&#xff0c;PoA&#xff09; 是部署在 Sui 上的鏈上憑證&#xff0c;它為數據托管創建了一個可驗證的公開記錄&#xff0c;并作為存儲服務正式啟動的標志。PoA 中的“激勵”來自一個健全的經濟框架&#xff1a;存儲節點需…

云存儲(參考自騰訊云計算工程師認證)

目錄 存儲基礎知識&#xff1a; RAID&#xff1a; 云存儲概述&#xff1a; 云存儲產品&#xff1a; CBS&#xff1a; CFS文件存儲&#xff1a; COS對象存儲&#xff1a; 云存儲安全&#xff1a; 存儲基礎知識&#xff1a; 機械硬盤&#xff1a;HDD&#xff0c;即傳統硬…

面試tips--JVM(2)--對象創建的過程

一、創建對象的完整過程1. 類加載檢查JVM 遇到 new 指令時&#xff0c;首先去檢查這個類 User 是否已經被加載、解析和初始化過。如果沒有&#xff0c;就先執行 類加載過程&#xff08;加載 .class 文件到方法區/元空間、創建 Class 對象等&#xff09;。【這個過程就是加載、驗…

【Web安全】CRLF注入攻擊深度解析:原理、場景與安全測試防御指南

文章目錄前言&#xff1a;為什么CRLF注入是安全測試不可忽視的威脅&#xff1f;1. CRLF注入核心原理&#xff1a;從字符定義到協議依賴1.1 什么是CRLF&#xff1f;1.2 CRLF在HTTP協議中的關鍵作用1.3 CRLF注入的本質&#xff1a;格式混淆攻擊2. CRLF注入典型利用場景與安全測試…

【安全學習】DVWA 靶場 SQL 注入漏洞原理分析與防御策略(教育用途)

注意&#xff1a;本文內容僅用于合法授權的安全研究、教學演示及漏洞復現&#xff0c;嚴禁用于任何未授權的系統或網絡環境。 所有操作需在本地沙箱或個人可控靶場中執行&#xff0c;切勿對生產環境、他人系統進行測試&#xff0c;非法使用后果自負。&#x1f4cc; 法律與道德雙…

Langflow Memory 技術深度分析

Langflow Memory 技術深度分析 1. Memory 技術概述和設計理念 1.1 技術概述 Langflow 的 Memory 系統是一個多層次的記憶管理框架&#xff0c;專門設計用于處理對話歷史、上下文狀態和會話數據的存儲與檢索。該系統采用了分層架構設計&#xff0c;支持多種記憶類型和存儲后端&a…

從0開始搭建一個前端項目(vue + vite + less + typescript)

版本 node&#xff1a;v22.17.1 pnpm&#xff1a;v10.13.1 vue&#xff1a;^3.5.18 vite&#xff1a;^7.0.6 typescipt&#xff1a;~5.8.0腳手架初始化vue pnpm create vuelatest只選擇&#xff1a; TypeScript, JSX 3. 用vscode打開創建的項目&#xff0c;并刪除多余的代碼esl…

(十)ps識別:Swin Transformer-T 與 ResNet50 結合的 PS 痕跡識別模型訓練過程解析

Swin Transformer-T 與 ResNet50 結合的 PS 痕跡識別模型 思路分析模型融合思路&#xff1a; 利用ResNet50提取圖像的局部紋理和邊緣特征&#xff0c;這對檢測篡改區域的細微變化非常重要利用Swin Transformer-T捕捉全局上下文信息和長距離依賴關系&#xff0c;有助于理解圖像整…

[ICCV25]TRACE:用3D高斯直接學習物理參數,讓AI“推演”未來場景

導讀在復雜的動態世界中&#xff0c;讓機器人既能看懂場景&#xff0c;又能預測未來變化&#xff0c;是一項極具挑戰性的任務。過去的方法往往依賴人工標注或簡化的物理模型&#xff0c;卻難以真正捕捉物體運動的規律。TRACE 提出了一個全新的思路&#xff1a;把三維場景中的每…

電商數據開發實踐:深度剖析1688商品詳情 API 的技術與應用

在電商行業數字化轉型的進程中&#xff0c;數據獲取與處理的效率和準確性&#xff0c;直接影響著企業的競爭力。作為開發者&#xff0c;相信大家都遇到過這類棘手問題&#xff1a;在構建時&#xff0c;因數據不一致導致采購決策失誤&#xff1b;使用傳統&#xff0c;又常遭遇電…

Docker 詳解+示例(部署Kafka鏡像容器)

介 紹Docker 是一個開源的容器化平臺&#xff0c;它的核心目標是解決 “軟件在不同環境下運行不一致” 的問題&#xff0c;實現 “一次構建&#xff0c;到處運行” 。它基于 Linux 內核的底層技術&#xff0c;將應用程序及其依賴&#xff08;如庫文件、配置、運行環境等&#x…

SciPy科學計算與應用:SciPy應用實戰-數據分析與工程計算

SciPy案例研究&#xff1a;從理論到實踐 學習目標 通過本課程&#xff0c;學員將了解一系列實際案例&#xff0c;深入探討SciPy庫在數據分析、物理模擬和工程計算中的應用。同時學員將學習如何利用SciPy解決實際問題&#xff0c;加深對SciPy各個模塊的理解和應用能力。 相關知識…

React學習教程,從入門到精通, ReactJS - 架構(6)

ReactJS - 架構 React應用的架構 React的架構就像一個井然有序的廚房&#xff0c;每個工具都有其特定的位置和用途。在其核心&#xff0c;React遵循一個基于組件的架構&#xff0c;這意味著我們使用可重用的組件構建應用程序。 組件&#xff1a;構建塊 可以把組件想象成樂高積木…

Bias / variance and neural networks|偏差/方差和神經網絡

----------------------------------------------------------------------------------------------- 這是我在我的網站中截取的文章&#xff0c;有更多的文章歡迎來訪問我自己的博客網站rn.berlinlian.cn&#xff0c;這里還有很多有關計算機的知識&#xff0c;歡迎進行留言或…

Linux HMM(Heterogeneous Memory Management)的應用

原理篇見【https://blog.csdn.net/shenjunpeng/article/details/150931847?spm1011.2415.3001.5331】 1. HMM 的優勢與挑戰 1.1 優勢 統一虛擬地址空間&#xff1a;簡化異構計算平臺的數據共享和訪問。 高效頁表同步&#xff1a;支持設備端的 page fault 和頁表同步&#x…

鴻蒙創新賽活動——Mac提交壓縮失敗后續

Mac提交壓縮失敗后續來了… 傳送帶【上一篇】 背景 華為2025HarmonyOS創新賽 上傳作品的時候&#xff0c;遇到了一個提示 ZIP包中的Office文件含有嵌入文件&#xff0c;就去這個Office文件找&#xff0c;怎么也找不到嵌入的文件。 解決方法1 上次推薦的解決方式是&#xff0…

Ubuntu操作系統下使用mysql、mongodb、redis

目錄 一、核心步驟概覽 二. MySQL &#xff08;下面以其他用戶為例&#xff09; 1,、安裝 2、管理服務 3、連接與使用 4、配置文件位置 5、下面來演示一下安裝好之后如何在Linux操作系統中遠程登錄和window互連Linux 遠程登錄 window連Linux&#xff08;連不上的&…