转自:http://blog.163.com/vic_kk/blog/static/494705242010719483774/

第一部分在这里

1.5 Interrupt Flow Handling (中断流处理)

前面提到了
IRQ 子系统的三个 抽象层: High-level interrupt Service routine, Interrupt Flow
Handling, Chip-level hardware Encapsulation. 本节描述 Interrupt Flow
Handling 的过程, 即, 处理边沿触发、水平触发等等的过程。

1.5.1 设置控制硬件 (Setting Controller Hardware)

内核提供了一些标准函数用于注册 irq_chips 和设置 flow handlers ,如下:

int  set_irq_chip(unsigned int irq, struct irq_chip *chip);
int set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
int set_irq_chip_data(unsigned int irq, void *data);
void set_irq_chip_and_handler(unsigned int irq, struct irq_chip *chip,
irq_flow_handler_t handle);
void set_irq_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
irq_flow_handler_t handle, const char *name);
  • set_irq_chip:
    该函数用于将一个 IRQ Chip (irq_chip 的实例) 与一个特定的中断相关联。 除了从 irq_desc 中选择合适的元素外,该函数还负责设置 chip 中的指针, 并且设置默认的中断处理函数。
  • set_irq_handler 和 set_irq_chained_handler:
    为指定的 IRQ 设置 flow handler function, 两者在内部均使用 __set_irq_handler , __set_irq_handler 进行一些检查,并设置 irq_desc[irq]->handle_irq.
  • set_chip_and_handler:
    一个辅助函数,使用它可以避免逐个的使用前面提到的三个函数。

1.5.2 Flow Handling

前面提到,中断有不同的触发方式: edge-trggering 或者 level-triggering, 内核对他们的处理方式有所不同。 但相同的是,这两个处理中,都需要在 flow-handling 完成后负责调用 high-level 的中断处理函数。

  • 边沿触发中断 (Edge-Triggered Interrupts)

    边沿触发方式在现代的硬件中是最常见的一种方式,该方式的默认处理函数为 handle_edge_irq .

    边沿触发中断,在处理时候一般无需禁用该中断源。这样,在多处理器系统上,可能会出现这种情况:
    一个处理器上正在处理这个Flow-Handnle,此时在这个处理过程尚未完成的时候,又产生了另外一个边沿触发的中断。 对于这种情况,
    handler_edge_irq 的处理方法是:先更新这个 irq 的 irq_desc 上的状态为 PENDING, 然后暂时将这个中断
    mask 掉(这里的 mask 是通过 irq_chip 结构提供的函数在硬件上操作,而不是内核中软件意义上的 mask); 而前面负责处理这个
    irq 的 CPU , 在处理完了一个 IRQ 后,检查这个 irq_desc 的状态,如果为 PENDING,
    则表示在前面的处理过程中又有新的中断信号产生了,需要继续处理,直到这个 irq_desc 的状态不是 PENDING 为止。

    在 Flow-Handle 的每个循环的结尾, hander_edge_irq 都会以 iqd_desc->action 为参数来通过 handleIRQevent 调用 High-level 处理函数 。

    整体流程如下图所示:

    handle_edge_irq 流程

  • 水平触发中断 (Level-Triggered Interrupts)

    Level-Triggered Interrups 由函数 handle_level_irq 负责处理,他的流程比 handle_edge_irq 要简单一些, 如下图所示:

    handle_level_irq 流程

    需要注意的是,
    Level-Triggered Interrups 在处理的时候必须要将其 Mask 掉,这个 Mask 的过程通过函数
    mask_ack_irq 来完成。 maks_ack_irq 不但将 irq_desc 的状态设置为 mask ,同时还调用了
    chip->mask_ack 来设置硬件的 mask , 并向硬件发送 ACK 。 对于多核 CPU 所潜在的竞争冒险1,可以通过检测 irq_desc 的状态来避免 —— 一旦检测到 irq_desc->status 中包含了 IRQ_INPROGRESS , 则表明该 irq 正在被处理,直接退出即可。

    同边沿触发的处理一样,这里也通过 handle_IRQ_event 来调用了 High-level 的处理函数。

  • 其他类型的中断

    除了上述两种类型的的中断之外,还有一些其他不常用的中断,内核为他们提供了默认的 handler 。

    • handle_fasteoi_irq

      很多现代的
      IRQ 硬件仅需要做一点点流处理工作 (Flow-Handle),对他们来讲,在 IRQ 处理完毕之后, 只需要调用一个
      Chip-specific 的函数: chip->eoi 就可以了, 函数 handle_fasteoi_irq 负责这个工作。

    • handle_simple_irq

      一些实在是很简单的中断根本就不需要流控制,内核为他们提供了 handle_simple_irq 。

    • handle_percpu_irq

      有些 IRQ 只能发上在 SMP 上的某个指定的 CPU 上,这种 IRQ 被成为 Per-CPU IRQ , 内核提供了 handle_percpu_irq 来处理他们。该函数负责在中断处理完成之后向硬件汇报中断的接收,并调用 EOI 。

1.6 IRQ 的初始化及预留

 

1.6.1 IRQ 的注册

函数 request_irq 用于注册 IRQ , 其定义为:

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

可见,该函数是 request_threaded_irq 的一个 wrapper ,并将 request_threaded_irq 的参数 thread_fn 设置为空。 request_threaded_irq 的流程图如下图所示:

request_threaded_irq

该函数首先从内核中获取了这个 irq 对应的 irq_desc, 然后创建 irqaction, 设置这个 action 的 handler, flags, name, 和 dev_id 。 随后,将其余的工作交给了函数 __setup_irq, 由 __setup_irq 完后后续的工作。

__setup_irq 的主要作用如下:

  • 设置内核熵池

    前面提到过,内核通过一系列的事件的相关信息来生成随机数 (/dev/random),这里,如果传入的 flags 中声明了 IRQF_SAMPLE_RANDOM ,则调用 rand_initialize_irq 将 IRQ 添加到熵池所需的相关数据结构中。

  • 添加 irqaction

    前面提到,内核有一个全局的
    irq_desc Array, 从这个 Array 中可以根据 IRQ Number 来找到相应的 irq_desc ; 此外,每个
    irq_desc 中有一个 irqaction list ,这个 list 中记录了该 irq 中断发生时候需要调用的每一个 irqaction
    。 __setup_irq 需要将传入的 irqaction 添加到这个 list 的队尾。

  • 其他的 irq flags 检查

    根据传入参数的flags, 做一些别的检查和设置,没具体细看。

  • 注册 proc 文件系统

    __setup_irq 的最后,调用 register_irq_proc ,在 proc/irq 中为相应的 IRQ 创建了节点。 然后又调用 register_handler_proc , 生成/proc/irq/NUM/NAME 。从而使用户可以从 procfs 中得到该 IRQ 的信息。

1.6.2 释放 IRQ

在释放
IRQ 的时候,仅提供一个 IRQ Number 不行的,还必须提供这个 IRQ 对应的 dev_id. 该过程由函数 free_irq
来完成, free_irq 是 __free_irq 的一个包装,调 用 __free_irq 来完成 irqaction 的注销。

__free_irq
根据传入的 dev_id 从这个 IRQ 的所有 irqaction 上找到设备对应的那个 irqaction , 并将其从 IRQ 的
irqaction List 中移走; 如果移走的这个 handler 是这个 IRQ Line 上唯一的一个, 那么还需要将这个 IRQ
Line Diable。 随后,清理 proc 文件系统中的结点, 并将 action 这个结构返回给 free_irq, 由 free_irq
负责将数据结构 free 。

1.6.3 中断的注册 (Registering Interrupts, 指系统中断)

前面提到的
irq_request 仅用于外设的 IRQ 申请, 而对于 CPU 本身已经软件上的中断,处理的流程与此不同。
诸如软件发出的中断、异常以及陷阱,这些东西的注册是在系统初始化的时候执行的,并且初始化完成之后,在整个系统的活动周期内不会发生改变。
由于系统的中断是不能共享的(也没有必要共享,资源多得很),内核所需要做的,仅仅是将中断号和处理函数相关联。

一般来讲,内核对于这种系统中断的响应有如下两种:

  • 当错误发生的时候,向当前用户进程发送信号:

    例如,在 IA-32 和 AMD64 系统,当一个数字被0除的时候,会产生中断 0 , 这个中断会调用处理函数 divide_error ,并向用户进程发送信号 —— SIGPFE 。

  • 错误发生后, 内核自动更正错误:

    例如 IA-32 系统下, 中断 14 被用来作为 page fault (内存却页或者页面错误)的信号,当这个错误发生的时候,内核可以自动对该错误进行纠正。

Footnotes:

1
竞争冒险和前面提到的边沿触发中断的嵌套不同,由于处理 Level-Triggered Interrups 时候, 中断源那个硬件已经被暂时
Mask 掉,因此在一个水平触发中断的处理期间,同一个设备不会产生另外一个水平触发中断。 因此,竞争冒险指的是多个 CPU 同时选定要处理同一个
irq 。

Professional Linux Kernel Architecture 笔记 —— 中断处理(Part 2)【转】的更多相关文章

  1. linux kernel学习笔记-5内存管理_转

    void * kmalloc(size_t size, gfp_t gfp_mask); kmalloc()第一个参数是要分配的块的大小,第一个参数为分配标志,用于控制kmalloc()的行为. km ...

  2. ubuntu: qemu+gdb 调试linux kernel 学习笔记

    声明: 本笔记内容并非本人原创,90%来自网络资料的整合.同时,由于自己是刚刚接触qemu & gdbserver remote debug,本文也就算不得教程,仅供有缘人参考而已. ---- ...

  3. linux kernel内存回收机制

    转:http://www.wowotech.net/linux_kenrel/233.html linux kernel内存回收机制 作者:itrocker 发布于:2015-11-12 20:37 ...

  4. Linux kernel的中断子系统之(六):ARM中断处理过程

    返回目录:<ARM-Linux中断系统>. 总结:二中断处理经过两种模式:IRQ模式和SVC模式,这两种模式都有自己的stack,同时涉及到异常向量表中的中断向量. 三ARM处理器在感知到 ...

  5. Linux kernel的中断子系统之(八):softirq

    返回目录:<ARM-Linux中断系统>. 总结:中断分为上半部和下半部,上半部关中断:下半部开中断,处理可以延迟的事情.下半部有workqueue/softirq/tasklet三种方式 ...

  6. LINUX kernel笔记系列 :IO块参数 图

      Linux下,I/O处理的层次可分为4层: 系统调用层,应用程序使用系统调用指定读写哪个文件,文件偏移是多少 文件系统层,写文件时将用户态中的buffer拷贝到内核态下,并由cache缓存该部分数 ...

  7. linux kernel笔记

    文章目录 关于linux内核中的__attribute__关键字 Linux kernel启动参数 gdt / ldt PCB 关于linux内核中的__attribute__关键字 part I: ...

  8. Linux Kernel - Debug Guide (Linux内核调试指南 )

    http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级 ...

  9. linux内核编程笔记【原创】

    以下为本人学习笔记,如有转载请注明出处,谢谢 DEFINE_MUTEX(buzzer_mutex); mutex_lock(&buzzer_mutex); mutex_unlock(& ...

随机推荐

  1. MAC锁屏不断网(快捷键启用屏保)

    第一步:要设定锁定输入密码的设置,进入'系统偏好设置''安全性与隐私',将选项'进入睡眠或开始屏幕保护程序后'打勾,选'立即'. 第二步:到'launchpad'中的'其他'文件夹打开'Automat ...

  2. iOS进阶--将项目的编译速度提高5倍

    前言 作为开发团队的负责人,最近因为在快速迭代开发新功能,项目规模急速增长,单个端业务代码约23万行,私有库约6万行,第三方库代码约15万行,单个客户端的代码行数约60万.现在打包一次耗时需要11~1 ...

  3. 【bzoj1634】[Usaco2007 Jan]Protecting the Flowers 护花 贪心

    题目描述 Farmer John went to cut some wood and left N (2 <= N <= 100,000) cows eating the grass, a ...

  4. 【题解】51nod 1672区间交

    二分答案 + two - pointer + 树状数组大法好ヽ(゚∀゚)メ(゚∀゚)ノ 我们可以直接二分一个答案,然后检验 是否存在一个值大于等于这个二分的答案的,且覆盖次数大于等于 \(k\) 的区 ...

  5. webpack优化总结

    1. 分包 将不需要变动的第三方包分离出去, 主要方法有: (1). externals(2). DllPlugin(3). expose-loader(4). ProviderPlugin 2. 拆 ...

  6. Install JDK In Ubuntu

    安装Linux软件包管理器rpm apt install rpm 查看已安装的软件,如JDK rpm -qa|grep jdk #查询所有 找jdk 卸载已安装的软件 rpm -e nodeps 包名 ...

  7. 第三方库安装——lxml

    环境 操作系统:CentOS 6.7 32-bit Python:2.6.6 安装 安装依赖软件 yum -y install gcc make python-devel libxml2-devel ...

  8. Codeforces Round #403 (Div. 2, based on Technocup 2017 Finals)A模拟 B三分 C dfs D map

    A. Andryusha and Socks time limit per test 2 seconds memory limit per test 256 megabytes input stand ...

  9. mybatis的mapper的特殊符号处理

    这种问题在xml处理sql的程序中经常需要我们来进行特殊处理. 其实很简单,我们只需作如下替换即可避免上述的错误: < <= > >= & ' " < ...

  10. openstack安装问题

    KeyStone NoHandlers Errorroot@openstack-dev-r910:/home/brent/openstack# ./keystone_data.shNo handler ...