1. Linux驱动架构概述
Linux驱动架构是操作系统与硬件设备交互的核心桥梁,其设计遵循"一切皆文件"的Unix哲学。这种架构通过分层设计和模块化实现,使得Linux能够支持从嵌入式设备到服务器、从简单字符设备到复杂网络设备的广泛硬件平台。
关键设计思想:驱动开发者需要理解Linux将硬件设备抽象为文件节点的核心理念,用户空间通过标准的文件操作接口(open/read/write/ioctl等)与硬件交互。
1.1 驱动类型分类
Linux内核将设备驱动分为以下几类,每种类型对应不同的设备特性和操作方式:
-
字符设备驱动:提供字节流访问接口,如键盘、鼠标、串口等。典型特点:
- 数据不可随机访问(需顺序读写)
- 通常不经过系统缓存
- 通过/dev目录下的设备节点访问
-
块设备驱动:管理可随机访问的存储设备,如硬盘、SSD等。特点包括:
- 数据按固定大小的块进行读写
- 支持缓存机制
- 通常挂载为文件系统使用
-
网络设备驱动:处理网络数据包的收发,如以太网卡、WiFi模块等。特殊之处在于:
- 不映射到文件系统节点
- 使用套接字接口进行通信
- 需要处理协议栈相关操作
-
杂项设备驱动:归入misc子系统,用于简单设备(如随机数生成器)
-
平台设备驱动:针对片上系统(SoC)中的集成外设
-
总线设备驱动:包括PCI、USB、I2C、SPI等总线设备的驱动实现
2. 驱动模型核心架构
Linux设备驱动模型基于三大核心组件构建,这些组件定义了设备如何在内核中表示和管理:
2.1 驱动模型三大组件
c复制struct bus_type { // 总线
const char *name;
int (*match)(struct device *, struct device_driver *);
int (*probe)(struct device *);
int (*remove)(struct device *);
};
struct device { // 设备
struct device *parent;
struct kobject kobj;
const char *init_name;
struct device_driver *driver;
struct bus_type *bus;
void *platform_data;
};
struct device_driver { // 驱动
const char *name;
struct bus_type *bus;
struct module *owner;
int (*probe)(struct device *);
int (*remove)(struct device *);
};
总线(bus_type):作为设备和驱动的纽带,负责两者的匹配。关键操作包括:
- match():判断驱动是否支持某设备
- probe():初始化匹配成功的设备
- remove():设备卸载时的清理
设备(device):表示系统中的硬件设备,包含:
- 设备层次关系(parent/child)
- 所属总线信息
- 驱动指针
- 平台特定数据
驱动(device_driver):实现设备操作的核心代码,主要功能:
- 定义支持的设备名称或ID表
- 实现probe/remove等生命周期方法
- 提供设备特定的操作接口
2.2 设备树(Device Tree)支持
现代Linux驱动广泛使用设备树(Device Tree)来描述硬件配置,取代传统的硬编码方式。设备树采用文本格式(.dts)描述硬件,编译后生成二进制blob(.dtb)供内核解析。
典型设备树示例:
c复制/ {
compatible = "mycompany,myboard";
cpus {
cpu@0 {
compatible = "arm,cortex-a53";
};
};
memory@80000000 {
reg = <0x80000000 0x20000000>;
};
i2c@12340000 {
compatible = "mycompany,i2c";
reg = <0x12340000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
eeprom@50 {
compatible = "atmel,24c256";
reg = <0x50>;
};
};
};
设备树优势:
- 硬件描述与代码分离:修改硬件配置无需重新编译内核
- 层次化结构:清晰表达硬件拓扑关系
- 可扩展性:通过自定义属性添加厂商特定参数
3. 字符设备驱动架构
字符设备是最常见的驱动类型,下面深入分析其实现细节。
3.1 字符设备核心结构
完整字符设备驱动示例:
c复制#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#define DEVICE_NAME "mychardev"
#define CLASS_NAME "mycharclass"
#define DEVICE_COUNT 1
static int major_number = 0;
static struct class* char_class = NULL;
static struct device* char_device = NULL;
static struct cdev my_cdev;
// 文件操作结构
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = mydev_open,
.release = mydev_release,
.read = mydev_read,
.write = mydev_write,
.unlocked_ioctl = mydev_ioctl,
};
// 初始化函数
static int __init mydev_init(void)
{
dev_t devno;
int ret;
// 1. 分配主设备号
ret = alloc_chrdev_region(&devno, 0, DEVICE_COUNT, DEVICE_NAME);
major_number = MAJOR(devno);
// 2. 初始化cdev
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
// 3. 添加cdev到系统
ret = cdev_add(&my_cdev, devno, DEVICE_COUNT);
// 4. 创建设备类
char_class = class_create(THIS_MODULE, CLASS_NAME);
// 5. 创建设备文件节点
char_device = device_create(char_class, NULL, devno,
NULL, DEVICE_NAME);
return 0;
}
关键组件解析:
-
file_operations:定义设备操作接口
- open/release:设备打开/关闭时调用
- read/write:数据读写接口
- unlocked_ioctl:控制命令接口
-
设备号管理:
- 主设备号标识驱动类型
- 次设备号标识具体设备实例
- 动态分配(alloc_chrdev_region)或静态注册(register_chrdev_region)
-
cdev结构:内核中表示字符设备的核心结构
- 通过cdev_init关联file_operations
- 使用cdev_add注册到系统
-
sysfs集成:
- class_create创建设备类
- device_create创建设备节点
3.2 字符设备操作流程
典型字符设备驱动开发流程:
-
实现文件操作接口:
c复制static int mydev_open(struct inode *inode, struct file *filp) { filp->private_data = container_of(inode->i_cdev, struct mydev, cdev); return 0; } static ssize_t mydev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct mydev *dev = filp->private_data; if (copy_to_user(buf, dev->buffer, count)) return -EFAULT; return count; } -
处理用户空间数据:
- copy_to_user/copy_from_user:安全地在内核和用户空间传递数据
- put_user/get_user:单个值的传递
-
实现ioctl控制接口:
c复制#define MYDEV_IOCTL_RESET _IO('M', 0) #define MYDEV_IOCTL_SETVAL _IOW('M', 1, int) static long mydev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { switch (cmd) { case MYDEV_IOCTL_RESET: mydev_reset(dev); break; case MYDEV_IOCTL_SETVAL: if (copy_from_user(&val, (int __user *)arg, sizeof(int))) return -EFAULT; dev->value = val; break; default: return -ENOTTY; } return 0; } -
内存管理注意事项:
- 使用kmalloc/vmalloc分配内核内存
- DMA操作需要特殊处理(后文详述)
- 注意内存泄漏和竞态条件
4. 块设备驱动架构
块设备驱动相比字符设备更为复杂,需要处理缓存、请求队列等机制。
4.1 块设备核心结构
块设备驱动关键结构:
c复制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);
int (*getgeo)(struct block_device *, struct hd_geometry *);
struct module *owner;
};
struct gendisk {
int major; // 主设备号
int first_minor; // 第一个次设备号
int minors; // 次设备号数量
char disk_name[DISK_NAME_LEN]; // 磁盘名
struct block_device_operations *fops; // 操作集
struct request_queue *queue; // 请求队列
void *private_data; // 私有数据
sector_t capacity; // 容量(扇区数)
};
块设备驱动初始化示例:
c复制static int myblk_init(void)
{
struct request_queue *queue;
struct gendisk *gd;
// 1. 分配gendisk结构
gd = alloc_disk(MYDEV_MINORS);
// 2. 初始化请求队列
queue = blk_init_queue(mydev_request, &mydev_lock);
// 3. 设置gendisk
gd->major = MYDEV_MAJOR;
gd->first_minor = 0;
gd->fops = &mydev_fops;
gd->queue = queue;
gd->private_data = mydev_data;
snprintf(gd->disk_name, 32, "myblk%d", 0);
set_capacity(gd, mydev_size_sectors);
// 4. 添加到系统
add_disk(gd);
return 0;
}
4.2 请求队列处理
块设备的核心是请求队列处理机制,现代Linux内核提供多种队列模式:
-
传统请求队列:
c复制static void mydev_request(struct request_queue *q) { struct request *req; while ((req = blk_fetch_request(q)) != NULL) { if (req->cmd_type != REQ_TYPE_FS) { __blk_end_request_all(req, -EIO); continue; } if (rq_data_dir(req) == READ) mydev_read(req); else mydev_write(req); } } -
多队列(blk-mq)模式:
c复制static blk_status_t mydev_queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data *bd) { struct request *req = bd->rq; blk_mq_start_request(req); // 处理请求 if (req_op(req) == REQ_OP_READ) { mydev_process_read(req); } else { mydev_process_write(req); } blk_mq_end_request(req, BLK_STS_OK); return BLK_STS_OK; } -
性能优化技巧:
- 使用blk_queue_max_segments设置最大段数
- 通过blk_queue_physical_block_size设置物理块大小
- 启用write cache可提升性能但需注意数据安全
5. 网络设备驱动架构
网络设备驱动有着独特的架构,不基于文件接口而是通过套接字与协议栈交互。
5.1 网络设备核心结构
关键数据结构:
c复制struct net_device {
char name[IFNAMSIZ]; // 设备名
const struct net_device_ops *netdev_ops; // 网络设备操作
unsigned int flags; // 设备标志
int mtu; // 最大传输单元
unsigned char *dev_addr; // MAC地址
struct net_device_stats stats; // 统计信息
};
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);
void (*ndo_tx_timeout)(struct net_device *dev);
struct rtnl_link_stats64* (*ndo_get_stats64)(...);
};
5.2 网络设备驱动示例
初始化流程:
c复制static int mynet_init(void)
{
struct net_device *dev;
int ret;
// 1. 分配网络设备结构
dev = alloc_netdev(sizeof(struct mynet_priv), "eth%d",
NET_NAME_UNKNOWN, ether_setup);
// 2. 设置MAC地址
eth_hw_addr_random(dev);
// 3. 设置操作函数
dev->netdev_ops = &mynet_ops;
// 4. 注册网络设备
ret = register_netdev(dev);
return ret;
}
数据包发送处理:
c复制static netdev_tx_t mynet_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct mynet_priv *priv = netdev_priv(dev);
netif_stop_queue(dev);
memcpy_toio(priv->tx_buffer, skb->data, skb->len);
outl(TX_START, priv->ioaddr + TX_REG);
dev->stats.tx_packets++;
dev->stats.tx_bytes += skb->len;
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
接收数据包处理(通常在中断上下文中):
c复制static void mynet_rx_packet(struct net_device *dev)
{
struct sk_buff *skb = netdev_alloc_skb(dev, length);
memcpy_fromio(skb->data, priv->rx_buffer, length);
skb_put(skb, length);
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);
dev->stats.rx_packets++;
dev->stats.rx_bytes += length;
}
6. 输入子系统驱动架构
输入子系统为HID设备提供统一框架,如键盘、鼠标、触摸屏等。
6.1 输入子系统核心结构
典型实现:
c复制struct input_dev *input_dev;
static int myinput_init(void)
{
// 1. 分配输入设备
input_dev = input_allocate_device();
// 2. 设置设备类型
input_dev->name = "My Input Device";
// 3. 设置支持的事件类型
__set_bit(EV_KEY, input_dev->evbit); // 按键事件
__set_bit(EV_REL, input_dev->evbit); // 相对事件
// 4. 设置支持的按键
__set_bit(BTN_LEFT, input_dev->keybit); // 左键
// 5. 注册设备
return input_register_device(input_dev);
}
事件上报示例:
c复制static void report_input_event(int x, int y, int button)
{
input_report_abs(input_dev, ABS_X, x);
input_report_abs(input_dev, ABS_Y, y);
input_report_key(input_dev, BTN_LEFT, button & 0x01);
input_sync(input_dev);
}
输入子系统支持的事件类型:
- EV_KEY:按键事件(键盘、按钮)
- EV_REL:相对坐标事件(鼠标)
- EV_ABS:绝对坐标事件(触摸屏)
- EV_LED:LED状态
- EV_SND:声音反馈
7. Platform总线驱动架构
Platform总线用于片上系统(SoC)中的集成外设,如GPIO、定时器等。
7.1 Platform设备/驱动模型
设备定义:
c复制static struct resource mydev_resources[] = {
[0] = { // 内存资源
.start = 0x10000000,
.end = 0x1000FFFF,
.flags = IORESOURCE_MEM,
},
[1] = { // 中断资源
.start = IRQ_NUM,
.end = IRQ_NUM,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device mydev_device = {
.name = "my-platform-device",
.id = -1,
.resource = mydev_resources,
.num_resources = ARRAY_SIZE(mydev_resources),
};
驱动定义:
c复制static int mydev_probe(struct platform_device *pdev)
{
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
void __iomem *base = ioremap(res->start, resource_size(res));
int irq = platform_get_irq(pdev, 0);
request_irq(irq, mydev_interrupt, 0, "mydev", NULL);
return 0;
}
static struct platform_driver mydev_driver = {
.probe = mydev_probe,
.remove = mydev_remove,
.driver = {
.name = "my-platform-device",
.of_match_table = of_match_ptr(mydev_of_match),
},
};
8. I2C/SPI总线驱动架构
8.1 I2C驱动示例
I2C驱动结构:
c复制static struct i2c_driver my_i2c_driver = {
.driver = {
.name = "my_i2c_device",
.of_match_table = my_i2c_of_match,
},
.probe = my_i2c_probe,
.remove = my_i2c_remove,
.id_table = my_i2c_id,
};
设备树匹配:
c复制static const struct of_device_id my_i2c_of_match[] = {
{ .compatible = "vendor,my-i2c-device" },
{},
};
读写操作:
c复制static int my_i2c_read_reg(struct i2c_client *client, u8 reg)
{
return i2c_smbus_read_byte_data(client, reg);
}
static int my_i2c_write_reg(struct i2c_client *client, u8 reg, u8 val)
{
return i2c_smbus_write_byte_data(client, reg, val);
}
9. 设备树与驱动匹配
9.1 设备树节点示例
c复制i2c@12340000 {
compatible = "mycompany,i2c";
reg = <0x12340000 0x1000>;
mydevice@50 {
compatible = "vendor,my-device";
reg = <0x50>; // I2C地址
interrupt-parent = <&gpio>;
interrupts = <15 IRQ_TYPE_EDGE_FALLING>;
clock-frequency = <100000>; // I2C时钟
vendor,enable-gpio = <&gpio 16 GPIO_ACTIVE_HIGH>;
};
};
9.2 驱动匹配机制
c复制static const struct of_device_id my_device_of_match[] = {
{ .compatible = "vendor,my-device", .data = &my_device_data },
{},
};
static struct i2c_driver my_device_driver = {
.driver = {
.of_match_table = my_device_of_match,
},
.probe = my_device_probe,
};
9.3 获取设备树参数
c复制static int my_platform_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
u32 clock_freq;
of_property_read_u32(np, "clock-frequency", &clock_freq);
bool led_polarity = of_property_read_bool(np, "vendor,led-polarity");
u32 config_data[4];
of_property_read_u32_array(np, "vendor,config-data", config_data, 4);
int gpio = of_get_named_gpio(np, "vendor,enable-gpio", 0);
}
10. 驱动模型注册流程
典型驱动注册流程:
-
模块初始化:
c复制static int __init mydrv_init(void) { return platform_driver_register(&mydrv_driver); } module_init(mydrv_init); -
probe函数:设备发现时调用
- 获取资源(内存、中断等)
- 初始化硬件
- 注册设备接口
-
remove函数:设备移除时调用
- 释放资源
- 注销设备
-
模块退出:
c复制static void __exit mydrv_exit(void) { platform_driver_unregister(&mydrv_driver); } module_exit(mydrv_exit);
11. 驱动框架高级特性
11.1 电源管理
c复制static const struct dev_pm_ops mydev_pm_ops = {
.suspend = mydev_suspend,
.resume = mydev_resume,
.runtime_suspend = mydev_runtime_suspend,
.runtime_resume = mydev_runtime_resume,
};
static struct platform_driver mydev_driver = {
.driver = {
.pm = &mydev_pm_ops,
},
};
11.2 DMA支持
c复制static int mydev_dma_setup(struct mydev *dev)
{
dev->dma_buf = dma_alloc_coherent(&pdev->dev,
DMA_BUF_SIZE,
&dev->dma_handle,
GFP_KERNEL);
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
dev->dma_chan = dma_request_channel(mask, mydev_dma_filter, NULL);
return 0;
}
11.3 中断处理
c复制static irqreturn_t mydev_interrupt(int irq, void *dev_id)
{
struct mydev *dev = dev_id;
u32 status = readl(dev->base + INT_STATUS_REG);
if (status & DATA_READY_INT) {
mydev_process_data(dev);
writel(DATA_READY_INT, dev->base + INT_CLEAR_REG);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int mydev_request_irq(struct mydev *dev)
{
return request_irq(dev->irq, mydev_interrupt,
IRQF_SHARED | IRQF_TRIGGER_RISING,
"mydev", dev);
}
12. 驱动调试与测试
12.1 调试方法
-
printk调试:
c复制#define DEBUG #ifdef DEBUG #define PDEBUG(fmt, args...) printk(KERN_DEBUG "mydev: " fmt, ## args) #else #define PDEBUG(fmt, args...) #endif -
procfs接口:
c复制static int mydev_proc_show(struct seq_file *m, void *v) { seq_printf(m, "Driver Status:\n"); seq_printf(m, " Version: %s\n", DRIVER_VERSION); return 0; } -
debugfs接口:
c复制static int __init mydev_debugfs_init(void) { struct dentry *dir = debugfs_create_dir("mydev", NULL); debugfs_create_u32("debug_level", 0644, dir, &debug_level); return 0; } -
动态调试:
bash复制echo 'file mydriver.c +p' > /sys/kernel/debug/dynamic_debug/control
13. 驱动开发实践建议
-
代码组织:
- 将驱动拆分为核心功能、硬件抽象、平台适配等模块
- 使用Kconfig和Makefile管理编译选项
-
错误处理:
- 检查所有可能失败的操作(内存分配、资源获取等)
- 使用goto实现统一的错误处理路径
-
并发控制:
- 使用互斥锁保护共享数据
- 注意避免死锁情况
- 考虑使用原子操作处理简单计数器
-
性能优化:
- 减少中断处理时间
- 使用DMA代替CPU拷贝
- 合理使用缓存机制
-
兼容性考虑:
- 处理不同内核版本的API变化
- 使用#ifdef条件编译处理平台差异
-
文档与注释:
- 详细记录硬件寄存器定义
- 说明关键算法和设计决策
- 维护变更日志
-
测试策略:
- 单元测试核心功能
- 压力测试长时间运行稳定性
- 边界条件测试(如零长度传输)
-
用户空间接口设计:
- 提供清晰的ioctl命令定义
- 考虑sysfs属性文件暴露配置参数
- 实现procfs/debugfs接口用于调试
在开发实际驱动时,建议参考内核源码中同类驱动的实现,如drivers/char、drivers/block等目录下的代码。内核文档Documentation/driver-api/也包含丰富的开发指南。