news 2026/3/9 9:47:37

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/3/5 4:47:59

小红的二叉树【牛客tracker 每日一题】

小红的二叉树 时间限制&#xff1a;1秒 空间限制&#xff1a;1024M 知识点&#xff1a;数论 网页链接 牛客tracker 牛客tracker & 每日一题&#xff0c;完成每日打卡&#xff0c;即可获得牛币。获得相应数量的牛币&#xff0c;能在【牛币兑换中心】&#xff0c;换取相…

作者头像 李华
网站建设 2026/3/4 13:12:34

苹果应用隐私政策配置指南

引言 在开发iOS应用的过程中,隐私政策的配置是一个不可忽视的重要环节。苹果公司对应用的隐私保护有着严格的要求,如果不正确配置隐私信息,可能会导致应用无法通过审核。本文将详细介绍如何配置苹果应用的隐私政策,并通过一个实际案例来展示解决常见问题的步骤。 理解隐私…

作者头像 李华
网站建设 2026/3/9 7:41:48

多线程Web爬虫:如何避免超时错误

在解决LeetCode的多线程Web爬虫问题时,我发现一个有趣的现象:使用ThreadPoolExecutor时,代码可能会超时,即使是在非常简单的测试用例中。今天,我们来探讨一下为什么会发生这种情况,并提供一个优化方案。 问题分析 首先,让我们回顾一下原始的代码实现: class Solutio…

作者头像 李华
网站建设 2026/3/7 20:18:57

大数据环境下 Kafka 的集群搭建指南

大数据环境下 Kafka 的集群搭建指南 关键词&#xff1a;Kafka 集群、大数据、分布式系统、消息队列、高吞吐量 摘要&#xff1a;在大数据时代&#xff0c;如何高效处理海量实时数据流是企业的核心需求之一。Kafka 作为一款分布式消息队列&#xff0c;凭借高吞吐量、低延迟和强容…

作者头像 李华
网站建设 2026/3/5 17:09:06

智能配电监控模块:50A磁保持,负载5500W电机设备,工业配电安全新方案

智能配电监控模块是一款集大功率远程控制、每路独立电流监控和多功能自动化逻辑于一体的先进电气管理终端设备。一、核心特性 50A磁保持&#xff1a;指其核心执行单元。 能力&#xff1a;每路通道能安全承载和控制高达50安培的大电流&#xff0c;可直接驱动电机、电热器等11KW级…

作者头像 李华
网站建设 2026/3/8 4:28:25

mPLUG视觉问答工具提示词技巧:让分析更精准

mPLUG视觉问答工具提示词技巧&#xff1a;让分析更精准 1. 引言 你是否曾经遇到过这样的情况&#xff1a;上传一张图片到AI视觉问答工具&#xff0c;却得到了一个完全偏离主题的回答&#xff1f;或者明明图片中有明显的物体&#xff0c;但AI就是识别不出来&#xff1f;这往往…

作者头像 李华