一、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", ¶m1)) {
dev_err(&pdev->dev, "Missing param1\n");
return -EINVAL;
}
if (of_property_read_string(np, "vendor,param2", ¶m2)) {
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
九、最佳实践
-
错误处理:总是检查返回值,使用goto进行错误处理
-
内存泄漏:确保所有分配的内存都被释放
-
并发控制:正确处理多核和中断上下文
-
代码风格:遵循内核编码规范
-
文档:提供详细的模块参数说明和使用示例
-
兼容性:考虑不同内核版本的兼容性
这是一个完整的Linux设备驱动模块开发指南,涵盖了从基础到高级的各个方面。