保持简洁,保持功能单一。
-- Kelly Johnson
接下来该看redistributor了。
图1 GIC-600 redistributor
跟distributor连接的部分就不说了。Cpu_active是指示cluster或core的状态,可以用于idle管理。ppi_id用于多核设计时,区分每个redistributor。PPIs就是PPI中断线,在GIC-600是这么描述PPI的:
图2 PPI描述
从上面可以看出来,所谓的“私有”是说这些中断信号是core专有的。对于PPI,ARMv8定义了三种规格,8,12和16。所以对于不同的core来说,可能PPI数量不一样。
图3 Generic timer
上面是ARMv8-A的架构spec里,关于timer的图。我们可以看到,core的timer会发PPI,而中断控制器返回FIQ或者IRQ给core。细心的同学可能会问了,在redistributor上面没有FIQ和IRQ的信号啊?其实在第一篇文章里就说了,在现有的GICv3架构下,关于中断FIQ和IRQ,以及系统寄存器等放在了cluster/core端,对外留出了一组接口,叫cpu interface(在GICv2中实现在中断控制器这一侧),也就是图1中最下面的接口。其实这是一组简化的AXI4-Stream。
图4 GIC stream协议接口
由于是双向,所以是两组信号
图5 redistributor到CPU的信号
图6 CPU到redistributor的信号
既然是简化的总线协议,为了更便于CPU和redistributor通信,ARM又规定了具体的数据包格式。下图是CPU端发出的命令格式,具体的解释请查阅GICv3的文档,此处就不过多的贴图了。
图7 CPU interface命令
至此,就剩下最复杂的ITS了,这一部分是为了实现基于消息的中断。前面介绍过,LPI是一种基于消息的中断。也就是中断信息不在通过中断线进行传递。ITS就是要将接收到的LPI中断进行解析。
图8 GIC-600的ITS组件
既然是信息中断,就一定要有区分这些中断的方法,这样才能找到合适的中断处理对策。所以这里有两个概念。
EventID,用来表示外设发送中断的事件类型
DeviceID,用来表示哪一个外设发起LPI
而ITS需要将外设发送的DeviceID,eventID,通过一系列查表,得到LPI的中断号,再使用LPI中断号查表得到该中断的目标CPU。为此,ITS需要维护几张表,分别是device table,interrupt translation tableh和collection。
图9 ITS表
当外设写GITS_TRANSLATER寄存器,产生了LPI。这时ITS首先要拿着DeviceID,从device table中选择索引为DeviceID的表项。从该表项中,得到interrupt translation table的位置;然后再根据EventID,从interrupt translation table中选择索引为EventID的表项。得到中断号,以及中断所属的collection号;最后,使用collection号,从collection table中,选择索引为collection号的表项。得到redistributor的映射信息,最后根据collection表项的映射信息,将中断信息路由发送给对应的redistributor。
最后,提一句,GICv3中开始支持亲和性路由(affinity routing)。请原谅我偷点懒,直接把文档中的部分贴出来:
这里要解释一下什么是亲和性,我最初接触这个概念的时候简直是一头雾水,直到有一天看操作系统相关的书才明白是咋回事(忽略我的无知吧~~)。CPU亲和性是一种调度属性(scheduler property),Linux调度器会根据affinity的设置让指定的进程运行在"绑定"的CPU上,而不会在别的CPU上运行。其中有一个好处是,可以提高cache的命中率。当一个进程在某个CPU上运行时,会在该CPU的缓存中维护许多状态。下次该进程在相同的CPU上运行时,由于缓存中的数据而执行的更快。因此,多处理器的调度器应该考虑这种亲和性,尽可能的进程保持在同一个CPU上。同理,对于并发程序也是有好处的。感慨一下,做CPU设计,到了最后肯定要与操作系统相爱相杀,哈哈哈~~~
祝大家春节快乐!
领取专属 10元无门槛券
私享最新 技术干货