对于操作系统来说,中断的实现和裸机代码是不一样的,对于裸机代码来说,它的处理过程更符合下面这个流程,我们在编写裸板驱动代码的时候,一般处理的是硬件中断,而且中断号也是硬件中断号,这个中断号是和CPU寄存器的连线方式直接相关的,只要连线方式一确定,那么外设对应的中断号就确定了。

比如在下面这个图中,中断源可以是TIMER或者BUTTON,对应的中断号是6和16,当这些外设产生中断信号的时候,它首先通过中断控制器的判断将信号传给中断核心层,CPU收到该信号以后会做一些保存现场的工作,然后跳到中断向量表查找对应的中断处理程序并进行处理。

但是操作系统的处理方式并不是这样的,内核对这些驱动的架构都进行了抽象,让编写驱动的时候要符合一定的规则,这样一来,让驱动的编写过程变成了对驱动架构的填充过程,这确实省了不少事,所以这里简单记录一下不同驱动架构中,中断的接收和处理过程是怎么穿梭其中的。

对于内核的驱动架构来说,它不在如过家家似的裸机代码了,首先要对中断号进行映射,也要对外设的数据寄存器以及代码寄存器的地址进行映射。比如寄存器接受到中断号是16的中断信号,当这个信号映射到内核后,这个中断号是160了,当然这两个中断信号其实是同一个信号,只是在不同层之间的不同表示而已,他们是键值对的关系。

linux 驱动中断处理相关数据结构

在Linux内核中有一个表存放中断处理程序叫irq_desc[] ,每个个IRQ中断线,Linux都用一个irq_desc_t数据结构来描述,我们把它叫做 IRQ描述符, NR_IRQS 个IRQ形成一个全局数组 irq_desc[],其定义在 include/linux/irq.h 中:

数据结构irq_desc里有一项叫做 irqaction。它里面就包含着 handler() 函数。irq_desc_t 的 action 成员实际是一个 irqaction 结构的链表,因为多个设备可能共享一个中断源, 每个设备都必须提供一个 irqction 结构挂入 action 链表。

struct irqaction 结构记录当中断发生时具体的处理函数,定义在 include/linux/interrupt.h 文件中。

中断号IRQ的确定

不管对于哪种类型的驱动,都可以在 /proc/interrupts下找到映射完以后的kernel中断号:

第一列是kernel map后的中断号, 倒数第三列是中断的触发方式(上升沿、下降沿),最后一列是注册中断处理子程序是我们分配的描述信息

cat /proc/interrupts 
           CPU0       CPU1       CPU2       CPU3       
 20:  109111782 1287889318  833179606 1002449638       GIC  20  arch_timer
 35:          0          0          0          0       GIC  35  watchdog bark
 97:          0          0   30564292          0       GIC  97  edma_eth_tx0
 98:          0          0          6          0       GIC  98  edma_eth_tx1
 99:          0          0     131107          0       GIC  99  edma_eth_tx2
100:          0          0          4          0       GIC 100  edma_eth_tx3
101:          0          0          0      22339       GIC 101  edma_eth_tx4
102:          0          0          0          0       GIC 102  edma_eth_tx5
103:          0          0          0     205881       GIC 103  edma_eth_tx6
104:          0          0          0          0       GIC 104  edma_eth_tx7
105:   28786218          0          0          0       GIC 105  edma_eth_tx8
106:          4          0          0          0       GIC 106  edma_eth_tx9
107:      99063          0          0          0       GIC 107  edma_eth_tx10
108:          2          0          0          0       GIC 108  edma_eth_tx11
109:          0   29040737          0          0       GIC 109  edma_eth_tx12
110:          0          4          0          0       GIC 110  edma_eth_tx13
111:          0      78467          0          0       GIC 111  edma_eth_tx14
112:          0          8          0          0       GIC 112  edma_eth_tx15
127:          2          0          0          0       GIC 127  78b5000.spi
129:    5171604          0          0          0       GIC 129  i2c-msm-v2-irq
133:          0          0          0          0       GIC 133  sps
139:     238076          0          0          0       GIC 139  msm_serial_hsl0
164:          0          0          0          0       GIC 164  xhci-hcd:usb1
168:          0          0          0          0       GIC 168  xhci-hcd:usb3
173:          0          0          0          0       GIC 173  int_msi
174:       1742          0          0   69415235       GIC 174  wlan_pci
183:          0          0          0          0       GIC 183  int_pls_link_down
200:       1924          0   91612296          0       GIC 200  wlan_ahb
201:       1686  235856973          0          0       GIC 201  wlan_ahb
239:          0          0          0          0       GIC 239  sps
270:          0          0          0          0       GIC 270  sps
272:   41376101          0          0          0       GIC 272  edma_eth_rx0
274:          0   40781979          0          0       GIC 274  edma_eth_rx2
276:          0          0   38000530          0       GIC 276  edma_eth_rx4
278:          0          0          0   41502836       GIC 278  edma_eth_rx6
IPI0:          0          0          0          0  CPU wakeup interrupts
IPI1:          0          0          0          0  Timer broadcast interrupts
IPI2:   72607346   70337041   74354456   73214941  Rescheduling interrupts
IPI3:         14         21         17         20  Function call interrupts
IPI4:   28106763      93631   23136542   20144633  Single function call interrupts
IPI5:          0          0          0          0  CPU stop interrupts
IPI6:    6705108    6830143    5972690    5878126  IRQ work interrupts
IPI7:          0          0          0          0  completion interrupts
Err:          0

当然也可以在/proc/irq下搜索更多信息(find -name *wlan*),这个目录会列出所有内核支持的中断号(注意不是硬件中断),对于那些有绑定驱动的中断号,一般都会在对应的中断号目录下显示对应的名称,比如wlan_pci:

/proc/irq/174# ls
affinity_hint      smp_affinity       spurious
node               smp_affinity_list  wlan_pci

对于PCI总线驱动来说,在kernel启动的时候就会对外围设备进行扫描,并将这些外围PCI总线设备添加到链表里面,比如下面的代码,在pci_common_init的时候,在pci_fixup_irqs函数里面指明,当然PCI设备的irq号不是已经在配置空间里明确指定了的,在总线驱动初始化的过程中会从配置空间里面直接读取,如果驱动没有从配置空间里读取,这里也可以保证他不会丢失。

https://blog.csdn.net/eydwyz/article/details/72285858

关于IRQ号的映射关系,这篇文章介绍比较详细:

Linux kernel的中断子系统之(二):IRQ Domain介绍


  1. void __init pci_common_init(struct hw_pci *hw)  
  2. {  
  3.     struct pci_sys_data *sys;  
  4.   
  5.     INIT_LIST_HEAD(&hw->buses);  
  6.   
  7.     if (hw->preinit)  
  8.         hw->preinit();  
  9.     pcibios_init_hw(hw);  
  10.     if (hw->postinit)  
  11.         hw->postinit();  
  12.   
  13.     pci_fixup_irqs(pcibios_swizzle, pcibios_map_irq);

当然对于一些字符设备也可以从下面的方式,或者查看对应的DTS文件,染后经过一定的转换

$ grep -rns "IRQ" arch/arm/* | grep "UART" | grep "16"

$ vi arch/arm/plat-s5p/include/plat/irqs.h

关于更多中断子系统的资料可以查看这些文章:

Linux中断(interrupt)子系统之五:软件中断(softIRQ)
Linux中断(interrupt)子系统之四:驱动程序接口层 & 中断通用逻辑层
Linux中断(interrupt)子系统之三:中断流控处理层
Linux中断(interrupt)子系统之二:arch相关的硬件封装层
Linux中断(interrupt)子系统之一:中断系统基本原理

中断处理函数的注册

当我们确定一个事件的中断号以后,就可以对这个中断号注册中断处理函数,这也是中断处理的核心。

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);
}
extern void free_irq(unsigned int, void *);
request_irq 函数的参数如下:
unsigned int irq // 请求的中断号
irqreturn_t (*handler) // 安装的处理函数指针. .
unsigned long flags // 与中断管理相关的选项的位掩码(后面描述).
const char *dev_name //这个传递给 request_irq 的字串用在 /proc/interrupts 来显示中断的拥有者(/proc/interupts 最后一列)
void *dev_id // 用作共享中断线的指针. 它是一个独特的标识, 用在当释放中断线时置为 NULL, 但是使用这个项指向设备结构不管如何是个好主意. 我们将在"实现一个处理"一节中看到 dev_id 的
一个实际应用.


其中第一个参数是上面我们说的kernel的中断号

第二个参数是该终端对应的终端处理子函数

第三个参数用来控制中断类型,比如共享中断和快速中断

dev_id用来给共享中断指定处理线程

中断的处理过程

* 中断信号由外部设备发送到中断控制器,中断控制器根据IRQ号转换成相应的中断向量号传给CPU 。
* CPU接收中断后,保存现场,根据中断向量号到IDT中查找相应的处理函数。对于IRQ n的中断,它的处理函数IRQn_interrutp()。
* 调用do_IRQ()函数。该函数完成对中断控制器确认、设置中断源状态等动作, 接着它会根据IRQ号找到描述中断具体动作的irqaction结构变量action,执行如下代码:
    处理 irq 事件的函数,主要实现在 kernel/irq/handle.c 中,其中 res = action->handler (irq, action->dev_id);
    执行了调用用户注册的 handler() 函数的功能。

    irqreturn_t handle_irq_event(struct irq_desc *desc)
    handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
* 最后do_IRQ()函数要检查是否有软中断,如有则调用do_softirq()执行软中断。
* 跳转到 ret_from_intr退出,恢复中断前的现场。


中断处理我们都知道包括中断处理子程序的上半部分和下半部分,上半部分是需要快速执行并且不能阻塞的,主要的任务交给下半部分处理。我们重点讲讲下半部分,下半部分包括:tastlet,工作队列和软中断。

关于这些buttom half的内容就不赘述了,这些文章有详细分析


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

Concurrency Managed Workqueue之(一):workqueue的基本概念

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


 关于这些不同BH驱动架构的实例也可以参照这个PDF去练练

ARM驱动开发



GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐