Linux kernel的中断子系统之(一):综述

作者:linuxer 发布于:2014-8-14 19:12 分类:中断子系统

一、前言

一个合格的linux驱动工程师需要对kernel中的中断子系统有深刻的理解,只有这样,在写具体driver的时候才能:

1、正确的使用linux kernel提供的的API,例如最著名的request_threaded_irq(request_irq)接口

2、正确使用同步机制保护驱动代码中的临界区

3、正确的使用kernel提供的softirq、tasklet、workqueue等机制来完成具体的中断处理

基于上面的原因,我希望能够通过一系列的文档来描述清楚linux kernel中的中断子系统方方面面的知识。一方面是整理自己的思绪,另外一方面,希望能够对其他的驱动工程师(或者想从事linux驱动工作的工程师)有所帮助。

 

二、中断系统相关硬件描述

中断硬件系统主要有三种器件参与,各个外设、中断控制器和CPU。各个外设提供irq request line,在发生中断事件的时候,通过irq request line上的电气信号向CPU系统请求处理。外设的irq request line太多,CPU需要一个小伙伴帮他,这就是Interrupt controller。Interrupt Controller是连接外设中断系统和CPU系统的桥梁。根据外设irq request line的多少,Interrupt Controller可以级联。CPU的主要功能是运算,因此CPU并不处理中断优先级,那是Interrupt controller的事情。对于CPU而言,一般有两种中断请求,例如:对于ARM,是IRQ和FIQ信号线,分别让ARM进入IRQ mode和FIQ mode。对于X86,有可屏蔽中断和不可屏蔽中断。

本章节不是描述具体的硬件,而是使用了HW block这样的概念。例如CPU HW block是只ARM core或者X86这样的实际硬件block的一个逻辑描述,实际中,可能是任何可能的CPU block。


1、HW中断系统的逻辑block图

我对HW中断系统之逻辑block diagram的理解如下图所示:

irq_thumb1

系统中有若干个CPU block用来接收中断事件并进行处理,若干个Interrupt controller形成树状的结构,汇集系统中所有外设的irq request line,并将中断事件分发给某一个CPU block进行处理。从接口层面看,主要有两类接口,一种是中断接口。有的实现中,具体中断接口的形态就是一个硬件的信号线,通过电平信号传递中断事件(ARM以及GIC组成的中断系统就是这么设计的)。有些系统采用了其他的方法来传递中断事件,比如x86+APIC(Advanced Programmable Interrupt Controller)组成的系统,每个x86的核有一个Local APIC,这些Local APIC们通过ICC(Interrupt Controller Communication)bus连接到IO APIC上。IO APIC收集各个外设的中断,并翻译成总线上的message,传递给某个CPU上的Local APIC。因此,上面的红色线条也是逻辑层面的中断信号,可能是实际的PCB上的铜线(或者SOC内部的铜线),也可能是一个message而已。除了中断接口,CPU和Interrupt Controller之间还需要有控制信息的交流。Interrupt Controller会开放一些寄存器让CPU访问、控制。


2、多个Interrupt controller和多个cpu之间的拓扑结构

Interrupt controller有的是支持多个CPU core的(例如GIC、APIC等),有的不支持(例如S3C2410的中断控制器,X86平台的PIC等)。如果硬件平台中只有一个GIC的话,那么通过控制该GIC的寄存器可以将所有的外设中断,分发给连接在该interrupt controller上的CPU。如果有多个GIC呢(或者级联的interrupt controller都支持multi cpu core)?假设我们要设计一个非常复杂的系统,系统中有8个CPU,有2000个外设中断要处理,这时候你如何设计系统中的interrupt controller?如果使用GIC的话,我们需要两个GIC(一个GIC最多支持1024个中断源),一个是root GIC,另外一个是secondary GIC。这时候,你有两种方案:

(1)把8个cpu都连接到root GIC上,secondary GIC不接CPU。这时候原本挂接在secondary GIC的外设中断会输出到某个cpu,现在,只能是(通过某个cpu interface的irq signal)输到root GIC的某个SPI上。对于软件而言,这是一个比较简单的设计,secondary GIC的cpu interface的设定是固定不变的,永远是从一个固定的CPU interface输出到root GIC。这种方案的坏处是:这时候secondary GIC的PPI和SGI都是没有用的了。此外,在这种设定下,所有连接在secondary GIC上的外设中断要送达的target CPU是统一处理的,要么送去cpu0,要么cpu 5,不能单独控制。

(2)当然,你也可以让每个GIC分别连接4个CPU core,root GIC连接CPU0~CPU3,secondary GIC连接CPU4~CPU7。这种状态下,连接在root GIC的中断可以由CPU0~CPU3分担处理,连接在secondary GIC的中断可以由CPU4~CPU7分担处理。但这样,在中断处理方面看起来就体现不出8核的威力了。

注:上一节中的逻辑block示意图采用的就是方案一。

3、Interrupt controller把中断事件送给哪个CPU?

毫无疑问,只有支持multi cpu core的中断控制器才有这种幸福的烦恼。一般而言,中断控制器可以把中断事件上报给一个CPU或者一组CPU(包括广播到所有的CPU上去)。对于外设类型的中断,当然是送到一个cpu上就OK了,我看不出来要把这样的中断送给多个CPU进行处理的必要性。如果送达了多个cpu,实际上,也应该只有一个handler实际和外设进行交互,另外一个cpu上的handler的动作应该是这样的:发现该irq number对应的中断已经被另外一个cpu处理了,直接退出handler,返回中断现场。IPI的中断不存在这个限制,IPI更像一个CPU之间通信的机制,对这种中断广播应该是毫无压力。

实际上,从用户的角度看,其需求是相当复杂的,我们的目标可能包括:

(1)让某个IRQ number的中断由某个特定的CPU处理

(2)让某个特定的中断由几个CPU轮流处理

……

当然,具体的需求可能更加复杂,但是如何区分软件和硬件的分工呢?让硬件处理那么复杂的策略其实是不合理的,复杂的逻辑如果由硬件实现,那么就意味着更多的晶体管,更多的功耗。因此,最普通的做法就是为Interrupt Controller支持的每一个中断设定一个target cpu的控制接口(当然应该是以寄存器形式出现,对于GIC,这个寄存器就是Interrupt processor target register)。系统有多个cpu,这个控制接口就有多少个bit,每个bit代表一个CPU。如果该bit设定为1,那么该interrupt就上报给该CPU,如果为0,则不上报给该CPU。这样的硬件逻辑比较简单,剩余的控制内容就交给软件好了。例如如果系统有两个cpu core,某中断想轮流由两个CPU处理。那么当CPU0相应该中断进入interrupt handler的时候,可以将Interrupt processor target register中本CPU对应的bit设定为0,另外一个CPU的bit设定为1。这样,在下次中断发生的时候,interupt controller就把中断送给了CPU1。对于CPU1而言,在执行该中断的handler的时候,将Interrupt processor target register中CPU0的bit为设置为1,disable本CPU的比特位,这样在下次中断发生的时候,interupt controller就把中断送给了CPU0。这样软件控制的结果就是实现了特定中断由2个CPU轮流处理的算法。


4、更多的思考

面对这个HW中断系统之逻辑block diagram,我们其实可以提出更多的问题:

(1)中断控制器发送给CPU的中断是否可以收回?重新分发给另外一个CPU?

(2)系统中的中断如何分发才能获得更好的性能呢?

(3)中断分发的策略需要考虑哪些因素呢?

……

很多问题其实我也没有答案,慢慢思考,慢慢逼近真相吧。

 

二、中断子系统相关的软件框架

linux kernel的中断子系统相关的软件框架图如下所示:

isbs 

由上面的block图,我们可知linux kernel的中断子系统分成4个部分:

(1)硬件无关的代码,我们称之Linux kernel通用中断处理模块。无论是哪种CPU,哪种controller,其中断处理的过程都有一些相同的内容,这些相同的内容被抽象出来,和HW无关。此外,各个外设的驱动代码中,也希望能用一个统一的接口实现irq相关的管理(不和具体的中断硬件系统以及CPU体系结构相关)这些“通用”的代码组成了linux kernel interrupt subsystem的核心部分。

(2)CPU architecture相关的中断处理。 和系统使用的具体的CPU architecture相关。

(3)Interrupt controller驱动代码 。和系统使用的Interrupt controller相关。

(4)普通外设的驱动。这些驱动将使用Linux kernel通用中断处理模块的API来实现自己的驱动逻辑。

 

三、中断子系统文档规划

中断相关的文档规划如下:

1、linux kernel的中断子系统之(一),也就是本文,其实是一个导论,没有实际的内容,主要是给读者一个大概的软硬件框架。

2、linux kernel的中断子系统之(二):irq domain介绍。主要描述如何将一个HW interrupt ID转换成IRQ number。

3、linux kernel的中断子系统之(三):IRQ number和中断描述符。主要描述中断描述符相关的数据结构和接口API。

4、linux kernel的中断子系统之(四):high level irq event handler

5、linux kernel的中断子系统之(五):driver API。主要以一个普通的驱动程序为视角,看待linux interrupt subsystem提供的API,如何利用这些API,分配资源,是否资源,如何处理中断相关的同步问题等等。

6、linux kernel的中断子系统之(六):ARM中断处理过程,这份文档以ARM CPU为例,描述ARM相关的中断处理过程

7、linux kernel的中断子系统之(七):GIC代码分析,这份文档是以一个具体的interrupt controller为例,描述irq chip driver的代码构成情况。

8、linux kernel的中断子系统之(八):softirq

9、linux kernel的中断子系统之(九):tasklet



原创文章,转发请注明出处。蜗窝科技。http://www.wowotech.net/linux_kenrel/interrupt_subsystem_architecture.html

标签: 软件框架 中断子系统

评论:

wind
2015-12-15 22:24
超赞 ...
Torres
2015-12-15 21:52
@linuxer 博主你好,我是个新手。有些问题想要请教一下,最近需要写一个GPIO中断的驱动,不知道从哪里下手。
有以下几个问题:
(1)中断号,对于GPIO作中断引脚,中断号是和GPIO控制器的中断号相同,还是需要自己在device tree中自己定义
(2)我想把中断服务程序写在应用程序中,但是在驱动程序中已经有了中断服务程序。我想要外部GPIO中断到来,执行应用程序中的服务程序,应该如何实现。
linuxer
2015-12-16 08:38
@Torres:(1)第一个问题,对于GPIO type的中断控制器,IRQ number和GPIO controller的hw interrupt number往往采用线性映射的关系,这样比较简单,适合初学者(当然,使用其他方法也OK)。device tree定义的是GPIO controller的hw interrupt number,不会定义IRQ Number和hw interrupt number的关系。

(2)在用户空间完成硬件驱动也是OK的,只要没有timing和中断处理的需求。你想把中断服务程序写在应用程序中我感觉是不太可能的,你说的场景多半是属于AP和driver接口问题:即driver开放一个事件的接口给AP,当driver检测到GPIO中断到来的适合,通过AP和driver的接口将这个事件上报给AP,由AP完成这个事件服务处理程序。
Torres
2015-12-16 09:23
@linuxer:@linuxer感谢博主的解答
1、对于第一个问题,我是不是不需要再在device tree中添加节点,直接在驱动程序中用GPIO就可以。比如我用 PE5 这个IO口作为GPIO中断,我是否直接调用
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
        const char *name, void *dev)
就可以,第一个参数设置为5就可以。
2、开放事件接口,是linux中的信号机制吗?是不是硬件中断触发软件中断,将软件中断服务程序放入AP。
linuxer
2015-12-16 12:08
@Torres:1、不可以,PE5说明是port E中的第五个中断号,这里中断号都是HW interrupt ID,而request_irq中的是virtual interrupt number(我们常说的IRQ number)
2、具体AP和driver的接口可以选用select接口的形式,AP open了设备节点之后,select在该设备节点上,如果driver有中断事件来,select则从阻塞态返回,从而AP得到了driver的事件
Torres
2015-12-16 20:34
@linuxer:谢谢博主,我再理解一下
狂奔的蜗牛
2016-02-26 16:53
@Torres:也可以这样操作,linux里面有通用gpio驱动给写好了,直接在应用层调用类似这样的接口就直接可以操作那些gpio_to_irq ,set_irq_type(gpio_to_irq(45),IRQF_TRIGGER_LOW);//设置GPIO0_A0中断为低电平触发,等等
tigger
2015-03-24 13:41
好像tasklet跟workqueue还没有发表呢。
是不是先把草稿发出来?哈哈
linuxer
2015-03-24 23:47
@tigger:该来的总会来的,哈哈,本来想没有人催促更新就混过去了,看来还得写啊
tigger
2015-03-25 00:08
@linuxer:我也不想说的,但是思考了很久还是想跟你学啊。不跟你学一下感觉心里没底啊。你写的每一篇文章,都有我不知道的知识点。啥时候你到魔都来,我请你吃饭啊。我工资里面的50%都是跟你学的 给你半个月工资都没问题啊哈哈
linuxer
2015-03-25 00:29
@tigger:承蒙Tigger厚爱,他日若到上海,定会与君一醉方休,哈哈
目前草稿中的是tick device,完成这份文档,就还是回到中断子系统,完成最后的两篇,算是给version 1的中断子系统有一个交代
SleepDeXiang
2015-06-10 14:28
@linuxer:@linuxer @linuxer 来上海也要叫上我啊,我是很爱学习的好孩纸,很喜欢学习kernel里的东西的!
linuxer
2015-06-11 00:09
@SleepDeXiang:好啊,好啊。愿意在蜗窝科技停留的都是真爱linux kernel的好孩纸,大家一起努力吧!
SleepDeXiang
2015-06-11 10:02
@linuxer:哈哈 楼主可以组织蜗窝博友聚个会  交流交流技术啥的
linux_emb
2015-12-24 11:48
@linuxer:这个提议好阿,@wowo 怎么看。
rabbiteve
2015-03-04 23:01
思路相当清晰,赞个~~~
ayeu0425
2015-01-20 11:15
博主,还想请教你一个问题,因为也看到你写device tree的文章。
对于一个具体的中断,例如 interrupt = <0 118 0x4>,它分别描述是中断类型、中断号、中断触发方式。
这个中断触发方式是被使用的?
比如网卡驱动中有  irq=platform_get_irq(...)【为什么不是irq_of_parse_and_map】,然后request_irq(irq,handler,IRQF_DISABLED,...)。这是怎么确定触发方式是电平触发(0x04)的呢?
wowo
2015-01-20 14:31
@ayeu0425:对于那些标准的dts字段,如触发类型,可以让kernel在初始化的时候,帮忙解析,不再需要每个driver都手动调用irq_of_parse_and_map。大概流程如下,供您参考:
of_device_alloc
        num_irq = of_irq_count(np);
        of_irq_to_resource_table-->of_irq_to_resource-->irq_of_parse_and_map
                of_irq_map_one
                irq_create_of_mapping
                        irq_set_irq_type
对于那些需要主动调用irq_of_parse_and_map的场景,一定是irq driver实现的不标准,这不是一个好的设计。
ayeu0425
2015-01-21 10:49
@wowo:明白了,非常感谢!
tigger
2015-02-03 10:06
@ayeu0425:关于你fiq导致系统hung住的情况,我想是不是不用fiq,修改一下对于中断的优先级会比较好?
ayeu0425
2015-02-03 20:18
@tigger:谢谢楼主,现在问题已经基本解决了。  还有一些小问题需要慢慢调调!
ayue0425
2015-01-13 16:36
拜读了博主的中断系统,收货很多。
现在我在做这么一件事:把网卡收发中断改为FIQ mode。
我在gic中把网卡配置成fiq后,在实际的网卡驱动中 request_irq,为什么这个 irq handler还被调用?我明明在gic init中配置为fiq了啊?
在这里卡了很久了,我不知道自己的理解哪里出去了。望博主帮忙解答。谢谢!
tigger
2015-01-13 17:22
@ayue0425:因为request_irq 申请的是irq
如果申请fiq的话要用别的申请函数,比如fiq_glue_register_handler
如果有错误,请指正
ayeu0425
2015-01-14 11:27
@tigger:谢谢博主的回复。
楼主的意思是:
request_irq是不管在gic_dist_init和gic_cpu_init的配置的?我是现在不管fiq的申请,先想弄明白为什么在gic中配置成fiq,request_irq还能正常使用。
linuxer
2015-01-15 09:27
@ayeu0425:首先要搞清楚下面的问题:
1、外设的中断是分配GIC的group0还是group1的中断。如果是group1,那么GIC根本不会触发FIQ中断
2、如果是group0,那么是否enable了FIQ,GICC_CTLR寄存器有一个bit可以控制
3、GIC和ARM processor是如何连接的?一般而言,GIC的nFIQCPU和nIRQCPU的信号线会连接到ARM processor的nFIQ和nIRQ的信号线,不过,有些SOC的设计中也许修改了这些硬件信号的连接
ayeu0425
2015-01-16 11:08
@linuxer:非常感谢博主的热心回复!!!
1、我现在外设中断分配的是GIC的group0中断。
2、现在request_irq申请中断后已经不能触发irq中断了(原来的问题是因为 我把gic dist secure寄存器配错,导致配置的是 non-secure mode),但现在网卡的fiq还是不能被触发。这里不知道楼主有没有什么建议?
3、也许 GIC的nFIQCPU和nIRQCPU的信号线都会被连接到cpu,但fiq和irq不可能同时被触发吧,具体触发哪个应该也是在 irq-gic.c中配置的吧?   一个中断具体是fiq or irq触发在gic init中就已经确定了吧?

本人linux学习不长,对中断更没有仔细研究过。所以很多问题可能很愚蠢!
linuxer
2015-01-16 23:48
@ayeu0425:但现在网卡的fiq还是不能被触发。这里不知道楼主有没有什么建议?
-----------------------------------------------------------
建议是谈不上,我是觉得可以分段排除问题:
1、检查网卡芯片的irq request line是否产生了指定的电平或者边缘信号
2、检查GIC的寄存器配置(FIQ相关的配置)
3、检查ARM的状态寄存器配置(FIQ的状态是否disable了)
4、跟踪FIQ的处理代码

  一个中断具体是fiq or irq触发在gic init中就已经确定了吧?
---------------------------------------------------------
是的
linuxer
2015-01-16 23:50
@ayeu0425:你的系统中是否只是网卡配置在group0,产生FIQ,其他的仍然使用IRQ?这样做是为了网卡的实时性吗?我不仅对你的系统的中断配置感兴趣了,因为我遇到的大部分系统都是不使用FIQ的
ayeu0425
2015-01-19 11:51
@linuxer:是的,其他的都还是IRQ。
这样做是为了后面继续做AVB(audio video bridging)。需要网卡发收包的实时性好,把音视频的处理作为系统的最高优先级,用fiq就是为了不让系统的其他任务打断。
----------------------------------------
其实原来已经实现了一个epit的fiq,但网卡的fiq好像不是那么简单。  
感觉最有可能的是网卡中断没有触发。
以楼主的经验:在gic的配置中,对epit和网卡其实也只是中断号的区别而已吧?我的意思是说,我epit已经成功配置好,在配置网卡,其实只是根据网卡的中断号,改对应gic寄存器的值而已?流程应该是相同的吧?
ayeu0425
2015-01-23 21:06
@linuxer:现在fiq基本没问题了。不过新问题就来了,网卡基本上能ping通了,但内核会经常hang住。个人觉得好像是napi的问题,napi在操作时为了保护临界区用了很多local_irq_save、local_irq_restore。这几个函数感觉不会对fiq起作用。
puppypyb
2014-12-09 17:46
quote:"上面的红色线条也是逻辑层面的中断信号,可能是实际的PCB上的铜线(或者SOC内部的铜线),也可能是一个message而已"

我以为,就算是通过bus传输,其实也是铜线,因为bus本质上也是线。

2014-09-30 10:52
拜读!
中断的逻辑图中如果GIC画成两部分,一个是Distributor,一个是cpu interface,之前看过arm的block图,我觉得这样更好。
Androider
2014-08-29 17:18
有些疑惑。。。对于框架,我觉得看懂了,但是还是不会用,这属于什么情况啊?我是新手,刚刚开始Android驱动的工作,对内核了解甚少~~~给个指点吧,谢谢大牛~
linuxer
2014-08-29 19:30
@Androider:我的想法:
1、保持热情
2、学好英文。这个没有办法,不是崇洋媚外,这方面的确是外国人领先,掌握好了英文就掌握了一个打开知识宝库的钥匙
3、如果想做驱动,推荐看linux device driver(英文版),不懂的问题可以提出来,一起讨论
剩下的就交给时间吧
Androider
2014-09-01 11:53
@linuxer:看来做驱动就是慢功夫啊,谢谢博主的建议,祝蜗窝科技越办越好!
Androider
2014-09-01 12:05
@linuxer:对了,您当时有没有像我这样的阶段?是怎么解决的?怎么学的?
linuxer
2014-09-01 12:42
@Androider:第一次接触GNU/linux是在98年,用JDK,perl开发一些应用程序,2002年开始进入内核,版本是2.4.18。毫无疑问,最开始时候是最困难的,每一步都很困难,有两年的时间,我几乎是把所有的业余时间都用来阅读linux源代码,因为要补充的知识是在是太多了。这个阶段,情景分析那本书给了我很多帮助,我一直觉得对于刚入门的年轻人,《情景分析》要好过《深入理解linux内核》。《情景分析》那两本书几乎被我翻烂了。对于驱动部分,我选取了USB作为突破点,阅读协议,购买开发板学习等等。准备了2~3年,我才转投一家芯片公司真正开始了linux 内核的开发。做linux内核以及驱动,做好是去一家芯片公司,可以了解很多底层硬件的知识。真正进入驱动开发的角色反而比较简单,慢慢熟悉每一个硬件,调试各种驱动,fix各种issue,经过3~5年,总是可以成为一个较为资深的驱动工程师。

总结一下:
1、linux或者unix上的用户空间的系统编程对理解linux kernel帮助很大
2、最开始的时候是《情景分析》加上网络搜索来获取大量的细节的知识
3、《深入理解linux内核》给了很多框架性的指导
4、勤于思考,找到问题所在
Androider
2014-09-01 12:56
@linuxer:博主太好太有耐心了。我觉得我找到原因了,自己不好好学还想些乱七八糟的事情。。。以后要踏踏实实的学习!谢谢博主的指点,从来没有博主那么耐心的解答问题,感动啊。如果能点赞,我就给你用力的点个大赞!
Ash
2015-01-12 21:08
@linuxer:不能赞更多了!
sea
2014-08-29 10:38
真的是非常好的文章,感谢楼主!
linuxer
2014-08-29 19:21
@sea:不敢当啊,这些文章都是把自己的理解描述出来,其实也不一定对,大家可以一起讨论学习

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog