# 中断向量和中断函数

中断的处理其实包含了很多细节，有软件架构上的，也有硬件架构上的。

而我一直以来的困惑是一个中断是怎么样通过**一个中断向量运行到一个中断函数的**。这次我的目的是来解决这个问题。

## 从中断向量初始化开始

在前面的总结中我们已经看到IDT中有部分是用作处理外部中断的中断向量。那我们就来看看这些中断向量是如何设置的。

```
  start_kernel()
      init_IRQ()
          native_init_IRQ()
              idt_setup_apic_and_irq_gates()
                  idt_setup_from_table(idt_table, apic_idts, ARRAY_SIZE(apic_idts), true);
                  for_each_clear_bit_from(i, system_vectors, FIRST_SYSTEM_VECTOR) {
                      entry = irq_entries_start + 8 * (i - FIRST_EXTERNAL_VECTOR);
                      set_intr_gate(i, entry);
                  }
```

如果大家仔细看这个循环，就是将外部中断向量填写irq\_entries\_start对应的内容。那这个irq\_entries\_start对应的是什么呢？

```
ENTRY(irq_entries_start)
    vector=FIRST_EXTERNAL_VECTOR
    .rept (FIRST_SYSTEM_VECTOR - FIRST_EXTERNAL_VECTOR)
	UNWIND_HINT_IRET_REGS
	pushq	$(~vector+0x80)			/* Note: always in signed byte range */
	jmp	common_interrupt
	.align	8
	vector=vector+1
    .endr
END(irq_entries_start)
```

这段就是定义了(FIRST\_SYSTEM\_VECTOR - FIRST\_EXTERNAL\_VECTOR)个中断向量的函数。都长一个样，最后跳转到了common\_interrupt。

```
common_interrupt:
	addq	$-0x80, (%rsp)			/* Adjust vector to [-256, -1] range */
	call	interrupt_entry
	UNWIND_HINT_REGS indirect=1
	call	do_IRQ	/* rdi points to pt_regs */
	/* 0(%rsp): old RSP */
ret_from_intr:
  ...
END(common_interrupt)
```

这个函数太长了，我们就只看重点。重点就是大家都通过do\_IRQ来处理！

## do\_IRQ后断掉的线索

到此为止，一切顺利，但是好景不长。

```
  do_IRQ()
      handle_irq()
          generic_handle_irq_desc()
              desc->handle_irq(desc);
```

到这里我就抓瞎了，每个desc的handle\_irq是哪里设置的呢？难道每个都不同么？

从上面的代码中我们可以看到具体的中断处理由irq\_desc的handle\_irq来做。那这个handle\_irq到底是什么样子呢？

说实话，整个代码的框架暂时我还不知道，这一部分比我想象的要复杂一些。不仅涉及了软件的架构，还涉及到了硬件的部分知识。在这里暂时不做详细的描述，来看看找到的一种实现可能路径。

## pci设备注册中断处理的一种情况

我们以pci设备为例，来看看其中一种情况的代码流程。

### 从request\_irq()入手

request\_irq()是驱动向内核注册中断处理函数的API。那就从这里切入。

比如e1000的驱动中，e1000\_request\_irq()中有如下代码：

```
    e1000_request_irq()
        request_irq(adapter->pdev->irq, handler,..., ...)
            desc = irq_to_desc(irq);
```

可以看到，request\_irq()前两个参数中一个是irq号，另一个就是处理函数。有了这个irq号则可以找到对应的irq\_desc了。

> 也就是当前内核的架构中会通过某些方式实现irq\_desc和irq之间的映射。

这么看，这个irq到irq\_desc的映射在注册函数之前就存在了。这里不过是添加上具体的处理函数而已。

### pci\_host\_bridge->map\_irq()

到了这里就有点绕了，因为这个映射的方法也是有很多的。我们只看可能的一种方式。

还是从上面的代码入手，adapter->pdev->irq是一个pci设备的irq。那就是要找到pci设备的irq是什么时候设置的，就可以找到这个映射是什么时候建立的了。

找到的一个设置的地方在pci\_assign\_irq()中。摘取关键代码如下：

```
    int irq = 0;
    struct pci_host_bridge *hbrg = pci_find_host_bridge(dev->bus);
    irq = (*(hbrg->map_irq))(dev, slot, pin);
    dev->irq = irq;
```

可以看到，这里就涉及到硬件了。也就是由pci\_host\_bridge来决定某个设备获得的irq号是多少。

在代码中drivers/pci/controller目录下保存这pci\_host\_bridge设备的驱动。其中很多驱动的map\_irq都定义为of\_irq\_parse\_and\_map\_pci()。而这个函数的具体实现则调用了irq\_create\_of\_mapping()。

```
int of_irq_parse_and_map_pci(const struct pci_dev *dev, u8 slot, u8 pin)
{
	struct of_phandle_args oirq;
	int ret;

	ret = of_irq_parse_pci(dev, &oirq);
	if (ret)
		return 0; /* Proper return code 0 == NO_IRQ */

	return irq_create_of_mapping(&oirq);
}
```

### irq\_domain->map()

在of\_irq\_parse\_and\_map\_pci()中，如果of\_irq\_parse\_pci()失败，则会使用irq\_create\_of\_mapping()来设置irq\_desc的handle\_irq函数。其大致流程如下：

```
    irq_create_of_mapping()
        irq_create_fwspec_mapping()
            irq_create_mapping()
                irq_domain_associate()
                    irq_domain->ops->map()
```

到这里我们又接触到了一个新概念, irq\_domain。好在爬过了这么多坑，现在也很淡定了。顺藤摸瓜，可以找到这个map函数的一种可能是irq\_map\_generic\_chip()。

```
struct irq_domain_ops irq_generic_chip_ops = {
	.map	= irq_map_generic_chip,
	.unmap  = irq_unmap_generic_chip,
	.xlate	= irq_domain_xlate_onetwocell,
};
```

功夫不负有心人，在这个函数中，我们终于找到了irq\_desc->handle\_irq设置的地方了。

```
    irq_map_generic_chip()
        struct irq_chip_type *ct;
        irq_domain_set_info(..., ct->handler, NULL, NULL);
```

而这个ct->handler就是最后会赋值给irq\_desc->handle\_irq的了。

### chip\_types->handler

终于是时候看一眼这个handler长什么样子了，在代码中这个handler可以设置为几种情况：

* handle\_level\_irq
* handle\_edge\_irq
* handle\_edge\_eoi\_irq
* handle\_percpu\_irq
* ...

虽然形式上有很多个，但殊途同归大家最后都调用了\_\_handle\_irq\_event\_percpu()，而其中一部分核心代码如下：

```
struct irqaction *action;

for_each_action_of_desc(desc, action) {
  irqreturn_t res;

  trace_irq_handler_entry(irq, action);
  res = action->handler(irq, action->dev_id);
  trace_irq_handler_exit(irq, action, res);

  if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
          irq, action->handler))
    local_irq_disable();

  switch (res) {
  case IRQ_WAKE_THREAD:
    /*
     * Catch drivers which return WAKE_THREAD but
     * did not set up a thread function
     */
    if (unlikely(!action->thread_fn)) {
      warn_no_thread(irq, action);
      break;
    }

    __irq_wake_thread(desc, action);

    /* Fall through to add to randomness */
  case IRQ_HANDLED:
    *flags |= action->flags;
    break;

  default:
    break;
  }

  retval |= res;
}
```

这段就是大家熟知的irqaction的链表了。

我想终于可以说到这里，基本理清了中断处理的软件处理架构。

## 粗略的一张图

```
    IDT (idt_table)      struct irq_desc                                struct irqaction      struct irqaction
    +-----------+        +-------------------------------------+    +----------------+    +----------------+
    |           |        |action (struct irqaction*)         --|--->|         next --|--->|         next --| -> NULL
    +-----------+        |                                     |    |                |    |                |
    |           |        |                                     |    | +->handler     |    | +->handler     |
    +-----------+        +-------------------------------------+    +-|--------------+    +-|--------------+
    .           .   +--->|handle_irq()                         |      +---------------------+
    .           .   |    |= handle_level_irq                   |      |
    .           .   |    |= handle_edge_irq                    |      |
    +-----------+   |    |= handle_edge_eoi_irq                |      |
    |           |   |    |= handle_percpu_irq                  |      |
    +-----------+   |    |  |                                  |      |
    |do_IRQ  ---|---+    |  +->  __handle_irq_event_percpu() --|------+
    +-----------+        |                                     |
    |           |        +-------------------------------------+
    +-----------+        
    .           .        
    .           .        
    .           .        
    +-----------+
    |           |
    +-----------+
    |           |
    +-----------+
```
