【Linux驱动开发】 Linux字符设备开发详细指南

Linux字符设备开发详细指南

目录

  1. 概述
  2. 字符设备基础概念
  3. 内核模块基础
  4. 字符设备驱动架构
  5. 设备注册与注销
  6. 基本文件操作实现
  7. 高级特性
  8. ioctl系统调用
  9. 阻塞与非阻塞操作
  10. 并发控制
  11. 中断处理
  12. 内存管理
  13. 调试技巧
  14. 完整示例
  15. 最佳实践

概述

字符设备是Linux内核中最基本的设备类型之一,与块设备不同,字符设备以字符流的方式处理数据,不支持随机访问。常见的字符设备包括串口、键盘、鼠标等。

字符设备特点

  • 按字符流顺序访问
  • 不支持随机访问(无寻道操作)
  • 通常用于数据传输量较小的设备
  • 实现相对简单

字符设备基础概念

设备号

Linux使用设备号来标识设备,包括:

  • 主设备号(Major Number):标识设备驱动程序
  • 次设备号(Minor Number):标识具体的设备实例
c 复制代码
// 设备号类型定义
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

// 从dev_t提取主次设备号
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

设备文件

字符设备通过设备文件暴露给用户空间,通常位于/dev目录下:

bash 复制代码
# 创建设备节点
mknod /dev/mydevice c 240 0
# 参数说明:c表示字符设备,240是主设备号,0是次设备号

内核模块基础

模块基本结构

c 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

static int __init my_module_init(void)
{
    printk(KERN_INFO "Module loaded successfully\n");
    return 0;
}

static void __exit my_module_exit(void)
{
    printk(KERN_INFO "Module unloaded\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device driver");

模块编译

创建Makefile:

makefile 复制代码
obj-m += my_device.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

字符设备驱动架构

核心数据结构

file_operations结构体

file_operations是字符设备驱动的核心,定义了设备支持的操作:

c 复制代码
#include <linux/fs.h>

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iterate) (struct file *, struct dir_context *);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*mremap)(struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, loff_t, loff_t, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **, void **);
    long (*fallocate)(struct file *, int, loff_t, loff_t);
    void (*show_fdinfo)(struct seq_file *, struct file *);
};
设备数据结构

定义设备私有数据结构:

c 复制代码
struct my_device_data {
    struct cdev cdev;
    char buffer[BUFFER_SIZE];
    int data_size;
    struct semaphore sem;
    wait_queue_head_t read_queue;
    wait_queue_head_t write_queue;
    struct class *dev_class;
    struct device *dev;
};

设备注册与注销

分配设备号

c 复制代码
#include <linux/fs.h>

#define DEVICE_NAME "my_char_device"
#define MAJOR_NUM 240  // 选择一个未使用的主设备号
#define MINOR_NUM 0
#define DEVICE_COUNT 1

static dev_t dev_num;
static struct cdev my_cdev;

static int __init device_init(void)
{
    int result;
    
    // 方法1:静态分配设备号
    dev_num = MKDEV(MAJOR_NUM, MINOR_NUM);
    result = register_chrdev_region(dev_num, DEVICE_COUNT, DEVICE_NAME);
    
    // 方法2:动态分配设备号(推荐)
    // result = alloc_chrdev_region(&dev_num, MINOR_NUM, DEVICE_COUNT, DEVICE_NAME);
    
    if (result < 0) {
        printk(KERN_WARNING "Can't register device number\n");
        return result;
    }
    
    return 0;
}

初始化并添加字符设备

c 复制代码
static int __init device_init(void)
{
    int result;
    struct file_operations fops = {
        .owner = THIS_MODULE,
        .open = device_open,
        .release = device_release,
        .read = device_read,
        .write = device_write,
        .unlocked_ioctl = device_ioctl,
    };
    
    // 初始化字符设备
    cdev_init(&my_cdev, &fops);
    my_cdev.owner = THIS_MODULE;
    
    // 添加字符设备到内核
    result = cdev_add(&my_cdev, dev_num, DEVICE_COUNT);
    if (result) {
        printk(KERN_WARNING "Error adding character device\n");
        unregister_chrdev_region(dev_num, DEVICE_COUNT);
        return result;
    }
    
    return 0;
}

创建设备文件(自动)

c 复制代码
#include <linux/device.h>

static struct class *my_class;

static int __init device_init(void)
{
    // ... 前面的初始化代码 ...
    
    // 创建设备类
    my_class = class_create(THIS_MODULE, DEVICE_NAME);
    if (IS_ERR(my_class)) {
        cdev_del(&my_cdev);
        unregister_chrdev_region(dev_num, DEVICE_COUNT);
        return PTR_ERR(my_class);
    }
    
    // 创建设备文件
    device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME);
    
    return 0;
}

static void __exit device_exit(void)
{
    device_destroy(my_class, dev_num);
    class_destroy(my_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev_num, DEVICE_COUNT);
}

基本文件操作实现

open和release操作

c 复制代码
static int device_open(struct inode *inode, struct file *filp)
{
    struct my_device_data *dev;
    
    // 获取设备数据
    dev = container_of(inode->i_cdev, struct my_device_data, cdev);
    
    // 保存设备数据到file结构
    filp->private_data = dev;
    
    // 增加模块引用计数
    try_module_get(THIS_MODULE);
    
    printk(KERN_INFO "Device opened\n");
    return 0;
}

static int device_release(struct inode *inode, struct file *filp)
{
    // 减少模块引用计数
    module_put(THIS_MODULE);
    
    printk(KERN_INFO "Device closed\n");
    return 0;
}

read操作

c 复制代码
static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    struct my_device_data *dev = filp->private_data;
    ssize_t retval = 0;
    
    // 获取信号量
    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;
    
    // 等待数据可用
    while (dev->data_size == 0) {
        up(&dev->sem);
        
        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN;
        
        if (wait_event_interruptible(dev->read_queue, dev->data_size > 0))
            return -ERESTARTSYS;
        
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
    }
    
    // 计算可读数据量
    if (count > dev->data_size)
        count = dev->data_size;
    
    // 复制数据到用户空间
    if (copy_to_user(buf, dev->buffer, count)) {
        retval = -EFAULT;
        goto out;
    }
    
    // 移动缓冲区数据
    memcpy(dev->buffer, dev->buffer + count, dev->data_size - count);
    dev->data_size -= count;
    retval = count;
    
    // 唤醒等待写入的进程
    wake_up_interruptible(&dev->write_queue);
    
out:
    up(&dev->sem);
    return retval;
}

write操作

c 复制代码
static ssize_t device_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    struct my_device_data *dev = filp->private_data;
    ssize_t retval = -ENOMEM;
    
    // 获取信号量
    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;
    
    // 等待缓冲区空间
    while (dev->data_size == BUFFER_SIZE) {
        up(&dev->sem);
        
        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN;
        
        if (wait_event_interruptible(dev->write_queue, dev->data_size < BUFFER_SIZE))
            return -ERESTARTSYS;
        
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
    }
    
    // 计算可写入数据量
    if (count > BUFFER_SIZE - dev->data_size)
        count = BUFFER_SIZE - dev->data_size;
    
    // 从用户空间复制数据
    if (copy_from_user(dev->buffer + dev->data_size, buf, count)) {
        retval = -EFAULT;
        goto out;
    }
    
    dev->data_size += count;
    retval = count;
    
    // 唤醒等待读取的进程
    wake_up_interruptible(&dev->read_queue);
    
out:
    up(&dev->sem);
    return retval;
}

高级特性

llseek操作

c 复制代码
static loff_t device_llseek(struct file *filp, loff_t offset, int whence)
{
    struct my_device_data *dev = filp->private_data;
    loff_t new_pos;
    
    switch (whence) {
    case SEEK_SET:
        new_pos = offset;
        break;
    case SEEK_CUR:
        new_pos = filp->f_pos + offset;
        break;
    case SEEK_END:
        new_pos = dev->data_size + offset;
        break;
    default:
        return -EINVAL;
    }
    
    if (new_pos < 0)
        return -EINVAL;
    
    filp->f_pos = new_pos;
    return new_pos;
}

poll/select支持

c 复制代码
static unsigned int device_poll(struct file *filp, struct poll_table_struct *wait)
{
    struct my_device_data *dev = filp->private_data;
    unsigned int mask = 0;
    
    down(&dev->sem);
    
    poll_wait(filp, &dev->read_queue, wait);
    poll_wait(filp, &dev->write_queue, wait);
    
    if (dev->data_size > 0)
        mask |= POLLIN | POLLRDNORM;
    
    if (dev->data_size < BUFFER_SIZE)
        mask |= POLLOUT | POLLWRNORM;
    
    up(&dev->sem);
    
    return mask;
}

ioctl系统调用

ioctl命令定义

c 复制代码
// 使用_IO系列宏定义ioctl命令
#include <linux/ioctl.h>

#define IOCTL_MAGIC 'k'
#define IOCTL_GET_BUFFER_SIZE _IOR(IOCTL_MAGIC, 1, int)
#define IOCTL_SET_BUFFER_SIZE _IOW(IOCTL_MAGIC, 2, int)
#define IOCTL_CLEAR_BUFFER _IO(IOCTL_MAGIC, 3)
#define IOCTL_GET_DEVICE_INFO _IOR(IOCTL_MAGIC, 4, struct device_info)

struct device_info {
    int major;
    int minor;
    int buffer_size;
    int data_size;
};

ioctl实现

c 复制代码
static long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct my_device_data *dev = filp->private_data;
    int err = 0;
    int retval = 0;
    
    // 检查命令有效性
    if (_IOC_TYPE(cmd) != IOCTL_MAGIC)
        return -ENOTTY;
    
    if (_IOC_NR(cmd) > IOCTL_MAXNR)
        return -ENOTTY;
    
    // 检查访问权限
    if (_IOC_DIR(cmd) & _IOC_READ)
        err = !access_ok((void __user *)arg, _IOC_SIZE(cmd));
    else if (_IOC_DIR(cmd) & _IOC_WRITE)
        err = !access_ok((void __user *)arg, _IOC_SIZE(cmd));
    
    if (err)
        return -EFAULT;
    
    switch (cmd) {
    case IOCTL_GET_BUFFER_SIZE:
        retval = __put_user(BUFFER_SIZE, (int __user *)arg);
        break;
        
    case IOCTL_SET_BUFFER_SIZE:
        // 这里应该重新分配缓冲区,简化处理
        retval = -EINVAL;
        break;
        
    case IOCTL_CLEAR_BUFFER:
        down(&dev->sem);
        dev->data_size = 0;
        up(&dev->sem);
        wake_up_interruptible(&dev->write_queue);
        break;
        
    case IOCTL_GET_DEVICE_INFO:
        {
            struct device_info info;
            down(&dev->sem);
            info.major = MAJOR(dev_num);
            info.minor = MINOR(dev_num);
            info.buffer_size = BUFFER_SIZE;
            info.data_size = dev->data_size;
            up(&dev->sem);
            
            if (copy_to_user((void __user *)arg, &info, sizeof(info)))
                retval = -EFAULT;
        }
        break;
        
    default:
        retval = -ENOTTY;
    }
    
    return retval;
}

阻塞与非阻塞操作

等待队列

c 复制代码
#include <linux/wait.h>

// 初始化等待队列
static int __init device_init(void)
{
    // ... 其他初始化 ...
    init_waitqueue_head(&dev.read_queue);
    init_waitqueue_head(&dev.write_queue);
    // ...
}

阻塞读取示例

c 复制代码
static ssize_t device_read_blocking(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    struct my_device_data *dev = filp->private_data;
    
    // 等待条件满足
    wait_event_interruptible(dev->read_queue, device_has_data(dev));
    
    // 读取数据
    return device_read_data(dev, buf, count);
}

非阻塞读取示例

c 复制代码
static ssize_t device_read_nonblocking(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    struct my_device_data *dev = filp->private_data;
    
    // 检查是否有数据可用
    if (!device_has_data(dev))
        return -EAGAIN;
    
    // 读取数据
    return device_read_data(dev, buf, count);
}

综合实现

c 复制代码
static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    struct my_device_data *dev = filp->private_data;
    
    if (filp->f_flags & O_NONBLOCK) {
        return device_read_nonblocking(filp, buf, count, f_pos);
    } else {
        return device_read_blocking(filp, buf, count, f_pos);
    }
}

并发控制

信号量使用

c 复制代码
#include <linux/semaphore.h>

static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    struct my_device_data *dev = filp->private_data;
    ssize_t retval = 0;
    
    // 获取信号量
    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;
    
    // 临界区代码
    // ... 读取操作 ...
    
    // 释放信号量
    up(&dev->sem);
    
    return retval;
}

自旋锁使用

c 复制代码
#include <linux/spinlock.h>

typedef struct {
    char data[100];
    int size;
    spinlock_t lock;
} shared_data_t;

static shared_data_t shared_data;

static void update_data(char *new_data, int size)
{
    unsigned long flags;
    
    // 获取自旋锁
    spin_lock_irqsave(&shared_data.lock, flags);
    
    // 临界区代码
    memcpy(shared_data.data, new_data, size);
    shared_data.size = size;
    
    // 释放自旋锁
    spin_unlock_irqrestore(&shared_data.lock, flags);
}

读写锁

c 复制代码
#include <linux/rwlock.h>

typedef struct {
    char data[100];
    int size;
    rwlock_t lock;
} rw_data_t;

static rw_data_t rw_data;

static ssize_t read_data(char __user *buf, size_t count)
{
    unsigned long flags;
    ssize_t retval;
    
    // 获取读锁
    read_lock_irqsave(&rw_data.lock, flags);
    
    // 读取操作(多个读者可以同时访问)
    retval = copy_to_user(buf, rw_data.data, min(count, rw_data.size));
    
    // 释放读锁
    read_unlock_irqrestore(&rw_data.lock, flags);
    
    return retval;
}

static ssize_t write_data(const char __user *buf, size_t count)
{
    unsigned long flags;
    
    // 获取写锁
    write_lock_irqsave(&rw_data.lock, flags);
    
    // 写入操作(独占访问)
    copy_from_user(rw_data.data, buf, count);
    rw_data.size = count;
    
    // 释放写锁
    write_unlock_irqrestore(&rw_data.lock, flags);
    
    return count;
}

中断处理

注册中断处理程序

c 复制代码
#include <linux/interrupt.h>
#include <linux/gpio.h>

#define GPIO_IRQ_PIN 17
#define IRQ_NUMBER gpio_to_irq(GPIO_IRQ_PIN)

static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
    struct my_device_data *dev = dev_id;
    
    // 处理中断
    printk(KERN_INFO "Interrupt received\n");
    
    // 唤醒等待的进程
    wake_up_interruptible(&dev->read_queue);
    
    return IRQ_HANDLED;
}

static int __init device_init(void)
{
    int result;
    
    // 申请GPIO
    result = gpio_request(GPIO_IRQ_PIN, "my_device_irq");
    if (result) {
        printk(KERN_WARNING "Failed to request GPIO\n");
        return result;
    }
    
    // 设置GPIO方向
    result = gpio_direction_input(GPIO_IRQ_PIN);
    if (result) {
        gpio_free(GPIO_IRQ_PIN);
        return result;
    }
    
    // 注册中断处理程序
    result = request_irq(IRQ_NUMBER, my_interrupt_handler, 
                        IRQF_TRIGGER_RISING, "my_device", &dev);
    if (result) {
        gpio_free(GPIO_IRQ_PIN);
        return result;
    }
    
    return 0;
}

static void __exit device_exit(void)
{
    free_irq(IRQ_NUMBER, &dev);
    gpio_free(GPIO_IRQ_PIN);
}

中断驱动的I/O

c 复制代码
typedef struct {
    char buffer[BUFFER_SIZE];
    int head;
    int tail;
    int count;
    spinlock_t lock;
    wait_queue_head_t queue;
} circular_buffer_t;

static circular_buffer_t cb;

static irqreturn_t interrupt_handler(int irq, void *dev_id)
{
    unsigned long flags;
    char data;
    
    // 读取硬件数据(示例)
    data = read_hardware_data();
    
    // 保存到循环缓冲区
    spin_lock_irqsave(&cb.lock, flags);
    
    if (cb.count < BUFFER_SIZE) {
        cb.buffer[cb.head] = data;
        cb.head = (cb.head + 1) % BUFFER_SIZE;
        cb.count++;
        wake_up_interruptible(&cb.queue);
    }
    
    spin_unlock_irqrestore(&cb.lock, flags);
    
    return IRQ_HANDLED;
}

static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    unsigned long flags;
    int i;
    char temp_buf[BUFFER_SIZE];
    
    // 等待数据可用
    wait_event_interruptible(cb.queue, cb.count > 0);
    
    spin_lock_irqsave(&cb.lock, flags);
    
    // 从循环缓冲区读取数据
    for (i = 0; i < count && cb.count > 0; i++) {
        temp_buf[i] = cb.buffer[cb.tail];
        cb.tail = (cb.tail + 1) % BUFFER_SIZE;
        cb.count--;
    }
    
    spin_unlock_irqrestore(&cb.lock, flags);
    
    // 复制到用户空间
    return copy_to_user(buf, temp_buf, i) ? -EFAULT : i;
}

内存管理

kmalloc和kfree

c 复制代码
#include <linux/slab.h>

typedef struct {
    char *buffer;
    int size;
} dynamic_buffer_t;

static int allocate_buffer(dynamic_buffer_t *buf, int size)
{
    // 分配内存
    buf->buffer = kmalloc(size, GFP_KERNEL);
    if (!buf->buffer) {
        printk(KERN_WARNING "Failed to allocate memory\n");
        return -ENOMEM;
    }
    
    buf->size = size;
    return 0;
}

static void free_buffer(dynamic_buffer_t *buf)
{
    if (buf->buffer) {
        kfree(buf->buffer);
        buf->buffer = NULL;
        buf->size = 0;
    }
}

vmalloc和vfree

c 复制代码
#include <linux/vmalloc.h>

static int *large_array;

static int __init init_large_array(void)
{
    // 分配大内存块(可能不连续)
    large_array = vmalloc(SIZE_LARGE_ARRAY * sizeof(int));
    if (!large_array) {
        printk(KERN_WARNING "Failed to allocate large array\n");
        return -ENOMEM;
    }
    
    // 初始化数组
    memset(large_array, 0, SIZE_LARGE_ARRAY * sizeof(int));
    
    return 0;
}

static void __exit cleanup_large_array(void)
{
    if (large_array) {
        vfree(large_array);
        large_array = NULL;
    }
}

内存池

c 复制代码
#include <linux/mempool.h>

#define MIN_POOL_ELEMENTS 16
#define MAX_POOL_ELEMENTS 32

static mempool_t *my_mempool;
static kmem_cache_t *my_cache;

static void *mempool_alloc_function(gfp_t gfp_mask, void *pool_data)
{
    return kmalloc(sizeof(my_data_t), gfp_mask);
}

static void mempool_free_function(void *element, void *pool_data)
{
    kfree(element);
}

static int __init init_mempool(void)
{
    // 创建slab缓存
    my_cache = kmem_cache_create("my_cache", sizeof(my_data_t), 0, 
                                 SLAB_HWCACHE_ALIGN, NULL);
    if (!my_cache)
        return -ENOMEM;
    
    // 创建内存池
    my_mempool = mempool_create(MIN_POOL_ELEMENTS, mempool_alloc_function,
                               mempool_free_function, NULL);
    if (!my_mempool) {
        kmem_cache_destroy(my_cache);
        return -ENOMEM;
    }
    
    return 0;
}

static void __exit cleanup_mempool(void)
{
    if (my_mempool) {
        mempool_destroy(my_mempool);
        my_mempool = NULL;
    }
    
    if (my_cache) {
        kmem_cache_destroy(my_cache);
        my_cache = NULL;
    }
}

调试技巧

使用printk

c 复制代码
// 不同级别的打印信息
printk(KERN_EMERG "Emergency message\n");
printk(KERN_ALERT "Alert message\n");
printk(KERN_CRIT "Critical message\n");
printk(KERN_ERR "Error message\n");
printk(KERN_WARNING "Warning message\n");
printk(KERN_NOTICE "Notice message\n");
printk(KERN_INFO "Info message\n");
printk(KERN_DEBUG "Debug message\n");

动态调试

c 复制代码
// 使用动态调试
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/dynamic_debug.h>

// 在代码中使用动态调试宏
pr_debug("Debug information: value=%d\n", value);

// 启用动态调试
echo 'module my_device +p' > /sys/kernel/debug/dynamic_debug/control

使用proc文件系统

c 复制代码
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static int device_proc_show(struct seq_file *m, void *v)
{
    struct my_device_data *dev = &global_device_data;
    
    seq_printf(m, "Device Information:\n");
    seq_printf(m, "Major number: %d\n", MAJOR(dev_num));
    seq_printf(m, "Minor number: %d\n", MINOR(dev_num));
    seq_printf(m, "Buffer size: %d\n", BUFFER_SIZE);
    seq_printf(m, "Current data size: %d\n", dev->data_size);
    
    return 0;
}

static int device_proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, device_proc_show, NULL);
}

static const struct file_operations device_proc_fops = {
    .owner = THIS_MODULE,
    .open = device_proc_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = single_release,
};

static int __init create_proc_entry(void)
{
    proc_create("my_device_info", 0, NULL, &device_proc_fops);
    return 0;
}

static void __exit remove_proc_entry(void)
{
    remove_proc_entry("my_device_info", NULL);
}

使用debugfs

c 复制代码
#include <linux/debugfs.h>

static struct dentry *debug_dir;
static struct dentry *debug_file;

static int debug_open(struct inode *inode, struct file *file)
{
    return single_open(file, debug_show, inode->i_private);
}

static ssize_t debug_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    // 处理调试命令
    char command[100];
    
    if (count > sizeof(command) - 1)
        return -EINVAL;
    
    if (copy_from_user(command, buf, count))
        return -EFAULT;
    
    command[count] = '\0';
    
    // 解析并执行调试命令
    process_debug_command(command);
    
    return count;
}

static const struct file_operations debug_fops = {
    .owner = THIS_MODULE,
    .open = debug_open,
    .read = seq_read,
    .write = debug_write,
    .llseek = seq_lseek,
    .release = single_release,
};

static int __init create_debug_entries(void)
{
    debug_dir = debugfs_create_dir("my_device", NULL);
    if (!debug_dir)
        return -ENOMEM;
    
    debug_file = debugfs_create_file("debug", 0644, debug_dir, NULL, &debug_fops);
    if (!debug_file) {
        debugfs_remove(debug_dir);
        return -ENOMEM;
    }
    
    return 0;
}

static void __exit remove_debug_entries(void)
{
    debugfs_remove(debug_file);
    debugfs_remove(debug_dir);
}

完整示例

完整的字符设备驱动

c 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/semaphore.h>
#include <linux/wait.h>
#include <linux/sched.h>

#define DEVICE_NAME "my_char_dev"
#define BUFFER_SIZE 1024
#define MAJOR_NUM 240

// 设备数据结构
struct my_char_device {
    struct cdev cdev;
    char buffer[BUFFER_SIZE];
    int data_size;
    struct semaphore sem;
    wait_queue_head_t read_queue;
    wait_queue_head_t write_queue;
    struct class *dev_class;
    struct device *dev;
};

// 全局变量
static dev_t dev_num;
static struct my_char_device my_device;

// 文件操作函数声明
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char __user *, size_t, loff_t *);

// 文件操作结构体
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = device_open,
    .release = device_release,
    .read = device_read,
    .write = device_write,
};

// open函数
static int device_open(struct inode *inode, struct file *filp)
{
    struct my_char_device *dev;
    
    dev = container_of(inode->i_cdev, struct my_char_device, cdev);
    filp->private_data = dev;
    
    try_module_get(THIS_MODULE);
    printk(KERN_INFO "Device opened\n");
    
    return 0;
}

// release函数
static int device_release(struct inode *inode, struct file *filp)
{
    module_put(THIS_MODULE);
    printk(KERN_INFO "Device closed\n");
    
    return 0;
}

// read函数
static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    struct my_char_device *dev = filp->private_data;
    ssize_t retval = 0;
    
    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;
    
    while (dev->data_size == 0) {
        up(&dev->sem);
        
        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN;
        
        if (wait_event_interruptible(dev->read_queue, dev->data_size > 0))
            return -ERESTARTSYS;
        
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
    }
    
    if (count > dev->data_size)
        count = dev->data_size;
    
    if (copy_to_user(buf, dev->buffer, count)) {
        retval = -EFAULT;
        goto out;
    }
    
    memcpy(dev->buffer, dev->buffer + count, dev->data_size - count);
    dev->data_size -= count;
    retval = count;
    
    wake_up_interruptible(&dev->write_queue);
    
out:
    up(&dev->sem);
    return retval;
}

// write函数
static ssize_t device_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    struct my_char_device *dev = filp->private_data;
    ssize_t retval = -ENOMEM;
    
    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;
    
    while (dev->data_size == BUFFER_SIZE) {
        up(&dev->sem);
        
        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN;
        
        if (wait_event_interruptible(dev->write_queue, dev->data_size < BUFFER_SIZE))
            return -ERESTARTSYS;
        
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
    }
    
    if (count > BUFFER_SIZE - dev->data_size)
        count = BUFFER_SIZE - dev->data_size;
    
    if (copy_from_user(dev->buffer + dev->data_size, buf, count)) {
        retval = -EFAULT;
        goto out;
    }
    
    dev->data_size += count;
    retval = count;
    
    wake_up_interruptible(&dev->read_queue);
    
out:
    up(&dev->sem);
    return retval;
}

// 模块初始化函数
static int __init char_device_init(void)
{
    int result;
    
    // 注册设备号
    dev_num = MKDEV(MAJOR_NUM, 0);
    result = register_chrdev_region(dev_num, 1, DEVICE_NAME);
    if (result < 0) {
        printk(KERN_WARNING "Can't register device number\n");
        return result;
    }
    
    // 初始化字符设备
    cdev_init(&my_device.cdev, &fops);
    my_device.cdev.owner = THIS_MODULE;
    
    // 添加字符设备到内核
    result = cdev_add(&my_device.cdev, dev_num, 1);
    if (result) {
        printk(KERN_WARNING "Error adding character device\n");
        goto fail_cdev_add;
    }
    
    // 创建设备类
    my_device.dev_class = class_create(THIS_MODULE, DEVICE_NAME);
    if (IS_ERR(my_device.dev_class)) {
        result = PTR_ERR(my_device.dev_class);
        goto fail_class_create;
    }
    
    // 创建设备文件
    my_device.dev = device_create(my_device.dev_class, NULL, dev_num, NULL, DEVICE_NAME);
    if (IS_ERR(my_device.dev)) {
        result = PTR_ERR(my_device.dev);
        goto fail_device_create;
    }
    
    // 初始化同步机制
    sema_init(&my_device.sem, 1);
    init_waitqueue_head(&my_device.read_queue);
    init_waitqueue_head(&my_device.write_queue);
    my_device.data_size = 0;
    
    printk(KERN_INFO "Character device registered successfully\n");
    return 0;

fail_device_create:
    class_destroy(my_device.dev_class);
fail_class_create:
    cdev_del(&my_device.cdev);
fail_cdev_add:
    unregister_chrdev_region(dev_num, 1);
    return result;
}

// 模块退出函数
static void __exit char_device_exit(void)
{
    device_destroy(my_device.dev_class, dev_num);
    class_destroy(my_device.dev_class);
    cdev_del(&my_device.cdev);
    unregister_chrdev_region(dev_num, 1);
    
    printk(KERN_INFO "Character device unregistered\n");
}

module_init(char_device_init);
module_exit(char_device_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A complete character device driver");
MODULE_VERSION("1.0");

Makefile

makefile 复制代码
obj-m += my_char_device.o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

install:
	sudo insmod my_char_device.ko

uninstall:
	sudo rmmod my_char_device

test:
	echo "Hello, device!" > /dev/my_char_dev
	cat /dev/my_char_dev

测试程序

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define DEVICE_PATH "/dev/my_char_dev"

int main(int argc, char *argv[])
{
    int fd;
    char buffer[256];
    ssize_t bytes;
    
    // 打开设备
    fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        return 1;
    }
    
    printf("Device opened successfully\n");
    
    // 写入数据
    if (argc > 1) {
        bytes = write(fd, argv[1], strlen(argv[1]));
        printf("Wrote %zd bytes to device\n", bytes);
    }
    
    // 读取数据
    bytes = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes > 0) {
        buffer[bytes] = '\0';
        printf("Read %zd bytes from device: %s\n", bytes, buffer);
    }
    
    // 关闭设备
    close(fd);
    
    return 0;
}

最佳实践

1. 错误处理

  • 始终检查返回值
  • 使用适当的错误码
  • 提供有意义的错误信息

2. 内存管理

  • 使用适当的内存分配函数
  • 检查分配结果
  • 确保释放所有分配的内存

3. 同步机制

  • 使用适当的同步原语
  • 避免死锁
  • 最小化临界区

4. 可移植性

  • 使用内核API而不是直接访问硬件
  • 避免架构相关的代码
  • 遵循内核编码风格

5. 调试和测试

  • 添加足够的调试信息
  • 创建测试程序
  • 使用内核调试工具

6. 文档和维护

  • 添加详细的注释
  • 创建使用文档
  • 维护变更日志

7. 安全性

  • 验证用户输入
  • 检查权限
  • 避免缓冲区溢出

8. 性能优化

  • 最小化系统调用开销
  • 使用高效的数据结构
  • 考虑使用DMA(如适用)

总结

Linux字符设备驱动开发是内核编程的重要组成部分。通过理解核心概念、掌握基本架构、实现必要的功能,并遵循最佳实践,可以开发出高质量、可靠的字符设备驱动程序。

关键点回顾:

  • 理解字符设备与块设备的区别
  • 掌握设备注册和文件操作实现
  • 熟练使用同步机制处理并发
  • 正确处理阻塞和非阻塞操作
  • 实现必要的ioctl命令
  • 注重错误处理和调试
  • 遵循内核编程规范

随着经验的积累,可以进一步探索更高级的主题,如内存映射、DMA操作、电源管理等,以开发更复杂和功能丰富的设备驱动程序。

相关推荐
p66666666683 小时前
【☀Linux驱动开发笔记☀】linux下led驱动(非设备树)_03
linux·驱动开发·笔记·嵌入式硬件·学习
以琦琦为中心3 小时前
在RK3568开发板嵌入式开发中,配置NFS服务是实现与Ubuntu虚拟机之间文件共享的常用方法
linux·运维·ubuntu·rk3568
Nimsolax3 小时前
Linux网络DNS与ICMP
linux·网络
赖small强4 小时前
【Linux驱动开发】Linux UART 通信详解:从硬件到驱动再到应用
linux·驱动开发·uart
赖small强4 小时前
【Linux驱动开发】Linux 设备驱动中的阻塞与非阻塞 I/O:机制、源码与示例
linux·驱动开发·阻塞与非阻塞
yolo_guo4 小时前
opencv 学习: QA_02 什么是图像中的高频成分和低频成分
linux·c++·opencv·计算机视觉
q***56384 小时前
在 Ubuntu 22.04 上安装和配置 Nginx 的完整指南
linux·nginx·ubuntu
大聪明-PLUS4 小时前
Linux 中的 CPU。文章 1. 利用率
linux·嵌入式·arm·smarc
热爱编程的OP5 小时前
Linux进程池与管道通信详解:从原理到实现
linux·开发语言·c++