mcpField
W
writing-char-drivers

writing-char-drivers

github.com/mohitmishra786开发269299 次安装18 周前更新
代码仓库

关于 writing-char-drivers

面向 Linux 字符设备实现的 Agent 技能,涵盖注册、用户空间 I/O、ioctl、mmap 和轮询,侧重实用模式与常见问题。

主要功能

  1. 01字符设备注册
  2. 02安全的用户空间数据复制
  3. 03ioctl 命令设计
  4. 04设备内存 mmap
  5. 05轮询与异步 I/O

适用场景

  1. 01通过 /dev 节点开放硬件
  2. 02实现内核 read、write 和 poll
  3. 03设计类型安全的 ioctl 接口
  4. 04在校验条件下映射设备内存

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mohitmishra786/low-level-dev-skills --skill writing-char-drivers

安装后在 Claude Code 中输入/writing-char-drivers确认已加载。

详细介绍

编写字符设备驱动

目的

指导 Agent 实现 Linux 字符设备:struct file_operations、cdev 注册、安全的用户空间数据拷贝、ioctl 设计和基础 mmap——提供超出 skills/kernel/device-drivers 的深入内容。

适用场景

  • 将硬件暴露为 /dev/mydev
  • 在内核中实现 read/write/poll
  • 使用类型安全的宏定义 ioctl 命令
  • 将设备 MMIO 映射到用户空间(需谨慎)

工作流

1. 字符设备注册
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#define MY_MAJOR 0   /* 0 = dynamic alloc */
#define MY_MINOR 0

static dev_t devno;
static struct cdev my_cdev;
static struct class *class;

static const struct file_operations my_fops = {
    .owner          = THIS_MODULE,
    .open           = my_open,
    .release        = my_release,
    .read           = my_read,
    .write          = my_write,
    .unlocked_ioctl = my_ioctl,
    .llseek         = no_llseek,
};

static int __init my_init(void)
{
    int ret = alloc_chrdev_region(&devno, MY_MINOR, 1, "mydev");
    if (ret)
        return ret;

    cdev_init(&my_cdev, &my_fops);
    ret = cdev_add(&my_cdev, devno, 1);
    if (ret)
        goto err_cdev;

    class = class_create("mydev");
    device_create(class, NULL, devno, NULL, "mydev");
    return 0;

err_cdev:
    unregister_chrdev_region(devno, 1);
    return ret;
}

现代驱动通常会在 probe 中使用 devm_* 变体。

2. 安全的用户空间 I/O
static ssize_t my_read(struct file *filp, char __user *buf,
                       size_t count, loff_t *ppos)
{
    char kbuf[128];
    ssize_t len;

    if (*ppos >= sizeof(kbuf))
        return 0;
    len = min(count, sizeof(kbuf) - *ppos);
    memcpy(kbuf, "data", 4);
    if (copy_to_user(buf, kbuf + *ppos, len))
        return -EFAULT;
    *ppos += len;
    return len;
}

切勿直接解引用 __user 指针。

3. ioctl 模式
#include <linux/ioctl.h>

#define MY_IOC_MAGIC 'k'
#define MY_IOC_RESET  _IO(MY_IOC_MAGIC, 0)
#define MY_IOC_SET    _IOW(MY_IOC_MAGIC, 1, int)

static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    switch (cmd) {
    case MY_IOC_RESET:
        return 0;
    case MY_IOC_SET: {
        int val;
        if (copy_from_user(&val, (void __user *)arg, sizeof(val)))
            return -EFAULT;
        return 0;
    }
    default:
        return -ENOTTY;
    }
}

使用 _IOWR 和固定大小的结构体;在双架构环境中优先使用 compat_ioctl。

4. mmap(设备内存)
static int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
    unsigned long size = vma->vm_end - vma->vm_start;
    phys_addr_t phys = device_phys_base;

    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
    return remap_pfn_range(vma, vma->vm_start, phys >> PAGE_SHIFT,
                           size, vma->vm_page_prot);
}

只有在明确限制大小并进行权限检查后,才使用 mmap 映射 DMA 缓冲区。

5. poll / 异步 I/O

实现 poll 和 wake_up_interruptible 以支持阻塞读取;使用 fasync_helper 处理 SIGIO。

6. Agent 使用
/writing-char-drivers Add unlocked_ioctl SET_SPEED to existing platform driver

常见问题

现象原因修复方法
-EFAULT用户指针无效验证 access_ok(旧版方式)/ 使用 copy_*
ENOTTYioctl 魔数错误与用户空间的 ioctl.h 保持一致
主设备号冲突静态主设备号已被占用使用 alloc_chrdev_region
mmap 触发 SIGSEGV设备映射采用了缓存使用 pgprot_noncached
ioctl 中休眠持有自旋锁阻塞前释放锁

相关技能

  • skills/kernel-dev/platform-device-model — probe 上下文
  • skills/kernel/device-drivers — 完整的驱动生命周期
  • skills/kernel/kernel-concurrency — 文件操作中的锁
  • skills/kernel-dev/kernel-debugging-advanced — 跟踪 ioctl 路径
  • skills/low-level-programming/linux-kernel-modules — 模块样板代码

评论

支持 Markdown