当前位置: 首页 > news >正文

第10章:中断处理-6:Implementing a Handler

In continuation of the previous text 第10章:中断处理-5:Fast and Slow Handlers,  let's GO ahead.

Implementing a Handler

So far, we’ve learned to register an interrupt handler but not to write one. Actually, there’s nothing unusual about a handler—it’s ordinary C code.

到目前为止,我们已经学会了注册中断处理程序,但还没学习如何编写它。实际上,中断处理程序并没有什么特别之处 —— 它就是普通的 C 代码。

The only peculiarity is that a handler runs at interrupt time and, therefore, suffers some restrictions on what it can do. These restrictions are the same as those we saw with kernel timers. A handler can’t transfer data to or from user space, because it doesn’t execute in the context of a process. Handlers also cannot do anything that would sleep, such as calling wait_event, allocating memory with anything other than GFP_ATOMIC, or locking a semaphore. Finally, handlers cannot call schedule.

唯一的特殊之处在于,处理程序运行在中断上下文中,因此其能执行的操作受到一些限制。这些限制与我们在 kernel 定时器中看到的相同:

  • 无法与用户空间传输数据,因为它不运行在任何进程的上下文中;

  • 不能执行任何可能导致睡眠的操作,比如调用 wait_event、使用非 GFP_ATOMIC 标志分配内存,或是锁定信号量;

  • 最终,处理程序不能调用 schedule(进程调度函数)。

The role of an interrupt handler is to give feedback to its device about interrupt reception and to read or write data according to the meaning of the interrupt being serviced. The first step usually consists of clearing a bit on the interface board; most hardware devices won’t generate other interrupts until their “interrupt-pending” bit has been cleared. Depending on how your hardware works, this step may need to be performed last instead of first; there is no catch-all rule here. Some devices don’t require this step, because they don’t have an “interrupt-pending” bit; such devices are a minority, although the parallel port is one of them. For that reason, short does not have to clear such a bit

A typical task for an interrupt handler is awakening processes sleeping on the device if the interrupt signals the event they’re waiting for, such as the arrival of new data.

To stickwith the frame grabber example, a process could acquire a sequence of images by continuously reading the device; the read call blocks before reading each frame, while the interrupt handler awakens the process as soon as each new frame arrives. This assumes that the grabber interrupts the processor to signal successful arrival of each new frame.

中断处理程序的作用是:向设备反馈 “中断已接收”,并根据当前中断的含义读取或写入数据。

  1. 清除中断挂起位:第一步通常是清除接口板上的某个标志位 —— 大多数硬件设备在 “中断挂起位” 被清除前,不会产生新的中断。不过,根据硬件工作方式,这一步也可能需要放在最后(无统一规则)。有些设备无需此步骤(如并行端口),因为它们没有 “中断挂起位”,这类设备并不常见。因此 short 模块不需要清除该位。

  2. 唤醒等待进程:处理程序的典型任务是,若中断表示等待事件已发生(如新数据到达),则唤醒在该设备上睡眠的进程。

以帧捕获器为例:进程可通过持续读取设备获取图像序列,每次读取帧前,read 调用会阻塞;而每当新帧到达,中断处理程序就会唤醒该进程 —— 前提是捕获器会通过中断通知处理器 “新帧已成功到达”。

The programmer should be careful to write a routine that executes in a minimum amount of time, independent of its being a fast or slow handler. If a long computa tion needs to be performed, the best approach is to use a tasklet or workqueue to schedule computation at a safer time (we’ll lookat how workcan be deferred in this manner in the section “Top and Bottom Halves.”) 

无论处理程序是快速还是慢速类型,开发者都应确保它的执行时间尽可能短。若需要执行长时间计算,最佳方式是使用 tasklet 或 workqueue,将计算任务调度到更安全的时机执行(我们将在 “上半部与下半部” 章节中介绍如何延迟执行这类任务)。

Our sample code in short responds to the interrupt by calling do_gettimeofday and printing the current time into a page-sized circular buffer. It then awakens any read ing process, because there is now data available to be read.

short 模块中的中断处理程序逻辑很简单:调用 do_gettimeofday 获取当前时间,将其打印到一个页大小的循环缓冲区中,然后唤醒所有正在读取该设备的进程 —— 因为此时已有可读取的数据。

这段代码完整展示了 short 模块的中断处理逻辑,以及 /dev/shortint 设备文件的读写实现,是中断驱动开发的典型示例。核心功能是记录中断发生时间、更新循环缓冲区,并唤醒等待读取的进程,完全遵循中断上下文的编程限制。

irqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{struct timeval tv;       // 用于存储当前时间戳int written;             // 记录写入缓冲区的字节数// 1. 获取当前时间(中断发生的精确时间)do_gettimeofday(&tv);    // 内核函数:获取当前秒和微秒// 2. 将时间戳格式化并写入循环缓冲区// 格式为 "秒(8位).微秒(6位)\n",共16字节(确保与缓冲区步长匹配)written = sprintf((char *)short_head, "%08u.%06u\n",(int)(tv.tv_sec % 100000000),  // 取秒的后8位(避免溢出)(int)(tv.tv_usec));            // 微秒(0~999999)// 3. 校验写入长度(确保缓冲区操作的原子性)BUG_ON(written != 16);   // 若写入不是16字节,触发内核BUG(调试用)// 4. 更新循环缓冲区的写指针(核心:避免数据不一致)short_incr_bp(&short_head, written);  // 移动写指针,超过缓冲区则环绕// 5. 唤醒等待数据的读进程wake_up_interruptible(&short_queue);  // 唤醒在short_queue上睡眠的可中断进程// 6. 返回中断处理结果return IRQ_HANDLED;      // 告知内核:本设备的中断已被处理
}

关键细节:

  • 时间记录:用 do_gettimeofday 获取时间,格式化为 “秒。微秒” 字符串,确保每条记录固定 16 字节,简化缓冲区操作;

  • BUG_ON 断言:用于调试,避免因格式错误导致缓冲区指针混乱;

  • 进程唤醒:通过 wake_up_interruptible 唤醒在 short_queue 等待队列上的读进程,实现 “中断触发数据可读” 的逻辑。

This code, though simple, represents the typical job of an interrupt handler. It, in turn, calls short_incr_bp, which is defined as follows:

这段代码虽然简单,却代表了中断处理程序的典型工作。它调用了 short_incr_bp 函数,该函数定义如下:

static inline void short_incr_bp(volatile unsigned long *index, int delta)
{unsigned long new = *index + delta;  // 计算新指针位置barrier();  /* 禁止编译器优化:确保new计算完成后再更新index */// 若新指针超过缓冲区末尾,则环绕到起始位置*index = (new >= (short_buffer + PAGE_SIZE)) ? short_buffer : new;
}

This function has been carefully written to wrap a pointer into the circular buffer without ever exposing an incorrect value. The barrier call is there to blockcompiler optimizations across the other two lines of the function. Without the barrier, the compiler might decide to optimize out the new variable and assign directly to *index. That optimization could expose an incorrect value of the index for a brief period in the case where it wraps. By taking care to prevent in inconsistent value from ever being visible to other threads, we can manipulate the circular buffer pointers safely without locks.

这个函数经过精心设计,用于更新循环缓冲区的指针并实现环绕,且不会暴露任何不正确的值。barrier 调用的作用是阻止编译器对函数中另外两行代码进行跨语句优化。如果没有 barrier,编译器可能会优化掉 new 变量,直接对 *index 进行赋值操作。这种优化在指针环绕的情况下,可能会导致 index 的值在短时间内出现错误。通过防止不一致的值被其他线程看到,我们无需加锁就能安全地操作循环缓冲区的指针。

关键细节:

  • volatile 修饰指针:防止编译器将指针值缓存到寄存器(确保每次读取内存中的最新值)。

  • barrier() 作用:阻止编译器将 new 的计算与 *index 的赋值合并为一条指令。若没有此 barrier,可能出现 “指针暂时超过缓冲区” 的中间状态,导致读进程看到错误值。

  • 无锁设计:通过 “先计算新值,再原子赋值” 确保指针更新的原子性,避免多线程(中断上下文与读进程)访问冲突。

The device file used to read the buffer being filled at interrupt time is /dev/shortint. This device special file, together with /dev/shortprint, wasn’t introduced in Chapter 9, because its use is specific to interrupt handling. The internals of /dev/ shortint are specifically tailored for interrupt generation and reporting. Writing to the device generates one interrupt every other byte; reading the device gives the time when each interrupt was reported.

用于读取中断时填充的缓冲区的设备文件是 /dev/shortint。这个特殊设备文件以及 /dev/shortprint 在第 9 章中没有介绍,因为它们的用途专门针对中断处理。/dev/shortint 的内部实现是为中断生成和报告量身定制的:向该设备写入数据时,每写入一个字节就会触发一次中断;读取该设备时,会获得每次中断被报告的时间。

If you connect together pins 9 and 10 of the parallel connector, you can generate interrupts by raising the high bit of the parallel data byte. This can be accomplished by writing binary data to /dev/short0 or by writing anything to /dev/shortint.*

The following code implements read and write for /dev/shortint:

如果将并行端口连接器的 9 号引脚和 10 号引脚短接,就可以通过置位并行数据字节的最高位来生成中断。这可以通过向 /dev/short0 写入二进制数据,或向 /dev/shortint 写入任意数据来实现 *。以下代码实现了 /dev/shortint 的读操作和写操作:

ssize_t short_i_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{int count0;                  // 可读取的有效数据长度DEFINE_WAIT(wait);           // 定义等待队列项(栈上分配)// 1. 若缓冲区为空,进入等待状态while (short_head == short_tail) {  // 头指针=尾指针 → 缓冲区空prepare_to_wait(&short_queue, &wait, TASK_INTERRUPTIBLE);// 再次检查(防止唤醒后数据已被其他进程取走)if (short_head == short_tail)schedule();         // 让出CPU,进入睡眠finish_wait(&short_queue, &wait);  // 退出等待队列// 若等待中收到信号,返回中断错误(用户态可重试)if (signal_pending(current))return -ERESTARTSYS;}// 2. 计算可读取的数据量(处理缓冲区环绕情况)count0 = short_head - short_tail;if (count0 < 0)              // 头指针 < 尾指针 → 数据已环绕count0 = short_buffer + PAGE_SIZE - short_tail;// 限制读取长度不超过用户请求和可用数据量if (count0 < count)count = count0;// 3. 将数据从内核缓冲区复制到用户空间if (copy_to_user(buf, (char *)short_tail, count))return -EFAULT;          // 复制失败(用户空间地址无效)// 4. 更新读指针short_incr_bp(&short_tail, count);// 5. 返回成功读取的字节数return count;
}ssize_t short_i_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{int written = 0, odd = *f_pos & 1;  // odd:文件偏移量的奇偶性(用于交替触发中断)unsigned long port = short_base;    // 并行端口数据寄存器地址void *address = (void *)short_base; // 内存映射方式的端口地址if (use_mem) {  // 若使用内存映射访问端口(I/O内存方式)while (written < count)// 交替写入0xff(最高位1)和0x00(最高位0),触发中断(9号引脚电平变化)iowrite8(0xff * ((++written + odd) & 1), address);} else {        // 若使用I/O端口访问(inb/outb方式)while (written < count)outb(0xff * ((++written + odd) & 1), port);}*f_pos += count;  // 更新文件偏移量return written;   // 返回写入的字节数
}

关键逻辑说明:

  • 中断触发原理:通过交替写入 0xff(最高位 1)和 0x00(最高位 0),使 9 号引脚电平变化(因 9 号与 10 号引脚短接),进而触发并行端口中断。

  • 两种访问方式:支持 I/O端口outb)和 I/O内存iowrite8),适配不同硬件访问模式。

The other device special file, /dev/shortprint, uses the parallel port to drive a printer; you can use it if you want to avoid connecting pins 9 and 10 of a D-25 connector. The write implementation of shortprint uses a circular buffer to store data to be printed, while the read implementation is the one just shown (so you can read the time your printer takes to eat each character).

In order to support printer operation, the interrupt handler has been slightly modi fied from the one just shown, adding the ability to send the next data byte to the printer if there is more data to transfer.

另一个特殊设备文件 /dev/shortprint 利用并行端口驱动打印机:如果不想短接 D-25 连接器的 9 号和 10 号引脚,可以使用这个设备。shortprint 的写操作实现使用循环缓冲区存储待打印的数据,而读操作实现则与上面展示的相同(因此你可以读取打印机处理每个字符所花费的时间)。

为了支持打印机操作,上面展示的中断处理程序已做了轻微修改,增加了 “若有更多数据需要传输,则向打印机发送下一个数据字节” 的功能。

补充总结:中断处理的核心链路

  1. 触发中断:用户向 /dev/shortint 写入数据 → 写操作交替改变并行端口 9 号引脚电平 → 因 9 号与 10 号引脚短接,触发中断。

  2. 处理中断short_interrupt 被调用 → 记录时间戳到循环缓冲区 → 唤醒等待的读进程。

  3. 读取数据:读进程从循环缓冲区获取时间戳 → 更新读指针 → 完成数据读取。

整个流程通过 “中断上下文记录数据 + 进程上下文读取数据 + 等待队列同步” 实现了中断事件的捕获与用户态交互。

http://www.dtcms.com/a/556921.html

相关文章:

  • 伊利网站建设评价多少钱?
  • Spring集成Mybatis-Plus(适用于非Springboot项目)
  • 做网站需要服务器么wordpress弹幕播放器
  • uni-app 请求封装
  • Less-7 GET-Dump into outfile-String
  • Windows系统暂停强制更新的操作(超详细说明)
  • Leetcode 43
  • 力扣每日一题——接雨水
  • 基于AWS Lambda事件驱动架构与S3智能生命周期管理的制造数据自动化处理方案
  • 营商环境建设网站建设公司网站的必要性
  • 小网站广告投放网站做支付需要准备什么东西吗
  • 第六届“大湾区杯”粤港澳金融数学建模竞赛赛题浅析-助攻快速选题
  • 【车载Android】使用自定义插件实现多语言自动化适配
  • 学习网站建设要什么学历网站颜色表
  • C++ 分治 归并排序解决问题 力扣 315. 计算右侧小于当前元素的个数 题解 每日一题
  • Linux UdpSocket的应用
  • docker compose 创建MySQL8后在容器里备份数据到宿主机(.sql文件)的方式
  • 南昌网站外包几何图形生成网站
  • 《算法通关指南:数据结构和算法篇 --- 顺序表相关算法题》--- 询问学号,寄包柜,合并两个有序数组
  • OS_3 Memory、4 File、5 IO
  • Jenkins vs Tekton vs Arbess,CI/CD工具一文纵评
  • 如何挑选中药饮片供应商才能确保产品质量与安全?
  • 自己制作的网站如何发布素材网站都有哪些
  • 双非大学生自学鸿蒙5.0零基础入门到项目实战 -《基础篇》
  • webrtc代码走读(十四)-QOS-Jitter
  • 计算机网络经典问题透视:当路由器需要同时连接以太网和ATM网络时,需要添加什么硬件?
  • IntelliJ IDEA从安装到使用:零基础完整指南
  • 怎么做局域网asp网站做网站1天转多钱
  • Oracle常用
  • [VT-Refine] Simulation | Fine-Tuning | docker/run.sh