news 2026/9/2 23:57:21

Linux内核驱动——中断子系统与 I2C 子系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux内核驱动——中断子系统与 I2C 子系统

目录

一、中断子系统

1.1 中断子系统架构

1.2 代码实例分析

1.2.1 tasklet

1.2.2 workqueue

1.3 对比总结

二、I2C 子系统

2.1 I2C 子系统架构

2.2 代码实例分析:LM75 温度传感器驱动

2.2.1 设备树配置

2.2.2 驱动注册与设备绑定

2.2.3 数据传输实现

2.2.4 应用层数据解析

三、总结与应用场景

3.1 核心设计思想

3.2 典型应用组合

3.3 开发关键要点


一、中断子系统

1.1 中断子系统架构

中断是 CPU 响应外部设备请求的一种机制。当某个硬件(如按键)发生事件时,会向 CPU 发送一个中断信号,触发中断服务程序执行。

Linux内核采用 “顶半部 + 底半部” 的设计思想来优化中断处理效率:

  • 顶半部(上半部,Top Half):快进快出, 仅完成 “紧急操作”(如置标志位、保存数据),不能阻塞、不能休眠(运行在中断上下文)。
  • 底半部(下半部,Bottom Half):处理非紧急、耗时操作(如数据解析、唤醒应用),运行在中断上下文或进程上下文,核心实现有两种:
    • tasklet:基于软中断实现,不能阻塞、不能休眠(中断上下文),适合短耗时处理;
    • workqueue:封装为普通内核线程(进程上下文),可以休眠、阻塞,适合长耗时操作。
中断子系统

1.2 代码实例分析

1.2.1 tasklet

// 底半部:tasklet(不能休眠) static struct tasklet_struct tsk; static void key_tasklet_handler(unsigned long arg) { condition = 1; wake_up_interruptible(&wq); // 唤醒应用层阻塞 printk("key_tasklet_handler arg = %ld\n", arg); } // 顶半部:中断触发后快速调度底半部 static irqreturn_t key_irq_handler(int irq, void * dev) { int arg = *(int *)dev; if(100 != arg) return IRQ_NONE; // 确认是目标中断 tasklet_schedule(&tsk); // 调度tasklet printk("irq = %d dev = %d\n", irq, arg); return IRQ_HANDLED; } // 初始化 tasklet_init(&tsk, key_tasklet_handler, 100);

关键点分析

  • 在中断处理函数中,通过 tasklet_schedule 调度 tasklet
  • tasklet 在软中断上下文中执行,不能睡眠
  • 适合快速处理按键事件,但不适合执行耗时操作

1.2.2 workqueue

// 底半部:workqueue(可以休眠) static struct work_struct work; static void key_work_func(struct work_struct *work) { ssleep(1); // 休眠1秒 condition = 1; wake_up_interruptible(&wq); printk("key_work_func ...\n"); } // 顶半部:中断触发后快速调度底半部 static irqreturn_t key_irq_handler(int irq, void * dev) { int arg = *(int *)dev; if(100 != arg) return IRQ_NONE; // 确认是目标中断 schedule_work(&work); // 调度workqueue printk("irq = %d dev = %d\n", irq, arg); return IRQ_HANDLED; } // 初始化 INIT_WORK(&work, key_work_func);

关键点分析:

  • 在中断处理函数中,通过 schedule_work 调度 work
  • work 在普通线程上下文中执行,可以调用 ssleep 等可能睡眠的函数
  • 适合执行可能耗时的操作,如 I/O 操作、复杂计算等操作

1.3 对比总结

特性taskletworkqueue
是否可阻塞
是否可休眠
是否可被调度
运行上下文中断上下文进程上下文
并发控制同类型 tasklet 串行执行可配置并发策略
适用场景紧急任务非紧急任务

二、I2C 子系统

2.1 I2C 子系统架构

Linux I2C 子系统采用分层设计,使驱动开发更加模块化和可重用。I2C子系统分为多个层次:

  • 硬件层:物理 I2C 控制器和设备
  • I2C总线驱动层(I2C Adapter):实现 I2C 总线通信协议,与硬件交互
  • I2C核心层(I2C Core):提供 I2C 总线通信的公共接口和数据结构
  • I2C设备驱动层(I2C Client):针对特定 I2C 设备的驱动程序
  • 应用层:用户空间应用程序
I2C子系统

2.2 代码实例分析:LM75 温度传感器驱动

2.2.1 设备树配置

在设备树中正确配置 I2C 设备节点:

lm75@48 { compatible = "ti,lm75"; reg = <0x48>; };

2.2.2 驱动注册与设备绑定

I2C 设备驱动通过 i2c_add_driver() 注册,核心是 of_device_id 匹配设备树中的 compatible 属性:

// 探测函数 static int probe(struct i2c_client * client, const struct i2c_device_id * device) { int ret = misc_register(&misc_dev); if(ret < 0) goto err_misc_register; lm75_client = client; printk("lm75 probe\n"); return 0; err_misc_register: printk("lm75 probe misc_register failed\n"); return ret; } // 设备树匹配表(与dts中lm75节点的compatible一致) static const struct of_device_id of_lm75_table[] = { {.compatible = "ti,lm75"}, {} }; // I2C驱动结构体 static struct i2c_driver lm75_driver = { .probe = probe, // 设备匹配成功后执行 .remove = remove, .driver = {.name = DEV_NAME, .of_match_table = of_lm75_table}, .id_table = lm75_table };

probe() 函数的核心工作:注册 misc 设备(提供 /dev/lm75 节点),保存 i2c_client 指针(用于后续通信)。

2.2.3 数据传输实现

I2C 设备驱动通过 i2c_msg 结构体描述通信数据,调用 master_xfer() 完成底层传输:

static ssize_t read(struct file * file, char __user * buf, size_t size, loff_t * loff) { int ret = 0; unsigned char temp[2] = {0}; // 构造I2C读取消息:从lm75地址读取2字节数据 struct i2c_msg msg = { .addr = lm75_client->addr, // 设备从机地址 .flags = I2C_M_RD, // 读操作 .len = 2, // 数据长度 .buf = temp // 数据缓冲区 }; // 调用Adapter的master_xfer执行传输 lm75_client->adapter->algo->master_xfer(lm75_client->adapter, &msg, 1); ret = copy_to_user(buf, temp, sizeof(temp)); // 数据传递给应用层 return sizeof(temp); }

2.2.4 应用层数据解析

应用层通过访问 /dev/lm75 读取原始数据,按 LM75 的寄存器格式解析为温度值:

#include <linux/i2c-dev.h> int main(int argc, const char *argv[]) { int fd = open("/dev/lm75", O_RDWR); if (fd < 0) { perror("open iic failed"); return 1; } unsigned char temp[2] = {0}; while(1) { int ret = read(fd, temp, sizeof(temp)); float t = 0.5 * ((temp[0] << 8 | temp[1]) >> 7); printf("t = %.1f\n", t); sleep(1); } close(fd); return 0; }

工作流程:

  1. 打开 /dev/lm75 设备节点。
  2. 每秒读取一次温度寄存器(共2字节)。
  3. 解析原始数据:0.5 × (value >> 7) 得到摄氏度。
  4. 输出当前温度。

注意:temp[0] << 8 | temp[1] 组合成 16 位值,右移 7 位去除小数部分,乘以 0.5 得到真实温度。

三、总结与应用场景

3.1 核心设计思想

  • 中断子系统:快慢分离,顶半部保证响应速度,底半部处理耗时逻辑,平衡实时性与效率;
  • I2C 子系统:分层解耦,核心层屏蔽底层差异,设备驱动专注外设逻辑,降低开发复杂度。

3.2 典型应用组合

实际开发中,两个子系统常结合使用:例如 I2C 传感器(如 LM75)配置中断引脚,当温度超过阈值时触发中断,中断底半部调度 I2C 数据读取,应用层通过阻塞等待获取最新数据,实现 “事件触发 + 高效通信” 的闭环。

3.3 开发关键要点

  • 中断驱动:注意资源释放(free_irq())、底半部选择(根据是否需要休眠)、阻塞等待的正确使用;
  • I2C 驱动:确保设备树 compatible 属性匹配、i2c_msg 参数正确(地址、长度、读写标志)、数据格式按外设手册解析。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 22:56:19

‍AI客服软件:应对高复杂售前咨询 图书行业客服能力的进化路径

图书行业一直被认为是“低客单、重咨询”的典型代表。看似标准化的商品背后&#xff0c;实际承载的是高度复杂的决策过程&#xff1a;版本差异、出版社不同、教材地区适配、内容体系差异、是否配套音视频资源……任何一个环节解释不清&#xff0c;都会直接影响用户是否下单。在…

作者头像 李华
网站建设 2026/8/30 6:24:06

Kafka 与大数据存储系统的完美融合方案

Kafka 与大数据存储系统的完美融合方案 关键词:Kafka、大数据存储、实时数据流、数据融合、分布式系统 摘要:在数字化时代,企业每天产生海量实时数据(如用户行为、IoT传感器、交易记录),这些数据需要“既快又稳”地被处理和存储。Kafka作为全球最流行的实时数据流平台,擅…

作者头像 李华
网站建设 2026/9/2 14:09:18

cv_resnet50_face-reconstruction VSCode Python环境配置详解

cv_resnet50_face-reconstruction VSCode Python环境配置详解 想在自己的电脑上跑通那个能从一张照片生成3D人脸的神奇模型吗&#xff1f;很多朋友在第一步——配置开发环境上就卡住了。网上的教程要么太零散&#xff0c;要么默认你已经是个Python老手&#xff0c;对新手一点都…

作者头像 李华
网站建设 2026/8/26 7:08:47

零基础教程:用亚洲美女-造相Z-Turbo一键生成惊艳人像

零基础教程&#xff1a;用亚洲美女-造相Z-Turbo一键生成惊艳人像 最近想用AI生成一些符合亚洲审美的精致人像&#xff0c;但发现很多模型要么对中文理解不好&#xff0c;要么生成的亚洲面孔总带着点“异域风情”&#xff0c;要么就是部署起来太复杂&#xff0c;对电脑配置要求…

作者头像 李华
网站建设 2026/9/2 18:44:25

小白必看!Cosmos-Reason1-7B推理工具保姆级使用教程

小白必看&#xff01;Cosmos-Reason1-7B推理工具保姆级使用教程想用AI解决数学题、逻辑推理或编程问题&#xff0c;但担心隐私泄露或使用限制&#xff1f;这个纯本地运行的推理工具可能是你的最佳选择你是否遇到过这样的情况&#xff1a;遇到复杂的数学题需要一步步推理&#x…

作者头像 李华