Linux驱动架构解析
Linux系统中的设备驱动程序采用分层与模块化的设计理念,遵循“一切皆文件”的核心哲学。这种设计不仅提升了代码的可维护性,也增强了系统的扩展能力。
主要驱动类型
根据设备功能和数据传输方式的不同,Linux驱动可分为以下几类:
- 字符设备驱动:以字节为单位进行数据读写,如串口、键盘等。
- 块设备驱动:支持随机访问,通常用于存储设备,如硬盘、SD卡。
- 网络设备驱动:负责网络数据包的收发,如以太网卡、Wi-Fi模块。
- 杂项设备驱动:用于管理主次设备号固定的特殊设备,简化注册流程。
- 平台设备驱动:针对片上外设(SoC内部资源)的抽象驱动模型。
- 总线设备驱动:涵盖PCI、USB、I2C、SPI等物理或逻辑总线上的设备控制。
驱动模型的核心组成
Linux内核通过三大关键结构实现设备与驱动的统一管理:
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 *);
};
这三者共同构建了设备-总线-驱动的匹配机制,实现了设备热插拔、自动绑定等功能。
设备树(Device Tree)的支持机制
现代嵌入式系统广泛使用设备树来描述硬件配置,避免驱动与具体平台硬编码绑定。以下是一个典型的设备树片段示例:
/ {
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>;
};
};
};
字符设备驱动的实现架构
作为最常见的驱动类型之一,字符设备驱动需完成设备号分配、cdev注册及用户空间接口对接等工作。其基本框架如下:
#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);
if (ret) {
unregister_chrdev_region(devno, DEVICE_COUNT);
return ret;
}
// 创建设备节点(可选)
char_class = class_create(THIS_MODULE, CLASS_NAME);
char_device = device_create(char_class, NULL, devno, NULL, DEVICE_NAME);
return 0;
}
该结构确保了用户程序可通过标准系统调用(open/read/write/ioctl等)与底层硬件交互。

3.2 字符设备操作流程四、块设备驱动架构 4.1 块设备核心结构 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; // 设备容量(以扇区为单位) }; 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; }; 4.2 请求队列处理 // 块设备初始化示例 static int myblk_init(void) { struct request_queue *queue; struct gendisk *gd; // 1. 分配gendisk结构体,支持MYDEV_MINORS个分区 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; } // 请求处理函数实现 static void mydev_request(struct request_queue *q) { struct request *req; // 循环获取并处理每一个I/O请求 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); } } 五、网络设备驱动架构 5.1 网络设备核心结构 // 网络设备结构体定义 struct net_device { char name[IFNAMSIZ]; // 网络设备名称 unsigned long mem_end; // 共享内存区域结束地址 unsigned long mem_start; // 共享内存区域起始地址 unsigned long base_addr; // I/O寄存器基地址 unsigned int irq; // 使用的中断号 const struct net_device_ops *netdev_ops; // 设备操作函数集 };
// 网络设备结构体关键字段定义
struct net_device {
const struct ethtool_ops *ethtool_ops; // ethtool操作接口
unsigned int flags; // 设备状态与功能标志
int mtu; // 最大传输单元配置
unsigned char *dev_addr; // 网络设备MAC地址指针
struct net_device_stats stats; // 数据收发统计信息
void *priv; // 驱动私有数据区域
};
// 定义网络设备的操作函数集合
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)(...);
int (*ndo_set_mac_address)(struct net_device *dev, void *addr);
int (*ndo_do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
};
5.2 示例:网络设备驱动实现
// 驱动模块初始化函数
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);
if (!dev)
return -ENOMEM;
// 步骤2:随机生成并设置硬件MAC地址
eth_hw_addr_random(dev);
// 步骤3:绑定设备操作函数集
dev->netdev_ops = &mynet_ops;
dev->ethtool_ops = &mynet_ethtool_ops;
// 步骤4:配置MTU及设备标志位
dev->mtu = 1500;
dev->flags |= IFF_NOARP;
// 步骤5:向内核注册该网络设备
ret = register_netdev(dev);
if (ret) {
free_netdev(dev);
return ret;
}
return 0;
}
// 数据包发送处理函数
static netdev_tx_t mynet_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct mynet_priv *priv = netdev_priv(dev);
// 操作步骤1:暂停发送队列以确保独占访问
netif_stop_queue(dev);
// 操作步骤2:将数据包内容复制至IO映射的发送缓冲区
memcpy_toio(priv->tx_buffer, skb->data, skb->len);
// 操作步骤3:触发硬件开始传输
outl(TX_START, priv->ioaddr + TX_REG);
// 操作步骤4:更新发送统计计数
dev->stats.tx_packets++;
dev->stats.tx_bytes += skb->len;
// 操作步骤5:释放套接缓冲区资源
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
六、输入子系统驱动架构
6.1 输入子系统核心组成结构
// 输入设备驱动代码示例 #include <linux/input.h>// 输入设备初始化流程 static int myinput_init(void) { int error; // 第一步:动态分配输入设备结构体 input_dev = input_allocate_device(); if (!input_dev) return -ENOMEM; // 第二步:设定设备基本信息 input_dev->name = "My Input Device"; input_dev->id.bustype = BUS_USB; input_dev->id.vendor = 0x1234; input_dev->id.product = 0x5678; input_dev->id.version = 0x0100; // 第三步:声明设备支持的事件类型struct input_dev *input_dev;
// 设置输入事件类型
__set_bit(EV_KEY, input_dev->evbit); // 启用按键事件
__set_bit(EV_REL, input_dev->evbit); // 启用相对坐标事件
__set_bit(EV_ABS, input_dev->evbit); // 启用绝对坐标事件
// 配置支持的按键码
__set_bit(BTN_LEFT, input_dev->keybit); // 支持左键
__set_bit(BTN_RIGHT, input_dev->keybit); // 支持右键
__set_bit(BTN_MIDDLE, input_dev->keybit); // 支持中键
// 定义绝对坐标的参数范围
input_set_abs_params(input_dev, ABS_X, 0, 1023, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 767, 0, 0);
// 注册输入设备到系统
error = input_register_device(input_dev);
if (error) {
input_free_device(input_dev);
return error;
}
return 0;
}
// 事件上报函数,用于提交输入数据
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_report_key(input_dev, BTN_RIGHT, button & 0x02);
// 发送同步事件,标志本次上报结束
input_sync(input_dev);
}
七、Platform总线驱动架构
7.1 Platform设备/驱动模型
// 定义Platform设备所使用的资源
static struct resource mydev_resources[] = {
[0] = {
.start = 0x10000000, // 内存起始物理地址
.end = 0x1000FFFF, // 内存结束地址
.flags = IORESOURCE_MEM, // 资源类型:内存
},
[1] = {
.start = IRQ_NUM, // 中断号起始
.end = IRQ_NUM, // 中断号结束
.flags = IORESOURCE_IRQ, // 资源类型:中断
},
};
// 构建Platform设备实例
static struct platform_device mydev_device = {
.name = "my-platform-device", // 设备名称
.id = -1, // 自动编号
.num_resources = ARRAY_SIZE(mydev_resources), // 资源数量
.resource = mydev_resources, // 指向资源数组
.dev = {
.platform_data = &mydev_pdata, // 绑定平台特定数据
},
};
// Platform驱动中的probe函数实现
static int mydev_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
int irq;
// 第一步:获取设备的内存资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = ioremap(res->start, resource_size(res));
// 获取中断资源
irq = platform_get_irq(pdev, 0);
// 第二步:取得平台数据指针
struct mydev_platform_data *pdata = dev_get_platdata(&pdev->dev);
// 第三步:进行硬件初始化操作
// (具体初始化代码省略)
return 0;
}
// 驱动移除时调用的清理函数
static int mydev_remove(struct platform_device *pdev)
{
// 执行必要的资源释放工作
return 0;
}
// 注册Platform驱动结构体
static struct platform_driver mydev_driver = {
.probe = mydev_probe,
.remove = mydev_remove,
.driver = {
.name = "my-platform-device",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(mydev_of_match),
},
};
八、I2C/SPI总线驱动架构
8.1 I2C驱动示例
struct input_dev *input_dev;
// I2C设备结构定义 struct i2c_client { unsigned short flags; // 标志位信息 unsigned short addr; // 7位从设备地址 char name[I2C_NAME_SIZE]; // 设备名称字段 struct i2c_adapter *adapter; // 关联的I2C适配器指针 struct device dev; // 内嵌设备模型结构 int irq; // 使用的中断号 }; // 驱动框架高级特性 10.1 电源管理 // 定义电源管理操作集 static const struct dev_pm_ops mydev_pm_ops = { .suspend = mydev_suspend, .resume = mydev_resume, .freeze = mydev_freeze, .thaw = mydev_thaw, .poweroff = mydev_poweroff, .restore = mydev_restore, .runtime_suspend = mydev_runtime_suspend, .runtime_resume = mydev_runtime_resume, .runtime_idle = mydev_runtime_idle, }; 10.3 中断处理 // 中断服务例程实现 static irqreturn_t mydev_interrupt(int irq, void *dev_id) { struct mydev *dev = dev_id; u32 status; // 获取当前中断状态寄存器值 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) { int ret; ret = request_irq(dev->irq, mydev_interrupt,九、驱动模型注册流程 // I2C驱动结构体初始化 static struct i2c_driver my_i2c_driver = { .driver = { .name = "my_i2c_device", .owner = THIS_MODULE, .of_match_table = my_i2c_of_match, }, .probe = my_i2c_probe, .remove = my_i2c_remove, .id_table = my_i2c_id, }; // 匹配设备树节点的兼容性列表 static const struct of_device_id my_i2c_of_match[] = { { .compatible = "vendor,my-i2c-device" }, {}, }; MODULE_DEVICE_TABLE(of, my_i2c_of_match); 10.2 DMA支持 // 初始化DMA相关资源 static int mydev_dma_setup(struct mydev *dev) { // 分配一致性内存用于DMA传输 dev->dma_buf = dma_alloc_coherent(&pdev->dev, DMA_BUF_SIZE, &dev->dma_handle, GFP_KERNEL); // 配置设备支持的DMA寻址能力 dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); // 申请专用DMA通道 dev->dma_chan = dma_request_channel(mask, mydev_dma_filter, NULL); return 0; } // I2C读写操作封装函数 static int my_i2c_read_reg(struct i2c_client *client, u8 reg, u8 *val) { 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); }
十一、驱动调试与测试
11.1 调试方法
#define DEBUG #undef PDEBUG #ifdef DEBUG # ifdef __KERNEL__ # define PDEBUG(fmt, args...) printk(KERN_DEBUG "mydev: " fmt, ## args) # else # define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) # endif #else # define PDEBUG(fmt, args...) #endif
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);
seq_printf(m, " Devices: %d\n", device_count);
return 0;
}
static int __init mydev_debugfs_init(void)
{
struct dentry *dir;
dir = debugfs_create_dir("mydev", NULL);
debugfs_create_u32("debug_level", 0644, dir, &debug_level);
debugfs_create_file("registers", 0444, dir, NULL, ®isters_fops);
return 0;
}
十二、Linux驱动架构特点总结
- 分层架构:从硬件抽象层到驱动核心层,再到子系统层和用户接口层,形成清晰的层次结构。
- 统一模型:采用设备-总线-驱动模型,支持热插拔机制,提升系统动态性。
- 设备树:实现硬件描述与驱动代码的分离,增强可移植性。
- 模块化设计:支持驱动模块的动态加载与卸载,便于开发和维护。
- 多平台兼容:适用于x86、ARM、MIPS、PowerPC等多种处理器架构。
- 电源管理:提供完整的电源管理框架,支持低功耗模式。
- DMA支持:具备高效的数据传输能力,减轻CPU负担。
- 中断处理:支持多种中断处理模式,适应不同硬件需求。
- 同步机制:集成自旋锁、互斥锁、信号量、完成量等并发控制手段。
- 内存管理:提供kmalloc、vmalloc以及DMA映射等灵活的内存分配方式。
该架构设计使Linux能够广泛支持从嵌入式设备到高性能服务器,涵盖从简单字符设备到复杂网络设备的多样化硬件平台。


雷达卡


京公网安备 11010802022788号







