Linux 设备驱动模块

一、Linux设备驱动基础

1. 驱动模块类型

cpp 复制代码
// 1. 字符设备驱动
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 *);
    int (*open) (struct inode *, struct file *);
    int (*release) (struct inode *, struct file *);
};

// 2. 块设备驱动
struct block_device_operations {
    int (*open) (struct block_device *, fmode_t);
    void (*release) (struct gendisk *, fmode_t);
    int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
};

// 3. 网络设备驱动
struct net_device_ops {
    int (*ndo_open)(struct net_device *dev);
    int (*ndo_stop)(struct net_device *dev);
    netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb, struct net_device *dev);
};

2. 基础驱动模块示例

cpp 复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "mydriver"
#define CLASS_NAME  "mydrv"
#define BUFFER_SIZE 1024

static int major_number;
static struct class* driver_class = NULL;
static struct device* driver_device = NULL;
static struct cdev my_cdev;

static char *device_buffer;
static int buffer_pointer = 0;

// 打开设备
static int driver_open(struct inode *inodep, struct file *filep) {
    pr_info("Device opened\n");
    return 0;
}

// 释放设备
static int driver_release(struct inode *inodep, struct file *filep) {
    pr_info("Device closed\n");
    return 0;
}

// 读操作
static ssize_t driver_read(struct file *filep, char __user *buffer, 
                          size_t len, loff_t *offset) {
    int bytes_to_read;
    int bytes_read;
    
    if (*offset >= buffer_pointer) {
        return 0; // EOF
    }
    
    bytes_to_read = min(len, (size_t)(buffer_pointer - *offset));
    
    if (copy_to_user(buffer, device_buffer + *offset, bytes_to_read)) {
        return -EFAULT;
    }
    
    *offset += bytes_to_read;
    bytes_read = bytes_to_read;
    
    pr_info("Read %d bytes from device\n", bytes_read);
    return bytes_read;
}

// 写操作
static ssize_t driver_write(struct file *filep, const char __user *buffer,
                           size_t len, loff_t *offset) {
    int bytes_to_write;
    int bytes_written;
    
    bytes_to_write = min(len, (size_t)(BUFFER_SIZE - *offset));
    
    if (bytes_to_write == 0) {
        return -ENOSPC;
    }
    
    if (copy_from_user(device_buffer + *offset, buffer, bytes_to_write)) {
        return -EFAULT;
    }
    
    *offset += bytes_to_write;
    bytes_written = bytes_to_write;
    buffer_pointer = max(buffer_pointer, (int)*offset);
    
    pr_info("Written %d bytes to device\n", bytes_written);
    return bytes_written;
}

// 文件操作结构体
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = driver_open,
    .release = driver_release,
    .read = driver_read,
    .write = driver_write,
};

// 模块初始化
static int __init driver_init(void) {
    dev_t dev_num;
    int result;
    
    pr_info("Initializing driver\n");
    
    // 分配缓冲区
    device_buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
    if (!device_buffer) {
        pr_err("Failed to allocate buffer\n");
        return -ENOMEM;
    }
    memset(device_buffer, 0, BUFFER_SIZE);
    
    // 动态分配主设备号
    result = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
    if (result < 0) {
        pr_err("Failed to allocate device number\n");
        goto err_free_buffer;
    }
    
    major_number = MAJOR(dev_num);
    pr_info("Major number: %d\n", major_number);
    
    // 初始化cdev结构
    cdev_init(&my_cdev, &fops);
    my_cdev.owner = THIS_MODULE;
    
    // 添加字符设备
    result = cdev_add(&my_cdev, dev_num, 1);
    if (result < 0) {
        pr_err("Failed to add cdev\n");
        goto err_unregister_region;
    }
    
    // 创建设备类
    driver_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(driver_class)) {
        pr_err("Failed to create class\n");
        result = PTR_ERR(driver_class);
        goto err_cdev_del;
    }
    
    // 创建设备
    driver_device = device_create(driver_class, NULL, dev_num, 
                                  NULL, DEVICE_NAME);
    if (IS_ERR(driver_device)) {
        pr_err("Failed to create device\n");
        result = PTR_ERR(driver_device);
        goto err_class_destroy;
    }
    
    pr_info("Driver initialized successfully\n");
    return 0;
    
err_class_destroy:
    class_destroy(driver_class);
err_cdev_del:
    cdev_del(&my_cdev);
err_unregister_region:
    unregister_chrdev_region(dev_num, 1);
err_free_buffer:
    kfree(device_buffer);
    return result;
}

// 模块退出
static void __exit driver_exit(void) {
    dev_t dev_num = MKDEV(major_number, 0);
    
    device_destroy(driver_class, dev_num);
    class_destroy(driver_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev_num, 1);
    kfree(device_buffer);
    
    pr_info("Driver exited\n");
}

module_init(driver_init);
module_exit(driver_exit);

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

二、Makefile编写

cpp 复制代码
# Makefile for kernel module
obj-m += mydriver.o
# 如果有多个源文件
# mydriver-objs := file1.o file2.o

# 内核源码路径
KDIR := /lib/modules/$(shell uname -r)/build
# 当前路径
PWD := $(shell pwd)

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

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

install:
	sudo insmod mydriver.ko

remove:
	sudo rmmod mydriver

info:
	modinfo mydriver.ko

test:
	# 创建设备节点
	sudo mknod /dev/mydriver c $(shell cat /proc/devices | grep mydriver | awk '{print $$1}') 0
	# 测试读写
	echo "Hello" | sudo tee /dev/mydriver
	sudo cat /dev/mydriver

三、模块参数

cpp 复制代码
#include <linux/module.h>
#include <linux/moduleparam.h>

// 模块参数示例
static int debug_level = 0;
static char *device_name = "default";
static int array_param[10];
static int array_count;

// 注册模块参数
module_param(debug_level, int, 0644);
MODULE_PARM_DESC(debug_level, "Debug level (0-3)");

module_param(device_name, charp, 0644);
MODULE_PARM_DESC(device_name, "Device name");

module_param_array(array_param, int, &array_count, 0644);
MODULE_PARM_DESC(array_param, "Array parameter");

// 使用示例
static int __init param_init(void) {
    int i;
    
    pr_info("Debug level: %d\n", debug_level);
    pr_info("Device name: %s\n", device_name);
    
    for (i = 0; i < array_count; i++) {
        pr_info("array_param[%d] = %d\n", i, array_param[i]);
    }
    
    return 0;
}

四、设备树支持

cpp 复制代码
// 设备树节点示例
/mydriver {
    compatible = "vendor,mydriver";
    reg = <0x10000000 0x1000>;  // 寄存器地址和大小
    interrupts = <0 35 4>;       // 中断号
    clock-frequency = <1000000>; // 时钟频率
    status = "okay";
    
    // 自定义属性
    vendor,param1 = <100>;
    vendor,param2 = "string value";
};

// 驱动中解析设备树
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>

static int mydriver_probe(struct platform_device *pdev) {
    struct device_node *np = pdev->dev.of_node;
    struct resource *res;
    u32 param1;
    const char *param2;
    int irq;
    
    // 获取寄存器地址
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENODEV;
    }
    
    // 获取中断号
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(&pdev->dev, "No IRQ resource\n");
        return -ENXIO;
    }
    
    // 解析设备树属性
    if (of_property_read_u32(np, "vendor,param1", &param1)) {
        dev_err(&pdev->dev, "Missing param1\n");
        return -EINVAL;
    }
    
    if (of_property_read_string(np, "vendor,param2", &param2)) {
        dev_err(&pdev->dev, "Missing param2\n");
        return -EINVAL;
    }
    
    return 0;
}

五、调试技巧

cpp 复制代码
// 1. 使用printk调试
#define DRIVER_NAME "mydriver"

#define dbg_info(fmt, ...) \
    printk(KERN_INFO DRIVER_NAME ": " fmt, ##__VA_ARGS__)

#define dbg_err(fmt, ...) \
    printk(KERN_ERR DRIVER_NAME ": " fmt, ##__VA_ARGS__)

#define dbg_dbg(fmt, ...) \
    do { if (debug_level > 0) \
        printk(KERN_DEBUG DRIVER_NAME ": " fmt, ##__VA_ARGS__); \
    } while (0)

// 2. proc文件系统接口
static int mydriver_proc_show(struct seq_file *m, void *v) {
    seq_printf(m, "Buffer size: %d\n", BUFFER_SIZE);
    seq_printf(m, "Current pointer: %d\n", buffer_pointer);
    return 0;
}

static int mydriver_proc_open(struct inode *inode, struct file *file) {
    return single_open(file, mydriver_proc_show, NULL);
}

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

// 3. 使用ftrace跟踪
// 在函数前添加
#ifdef CONFIG_FUNCTION_TRACER
#define TRACE_ENTRY() trace_printk("Entering %s\n", __func__)
#define TRACE_EXIT()  trace_printk("Exiting %s\n", __func__)
#else
#define TRACE_ENTRY()
#define TRACE_EXIT()
#endif

六、并发控制

cpp 复制代码
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/completion.h>

// 互斥锁示例
static DEFINE_MUTEX(my_mutex);

void my_function(void) {
    mutex_lock(&my_mutex);
    // 临界区代码
    mutex_unlock(&my_mutex);
}

// 自旋锁示例(用于中断上下文)
static DEFINE_SPINLOCK(my_spinlock);
unsigned long flags;

void my_isr_handler(void) {
    spin_lock_irqsave(&my_spinlock, flags);
    // 临界区代码
    spin_unlock_irqrestore(&my_spinlock, flags);
}

// 完成量示例(用于同步)
static DECLARE_COMPLETION(my_completion);

void wait_for_completion_function(void) {
    wait_for_completion(&my_completion);
}

void complete_function(void) {
    complete(&my_completion);
}

// 原子操作
atomic_t my_counter = ATOMIC_INIT(0);

void atomic_example(void) {
    atomic_inc(&my_counter);
    int val = atomic_read(&my_counter);
}

七、内存管理

cpp 复制代码
// 1. 使用kmalloc分配
ptr = kmalloc(size, GFP_KERNEL);
kfree(ptr);

// 2. 使用vmalloc分配(虚拟地址连续)
ptr = vmalloc(size);
vfree(ptr);

// 3. DMA缓冲区
#include <linux/dma-mapping.h>

dma_addr_t dma_handle;
char *dma_buffer;

dma_buffer = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
dma_free_coherent(dev, size, dma_buffer, dma_handle);

八、编译和测试

bash 复制代码
# 编译模块
make

# 查看模块信息
modinfo mydriver.ko

# 加载模块
insmod mydriver.ko debug_level=2 device_name="test"

# 查看已加载模块
lsmod | grep mydriver

# 查看内核日志
dmesg | tail

# 查看设备号
cat /proc/devices | grep mydriver

# 卸载模块
rmmod mydriver

# 查看模块依赖
modprobe --show-depends mydriver

九、最佳实践

  1. 错误处理:总是检查返回值,使用goto进行错误处理

  2. 内存泄漏:确保所有分配的内存都被释放

  3. 并发控制:正确处理多核和中断上下文

  4. 代码风格:遵循内核编码规范

  5. 文档:提供详细的模块参数说明和使用示例

  6. 兼容性:考虑不同内核版本的兼容性

这是一个完整的Linux设备驱动模块开发指南,涵盖了从基础到高级的各个方面。