Linux中断(interrupt)子系统之一:中断系统基本原理【转】

转自:http://blog.csdn.net/DroidPhone/article/details/7445825

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[-]

  1. 设备中断控制器和CPU
  2. IRQ编号
  3. 在驱动程序中申请中断
  4. 通用中断子系统Generic irq的软件抽象
  5. irq描述结构struct irq_desc
  6. 中断子系统的proc文件接口

这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于ARM这一体系架构,其他架构的原理其实也差不多,区别只是其中的硬件抽象层。内核版本基于3.3。虽然内核的版本不断地提升,不过自从上一次变更到当前的通用中断子系统后,大的框架性的东西并没有太大的改变。

 

/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.NET/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

1.  设备、中断控制器和CPU

一个完整的设备中,与中断相关的硬件可以划分为3类,它们分别是:设备、中断控制器和CPU本身,下图展示了一个smp系统中的中断硬件的组成结构:

                                                                       图 1.1  中断系统的硬件组成

        设备 
设备是发起中断的源,当设备需要请求某种服务的时候,它会发起一个硬件中断信号,通常,该信号会连接至中断控制器,由中断控制器做进一步的处理。在现代的移动设备中,发起中断的设备可以位于soc(system-on-chip)芯片的外部,也可以位于soc的内部,因为目前大多数soc都集成了大量的硬件IP,例如I2C、SPI、Display
Controller等等。

        中断控制器
 中断控制器负责收集所有中断源发起的中断,现有的中断控制器几乎都是可编程的,通过对中断控制器的编程,我们可以控制每个中断源的优先级、中断的电器类型,还可以打开和关闭某一个中断源,在smp系统中,甚至可以控制某个中断源发往哪一个CPU进行处理。对于ARM架构的soc,使用较多的中断控制器是VIC(Vector
Interrupt Controller),进入多核时代以后,GIC(General Interrupt
Controller)的应用也开始逐渐变多。

        CPU
 cpu是最终响应中断的部件,它通过对可编程中断控制器的编程操作,控制和管理者系统中的每个中断,当中断控制器最终判定一个中断可以被处理时,他会根据事先的设定,通知其中一个或者是某几个cpu对该中断进行处理,虽然中断控制器可以同时通知数个cpu对某一个中断进行处理,实际上,最后只会有一个cpu相应这个中断请求,但具体是哪个cpu进行响应是可能是随机的,中断控制器在硬件上对这一特性进行了保证,不过这也依赖于操作系统对中断系统的软件实现。在smp系统中,cpu之间也通过IPI(inter
processor interrupt)中断进行通信。

2.  IRQ编号

系统中每一个注册的中断源,都会分配一个唯一的编号用于识别该中断,我们称之为IRQ编号。IRQ编号贯穿在整个linux的通用中断子系统中。在移动设备中,每个中断源的IRQ编号都会在arch相关的一些头文件中,例如arch/xxx/mach-xxx/include/irqs.h。驱动程序在请求中断服务时,它会使用IRQ编号注册该中断,中断发生时,cpu通常会从中断控制器中获取相关信息,然后计算出相应的IRQ编号,然后把该IRQ编号传递到相应的驱动程序中。

3.  在驱动程序中申请中断

Linux中断子系统向驱动程序提供了一系列的API,其中的一个用于向系统申请中断:

 

[cpp] view plain copy

  1. int request_threaded_irq(unsigned int irq, irq_handler_t handler,  
  2.              irq_handler_t thread_fn, unsigned long irqflags,  
  3.              const char *devname, void *dev_id)  

其中,

  • irq是要申请的IRQ编号,
  • handler是中断处理服务函数,该函数工作在中断上下文中,如果不需要,可以传入NULL,但是不可以和thread_fn同时为NULL;
  • thread_fn是中断线程的回调函数,工作在内核进程上下文中,如果不需要,可以传入NULL,但是不可以和handler同时为NULL;
  • irqflags是该中断的一些标志,可以指定该中断的电气类型,是否共享等信息;
  • devname指定该中断的名称;
  • dev_id用于共享中断时的cookie data,通常用于区分共享中断具体由哪个设备发起;

关于该API的详细工作机理我们后面再讨论。

4.  通用中断子系统(Generic irq)的软件抽象

在通用中断子系统(generic
irq)出现之前,内核使用__do_IRQ处理所有的中断,这意味着__do_IRQ中要处理各种类型的中断,这会导致软件的复杂性增加,层次不分明,而且代码的可重用性也不好。事实上,到了内核版本2.6.38,__do_IRQ这种方式已经彻底在内核的代码中消失了。通用中断子系统的原型最初出现于ARM体系中,一开始内核的开发者们把3种中断类型区分出来,他们是:

  • 电平触发中断(level type)
  • 边缘触发中断(edge type)
  • 简易的中断(simple type)

后来又针对某些需要回应eoi(end of interrupt)的中断控制器,加入了fast eoi type,针对smp加入了per cpu
type。把这些不同的中断类型抽象出来后,成为了中断子系统的流控层。要使所有的体系架构都可以重用这部分的代码,中断控制器也被进一步地封装起来,形成了中断子系统中的硬件封装层。我们可以用下面的图示表示通用中断子系统的层次结构:

                                                 图 4.1  通用中断子系统的层次结构

        硬件封装层  它包含了体系架构相关的所有代码,包括中断控制器的抽象封装,arch相关的中断初始化,以及各个IRQ的相关数据结构的初始化工作,cpu的中断入口也会在arch相关的代码中实现。中断通用逻辑层通过标准的封装接口(实际上就是struct

irq_chip定义的接口)访问并控制中断控制器的行为,体系相关的中断入口函数在获取IRQ编号后,通过中断通用逻辑层提供的标准函数,把中断调用传递到中断流控层中。我们看看irq_chip的部分定义:

[cpp] view plain copy

  1. struct irq_chip {  
  2.     const char  *name;  
  3.     unsigned int    (*irq_startup)(struct irq_data *data);  
  4.     void        (*irq_shutdown)(struct irq_data *data);  
  5.     void        (*irq_enable)(struct irq_data *data);  
  6.     void        (*irq_disable)(struct irq_data *data);  
  7.   
  8.     void        (*irq_ack)(struct irq_data *data);  
  9.     void        (*irq_mask)(struct irq_data *data);  
  10.     void        (*irq_mask_ack)(struct irq_data *data);  
  11.     void        (*irq_unmask)(struct irq_data *data);  
  12.     void        (*irq_eoi)(struct irq_data *data);  
  13.   
  14.     int     (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);  
  15.     int     (*irq_retrigger)(struct irq_data *data);  
  16.     int     (*irq_set_type)(struct irq_data *data, unsigned int flow_type);  
  17.     int     (*irq_set_wake)(struct irq_data *data, unsigned int on);  
  18.         ......  
  19. };  

看到上面的结构定义,很明显,它实际上就是对中断控制器的接口抽象,我们只要对每个中断控制器实现以上接口(不必全部),并把它和相应的irq关联起来,上层的实现即可通过这些接口访问中断控制器。而且,同一个中断控制器的代码可以方便地被不同的平台所重用。

        中断流控层 
所谓中断流控是指合理并正确地处理连续发生的中断,比如一个中断在处理中,同一个中断再次到达时如何处理,何时应该屏蔽中断,何时打开中断,何时回应中断控制器等一系列的操作。该层实现了与体系和硬件无关的中断流控处理操作,它针对不同的中断电气类型(level,edge......),实现了对应的标准中断流控处理函数,在这些处理函数中,最终会把中断控制权传递到驱动程序注册中断时传入的处理函数或者是中断线程中。目前内核提供了以下几个主要的中断流控函数的实现(只列出部分):

  • handle_simple_irq(); 
  • handle_level_irq();  电平中断流控处理程序
  • handle_edge_irq();  边沿触发中断流控处理程序
  • handle_fasteoi_irq();  需要eoi的中断处理器使用的中断流控处理程序
  • handle_percpu_irq();  该irq只有单个cpu响应时使用的流控处理程序

        中断通用逻辑层  该层实现了对中断系统几个重要数据的管理,并提供了一系列的辅助管理函数。同时,该层还实现了中断线程的实现和管理,共享中断和嵌套中断的实现和管理,另外它还提供了一些接口函数,它们将作为硬件封装层和中断流控层以及驱动程序API层之间的桥梁,例如以下API:

  • generic_handle_irq();
  • irq_to_desc();
  • irq_set_chip();
  • irq_set_chained_handler();

        驱动程序API
 该部分向驱动程序提供了一系列的API,用于向系统申请/释放中断,打开/关闭中断,设置中断类型和中断唤醒系统的特性等操作。驱动程序的开发者通常只会使用到这一层提供的这些API即可完成驱动程序的开发工作,其他的细节都由另外几个软件层较好地“隐藏”起来了,驱动程序开发者无需再关注底层的实现,这看起来确实是一件美妙的事情,不过我认为,要想写出好的中断代码,还是花点时间了解一下其他几层的实现吧。其中的一些API如下:

  • enable_irq();
  • disable_irq();
  • disable_irq_nosync();
  • request_threaded_irq();
  • irq_set_affinity();

这里不再对每一层做详细的介绍,我将会在本系列的其他几篇文章中做深入的探讨。

5.  irq描述结构:struct irq_desc

整个通用中断子系统几乎都是围绕着irq_desc结构进行,系统中每一个irq都对应着一个irq_desc结构,所有的irq_desc结构的组织方式有两种:

        基于数组方式  平台相关板级代码事先根据系统中的IRQ数量,定义常量:NR_IRQS,在kernel/irq/irqdesc.c中使用该常量定义irq_desc结构数组:

[cpp] view plain copy

  1. struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {  
  2.     [0 ... NR_IRQS-1] = {  
  3.         .handle_irq = handle_bad_irq,  
  4.         .depth      = 1,  
  5.         .lock       = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),  
  6.     }  
  7. };  

        基于基数树方式 
当内核的配置项CONFIG_SPARSE_IRQ被选中时,内核使用基数树(radix
tree)来管理irq_desc结构,这一方式可以动态地分配irq_desc结构,对于那些具备大量IRQ数量或者IRQ编号不连续的系统,使用该方式管理irq_desc对内存的节省有好处,而且对那些自带中断控制器管理设备自身多个中断源的外部设备,它们可以在驱动程序中动态地申请这些中断源所对应的irq_desc结构,而不必在系统的编译阶段保留irq_desc结构所需的内存。

下面我们看一看irq_desc的部分定义:

[cpp] view plain copy

  1. struct irq_data {  
  2.     unsigned int        irq;  
  3.     unsigned long       hwirq;  
  4.     unsigned int        node;  
  5.     unsigned int        state_use_accessors;  
  6.     struct irq_chip     *chip;  
  7.     struct irq_domain   *domain;  
  8.     void            *handler_data;  
  9.     void            *chip_data;  
  10.     struct msi_desc     *msi_desc;  
  11. #ifdef CONFIG_SMP  
  12.     cpumask_var_t       affinity;  
  13. #endif  
  14. };  

 

[cpp] view plain copy

  1. struct irq_desc {  
  2.     struct irq_data     irq_data;  
  3.     unsigned int __percpu   *kstat_irqs;  
  4.     irq_flow_handler_t  handle_irq;  
  5. #ifdef CONFIG_IRQ_PREFLOW_FASTEOI  
  6.     irq_preflow_handler_t   preflow_handler;  
  7. #endif  
  8.     struct irqaction    *action;    /* IRQ action list */  
  9.     unsigned int        status_use_accessors;  
  10.     unsigned int        depth;      /* nested irq disables */  
  11.     unsigned int        wake_depth; /* nested wake enables */  
  12.     unsigned int        irq_count;  /* For detecting broken IRQs */  
  13.   
  14.     raw_spinlock_t      lock;  
  15.     struct cpumask      *percpu_enabled;  
  16. #ifdef CONFIG_SMP  
  17.     const struct cpumask    *affinity_hint;  
  18.     struct irq_affinity_notify *affinity_notify;  
  19. #ifdef CONFIG_GENERIC_PENDING_IRQ  
  20.     cpumask_var_t       pending_mask;  
  21. #endif  
  22. #endif  
  23.     wait_queue_head_t       wait_for_threads;  
  24.   
  25.     const char      *name;  
  26. } ____cacheline_internodealigned_in_smp;  

对于irq_desc中的主要字段做一个解释:     

        irq_data
 这个内嵌结构在2.6.37版本引入,之前的内核版本的做法是直接把这个结构中的字段直接放置在irq_desc结构体中,然后在调用硬件封装层的chip->xxx()回调中传入IRQ编号作为参数,但是底层的函数经常需要访问->handler_data,->chip_data,->msi_desc等字段,这需要利用irq_to_desc(irq)来获得irq_desc结构的指针,然后才能访问上述字段,者带来了性能的降低,尤其在配置为sparse

irq的系统中更是如此,因为这意味着基数树的搜索操作。为了解决这一问题,内核开发者把几个低层函数需要使用的字段单独封装为一个结构,调用时的参数则改为传入该结构的指针。实现同样的目的,那为什么不直接传入irq_desc结构指针?因为这会破坏层次的封装性,我们不希望低层代码可以看到不应该看到的部分,仅此而已。

        kstat_irqs  用于irq的一些统计信息,这些统计信息可以从proc文件系统中查询。

        action  中断响应链表,当一个irq被触发时,内核会遍历该链表,调用action结构中的回调handler或者激活其中的中断线程,之所以实现为一个链表,是为了实现中断的共享,多个设备共享同一个irq,这在外围设备中是普遍存在的。

        status_use_accessors  记录该irq的状态信息,内核提供了一系列irq_settings_xxx的辅助函数访问该字段,详细请查看kernel/irq/settings.h

        depth 
用于管理enable_irq()/disable_irq()这两个API的嵌套深度管理,每次enable_irq时该值减去1,每次disable_irq时该值加1,只有depth==0时才真正向硬件封装层发出关闭irq的调用,只有depth==1时才会向硬件封装层发出打开irq的调用。disable的嵌套次数可以比enable的次数多,此时depth的值大于1,随着enable的不断调用,当depth的值为1时,在向硬件封装层发出打开irq的调用后,depth减去1后,此时depth为0,此时处于一个平衡状态,我们只能调用disable_irq,如果此时enable_irq被调用,内核会报告一个irq失衡的警告,提醒驱动程序的开发人员检查自己的代码。

        lock  用于保护irq_desc结构本身的自旋锁。

        affinity_hit  用于提示用户空间,作为优化irq和cpu之间的亲缘关系的依据。

        pending_mask  用于调整irq在各个cpu之间的平衡。

        wait_for_threads  用于synchronize_irq(),等待该irq所有线程完成。

irq_data结构中的各字段:

        irq  该结构所对应的IRQ编号。

        hwirq  硬件irq编号,它不同于上面的irq;

        node  通常用于hwirq和irq之间的映射操作;

        state_use_accessors  硬件封装层需要使用的状态信息,不要直接访问该字段,内核定义了一组函数用于访问该字段:irqd_xxxx(),参见include/linux/irq.h。

        chip  指向该irq所属的中断控制器的irq_chip结构指针

        handler_data  每个irq的私有数据指针,该字段由硬件封转层使用,例如用作底层硬件的多路复用中断。

        chip_data  中断控制器的私有数据,该字段由硬件封转层使用。

        msi_desc  用于PCIe总线的MSI或MSI-X中断机制。

        affinity  记录该irq与cpu之间的亲缘关系,它其实是一个bit-mask,每一个bit代表一个cpu,置位后代表该cpu可能处理该irq。

这是通用中断子系统系列文章的第一篇,这里不会详细介绍各个软件层次的实现原理,但是有必要对整个架构做简要的介绍:

  • 系统启动阶段,取决于内核的配置,内核会通过数组或基数树分配好足够多的irq_desc结构;
  • 根据不同的体系结构,初始化中断相关的硬件,尤其是中断控制器;
  • 为每个必要irq的irq_desc结构填充默认的字段,例如irq编号,irq_chip指针,根据不同的中断类型配置流控handler;
  • 设备驱动程序在初始化阶段,利用request_threaded_irq() api申请中断服务,两个重要的参数是handler和thread_fn;
  • 当设备触发一个中断后,cpu会进入事先设定好的中断入口,它属于底层体系相关的代码,它通过中断控制器获得irq编号,在对irq_data结构中的某些字段进行处理后,会将控制权传递到中断流控层(通过irq_desc->handle_irq);
  • 中断流控处理代码在作出必要的流控处理后,通过irq_desc->action链表,取出驱动程序申请中断时注册的handler和thread_fn,根据它们的赋值情况,或者只是调用handler回调,或者启动一个线程执行thread_fn,又或者两者都执行;
  • 至此,中断最终由驱动程序进行了响应和处理。

6.  中断子系统的proc文件接口

在/proc目录下面,有两个与中断子系统相关的文件和子目录,它们是:

  • /proc/interrupts:文件
  • /proc/irq:子目录

读取interrupts会依次显示irq编号,每个cpu对该irq的处理次数,中断控制器的名字,irq的名字,以及驱动程序注册该irq时使用的名字,以下是一个例子:

/proc/irq目录下面会为每个注册的irq创建一个以irq编号为名字的子目录,每个子目录下分别有以下条目:

  • smp_affinity            irq和cpu之间的亲缘绑定关系;
  • smp_affinity_hint   只读条目,用于用户空间做irq平衡只用;
  • spurious                  可以获得该irq被处理和未被处理的次数的统计信息;
  • handler_name       驱动程序注册该irq时传入的处理程序的名字;

根据irq的不同,以上条目不一定会全部都出现,以下是某个设备的例子:

# cd /proc/irq
# ls
ls
332
248
......
......
12
11
default_smp_affinity

# ls 332
bcmsdh_sdmmc
spurious
node
affinity_hint
smp_affinity

# cat 332/smp_affinity
3

可见,以上设备是一个使用双核cpu的设备,因为smp_affinity的值是3,系统默认每个中断可以由两个cpu进行处理。

本章内容结束。接下来的计划:

Linux中断(interrupt)子系统之二:arch相关的硬件封装层

Linux中断(interrupt)子系统之三:中断流控处理层

Linux中断(interrupt)子系统之四:驱动程序接口层

Linux中断(interrupt)子系统之五:软件中断(softirq)

时间: 2024-10-22 22:57:40

Linux中断(interrupt)子系统之一:中断系统基本原理【转】的相关文章

中断子系统1_中断子系统初始化

// 控制单元对中断信号的处理: // 当cpu执行一条指令后,cs和eip包含下一条要执行的指令的逻辑地址,在处理那条指令之前, // 控制单元会检查在运行前一条指令时是否发生了一个中断或异常,如果发生了一个中断或异常, // 控制单元执行下列操作: // 1.确定与中断或异常关联的向量i // 2.读入由idtr寄存器指向的IDT表中的第i项 // 3.从gdtr寄存器获得GDT的基地址,并在GDT中查找,以读取IDT表项中的选择符所标识的段描述符. // 这个描述符指定中断或异常处理程序的

Linux 多核下绑定硬件中断到不同 CPU(IRQ Affinity)

转载 - Linux 多核下绑定硬件中断到不同 CPU(IRQ Affinity) 作者 digoal 日期 2016-11-20 标签 Linux , IRQ , 中断 , CPU亲和 , 绑定中断处理CPU 背景 原文 http://www.vpsee.com/2010/07/load-balancing-with-irq-smp-affinity/ 原文 硬件中断发生频繁,是件很消耗 CPU 资源的事情,在多核 CPU 条件下如果有办法把大量硬件中断分配给不同的 CPU (core) 处理

中断子系统3_中断入口处理

// 中断入口 // 注:gnu 每个符号分属global(被输出)和local(不被输出)两类中的一种. 1.1 #define ENTRY(name) \ .globl name; \ ALIGN; \//之后的代码对齐到32字节,使用NOP(0x90)补齐 name: // 代码对齐 // .align(n) power-of-2对齐 // 4 对齐到16字节, 5 对齐到32字节 // 0x90 NOP 指令的机器码,用于填充到指定的对齐字节 1.2 #define ALIGN .ali

浅谈Linux内核无线子系统

Linux 内核是如何实现无线网络接口呢?数据包是通过怎样的方式被发送和接收呢?今天跟着 LinuxStory 小编一起来探索一番吧! 刚开始工作接触 Linux 无线网络时,我曾迷失在浩瀚的基础代码中,寻找具有介绍性的材料来回答如上面提到的那些高层次的问题.跟踪探索了一段时间的源代码后,我写下了这篇总结,希望在 Linux 无线网络的工作原理上,读者能从这篇文章获得一个具有帮助性的概览. 1 全局概览 在开始探索 Linux 无线具体细节之前,让我们先来把握一下 Linux 无线子系统整体结构

线程-操作系统的中断和程序的中断

问题描述 操作系统的中断和程序的中断 操作系统的中断和java程序中的线程中断Thread.interrupted()有什么区别 解决方案 操作系统中断是指CPU对系统发生的某个事件做出的一种反应,CPU暂停正在执行的程序,保留现场后自动地转去执行相应的处理程序,处理完该事件后再返回断点继续执行被"打断"的程序. 中断可分为三类,第一类是由CPU外部引起的,称作中断,如I/O中断.时钟中断.控制台中断等.第二类是来自CPU的内部事件或程序执行中的事件引起的过程,称作异常,如由于CPU本

Linux内核分析(三)----初识linux内存管理子系统

原文:Linux内核分析(三)----初识linux内存管理子系统 Linux内核分析(三) 昨天我们对内核模块进行了简单的分析,今天为了让我们今后的分析没有太多障碍,我们今天先简单的分析一下linux的内存管理子系统,linux的内存管理子系统相当的庞大,所以我们今天只是初识,只要对其进行简单的了解就好了,不会去追究代码,但是在后面我们还会对内存管理子系统进行一次深度的分析. 在分析今天的内容之前,我们先来看出自http://bbs.chinaunix.net/thread-2018659-2

Linux安装完成之后如何修改系统时间?

Linux安装完成之后如何修改系统时间? 我们在安装完Linux操作系统之后,往往需要调整系统的时间,或是对当前的时间进行校对,我们可以使用以下三种方法: 1 设置你的时区: timeconfig里选择Asia/Shanghai (如果你位于GMT+8中国区域) 2 与标准时间服务器校准: ntpdate time.nist.gov 当然,如果你是李嘉诚,也可以跟自己的手表校准: date -s STRING (STRING格式见man date),修改后执行 clock -w 写到http:/

Linux下一个简单的日志系统的设计及其C代码实现

1.概述 在大型软件系统中,为了监测软件运行状况及排查软件故障,一般都会要求软件程序在运行的过程中产生日志文件.在日志文件中存放程序流程中的一些重要信息,包括:变量名称及其值.消息结构定义.函数返回值及其执行情况.脚本执行及调用情况等.通过阅读日志文件,我们能够较快地跟踪程序流程,并发现程序问题.因此,熟练掌握日志系统的编写方法并快速地阅读日志文件,是对一个软件开发工程师的基本要求. 本文详细地介绍了Linux下一个简单的日志系统的设计方法,并给出了其C代码实现.本文为相关开发项目Linux下软

Linux下必须要学的系统安全命令第1/4页_unix linux

虽然Linux和Windows NT/2000系统一样是一个多用户的系统,但是它们之间有不少重要的差别.对于很多习惯了Windows系统的管理员来讲,如何保证Linux操作系统安全.可靠将会面临许多新的挑战.本文将重点介绍Linux系统安全的命令.   passwd   1.作用   passwd命令原来修改账户的登陆密码,使用权限是所有用户.   2.格式   passwd [选项] 账户名称   3.主要参数   -l:锁定已经命名的账户名称,只有具备超级用户权限的使用者方可使用.   -u