writing-char-drivers

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Writing Character Drivers

编写字符驱动

Purpose

目的

Guide agents through Linux character device implementation:
struct file_operations
,
cdev
registration, safe userspace copies,
ioctl
design, and basic
mmap
— focused depth beyond
skills/kernel/device-drivers
.
指导Agent完成Linux字符设备的实现:
struct file_operations
cdev
注册、安全的用户空间拷贝、
ioctl
设计以及基础
mmap
——相比
skills/kernel/device-drivers
更具深度。

When to Use

适用场景

  • Exposing hardware to
    /dev/mydev
  • Implementing
    read
    /
    write
    /
    poll
    from kernel
  • Defining
    ioctl
    commands with type-safe macros
  • Mapping device MMIO to userspace (carefully)
  • 将硬件暴露到
    /dev/mydev
  • 在内核中实现
    read
    /
    write
    /
    poll
  • 使用类型安全宏定义
    ioctl
    命令
  • (谨慎地)将设备MMIO映射到用户空间

Workflow

工作流程

1. Char device registration

1. 字符设备注册

c
#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;
}
Modern drivers often use
devm_*
variants inside
probe
.
c
#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. Safe userspace I/O

2. 安全的用户空间I/O

c
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;
}
Never dereference
__user
pointers directly.
c
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 pattern

3. ioctl模式

c
#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;
    }
}
Use
_IOWR
with fixed-size structs; prefer
compat_ioctl
on bi-arch.
c
#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 (device memory)

4. mmap(设备内存)

c
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);
}
Prefer
mmap
of DMA buffers only with explicit size limits and permission checks.
c
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);
}
仅在明确限制大小并进行权限检查的情况下,优先对DMA缓冲区使用
mmap

5. poll / async I/O

5. poll / 异步I/O

Implement
poll
+
wake_up_interruptible
for blocking reads; use
fasync_helper
for SIGIO.
为阻塞式读取实现
poll
+
wake_up_interruptible
;为SIGIO使用
fasync_helper

6. Agent usage

6. Agent使用方式

/writing-char-drivers Add unlocked_ioctl SET_SPEED to existing platform driver
/writing-char-drivers Add unlocked_ioctl SET_SPEED to existing platform driver

Common Problems

常见问题

SymptomCauseFix
-EFAULT
Bad user pointerValidate
access_ok
(older) / rely on
copy_*
ENOTTY
Wrong ioctl magicMatch userspace
ioctl.h
Major conflictStatic major takenUse
alloc_chrdev_region
mmap SIGSEGVCached mapping to device
pgprot_noncached
Sleep in ioctlHolding spinlockDrop lock before blocking
症状原因修复方案
-EFAULT
用户指针无效验证
access_ok
(旧方法)/ 依赖
copy_*
函数
ENOTTY
ioctl魔数不匹配与用户空间的
ioctl.h
保持一致
主设备号冲突静态主设备号已被占用使用
alloc_chrdev_region
mmap导致SIGSEGV设备映射使用了缓存使用
pgprot_noncached
ioctl中休眠持有自旋锁阻塞前释放锁

Related Skills

相关技能

  • skills/kernel-dev/platform-device-model
    — probe context
  • skills/kernel/device-drivers
    — full driver lifecycle
  • skills/kernel/kernel-concurrency
    — locking in file ops
  • skills/kernel-dev/kernel-debugging-advanced
    — trace ioctl path
  • skills/low-level-programming/linux-kernel-modules
    — module boilerplate
  • skills/kernel-dev/platform-device-model
    — 探测上下文
  • skills/kernel/device-drivers
    — 完整驱动生命周期
  • skills/kernel/kernel-concurrency
    — 文件操作中的锁机制
  • skills/kernel-dev/kernel-debugging-advanced
    — 追踪ioctl路径
  • skills/low-level-programming/linux-kernel-modules
    — 模块模板