Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2122601
  • 博文数量: 374
  • 博客积分: 7276
  • 博客等级: 少将
  • 技术积分: 5668
  • 用 户 组: 普通用户
  • 注册时间: 2011-10-06 16:35
文章分类

全部博文(374)

文章存档

2013年(23)

2012年(153)

2011年(198)

分类: LINUX

2012-02-02 12:34:35

这是 Professional Linux Kernel Architecture 一书的中断处理部分的读书笔记,未 完,待续。

1 中断
1.1 中断的类型

依据中断的来源,中断可以分为两类:

  • Synchronous Interrupts and Exceptions:

    同步中断以及异常,他们由 CPU 本身产生,主要针对当前执行的程序。 异常 (Exceptions) 的发生有很多原因,例如:

    • 程序的运行时错误 (Runtime error), 例如一个数字被0除之类;

      这种错误通常需要通过某种方式来通知应用程序,由应用程序来进行相应处理。

    • 发生了 anaomalous condition 情况发生,需要“外部”的帮助来对齐处理,等等。

      这种异常,可以由内核来帮助处理。

  • Asynchromous interrupts:

    异步中断和 CPU 正在执行的应用程序无关,通常由外设产生,可能在任何时间产生。

1.2 硬件 IRQs (Interrupt Requests)

严格来讲,外设引起的中断应该被称为中断请求。中断请求号和中断号中间有一定的 映射关系。这个映射关系和平台有关。

1.3 中断的处理

中断处理函数根据中断号被存放到一个 Array 中;从这个 Array 中, 可以依据中断号,找到相应的 Handler 。

1.3.1 中断处理的出口和入口

中断程序的处理和把大象放进冰箱类似,可分三步:

  • 把冰箱门打开 —— 为中断处理函数的执行准备好环境;
  • 把大象放进去 —— 执行中断处理函数;
  • 把冰箱门关上 —— 推出中断处理函数,并从堆栈中恢复原有进程的状态。

其中,第一步和第三步又成为中断函数的入口和出口,他们负责确保处理器在用户空 间和内核空间的切换。

入口路径的一个主要任务就是从用户态堆栈切换到内核态的堆栈,与此同时,他还需要负责保存正在执行的应用程序使用的寄存器状态,以便在中断处理函数推出后恢复。 数据结构 pt_regs 用来列出内核态所修改的寄存器,该数据结构由底层的汇编语言来负责填写。

中断处理的出口,除了负责内核态和用户态的切换,还要检查是否需要调度等等。

1.3.2 中断处理函数

中断的嵌套会带来很多问题,在中断处理函数重屏蔽其他中断源可以解决防止中断嵌套,但是长时间地屏蔽中断会导致内核遗漏掉一些重要的中断。 因此,中断处理函数重中断的屏蔽时间应该越短越好。

中断处理函数应满足下面两个要求:

  • 代码量应越少越好,以便中断函数的快速执行。
  • 如果中断处理函数也可以并发,那么他们之间不得互相影响。

上述两个条件中,后者可以通过优化中断处理函数的设计和编码来实现,但想实现前者,则不那么容易。 但实际上,并非中断处理程序的所有工作的重要性都是相同的,通常来讲,一个中断服务程序可根据其是否需要马上处理而分为三个部分:

  • Critical actions:
    这个部分必须要在中断发上后马上执行,否则将无法保证系统的稳定性或者数据操作的正确性。 例如,当网卡上有数据包到达后,内核必须马上将数据从网卡挪到内存中,如果这个动作没有在中断发生后马上执行,该数据可能会被后续接收到的数据覆盖。 在进行这个 critical actions 的时候,其他的中断必须被关掉。
  • Noncritical actions:
    这些 actions 相比前面的 critical actions 重要性稍低,但是也应该尽快完成。 不同的是,它可以在打开其他中断的前提下进行,因此可能会被其他的中断所"中断"(受其他的中断影响)。
  • Deferrable actions:
    Deferrable actions 的重要性比前两者都低,可以不用放在中断处理函数中进行。内核可以等到没什么事情的时候再处理它。

对中断处理函数的划分,并根据各个部分的不同的重要性,可以将中断函数拆成若干不同的片段,放在不同的上下文中执行, 很大程度上,在保证了系统事件完整性的前提下,防止了中断的嵌套。

1.4 数据结构
1.4.1 基本数据结构和中断处理子系统
  • 基本数据结构
    内核中有一个全局的 Array ,用于存放每个 IRQ 中断处理程序的入口 (Entry) ,定义如下: struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    [0 ... NR_IRQS-1] = {
    .status = IRQ_DISABLED,
    .chip = &no_irq_chip,
    .handle_irq = handle_bad_irq,
    .depth = 1,
    .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    }
    };

    其中, NR_IRQS 是一个平台相关的常量(虽然和平台相关,但在单个的平台下为常量),表明了最多可以分配的 IRQ 的数目。 handle_bad_irq 是一个默认的中断处理函数,用于处理没有设置 handler 的中断。

  • 中断处理子系统

    内核 2.6 中引入了中断子系统 (IRQ subsystem),该子系统可以分为三个抽象层, 如下图所示:

    +---------------------------------------------------------+
    | |
    | |
    v v
    +-------------+ +---------------+ +---------------+ +-----------+
    | Flow | | Central IRQ | | Chip specific | | |
    | Handling |<--------->| database |<---------->| functions |<-------->| Hardware |
    +-------------+ +---------------+ +---------------+ +-----------+
    ^ ^
    | |
    | +---------------------+ |
    | | High-level | |
    +---->| Service routine |<--+
    +---------------------+

    其中:

    • High-level Interrupt Service routine:
      指中断处理中,驱动(或者其他的内核模块)需要负责的任务。 例如,网卡因接收到数据而产生的中断,则 High-level Interrupt Service 负责将数据从网卡拷贝到内存。
    • Interrupt Flow Handling(Flow-level):
      负责处理各种不同类型的中断流,例如边沿触发的中断或者水平触发的中断等等。
    • Chip-Level hardware Encapsulation:
      在电子设备这一层上直接与产生中断的硬件打交道的抽象层。

    下面是 irq_desc 数据结构,上述的这三个抽象层,可以在下面的数据结构中找到相应的 fields。

    /**
    * struct irq_desc - interrupt descriptor
    * @irq: interrupt number for this descriptor
    * @timer_rand_state: pointer to timer rand state struct
    * @kstat_irqs: irq stats per cpu
    * @irq_2_iommu: iommu with this irq
    * @handle_irq: highlevel irq-events handler [if NULL, __do_IRQ()]
    * @chip: low level interrupt hardware access
    * @msi_desc: MSI descriptor
    * @handler_data: per-IRQ data for the irq_chip methods
    * @chip_data: platform-specific per-chip private data for the chip
    * methods, to allow shared chip implementations
    * @action: the irq action chain
    * @status: status information
    * @depth: disable-depth, for nested irq_disable() calls
    * @wake_depth: enable depth, for multiple set_irq_wake() callers
    * @irq_count: stats field to detect stalled irqs
    * @last_unhandled: aging timer for unhandled count
    * @irqs_unhandled: stats field for spurious unhandled interrupts
    * @lock: locking for SMP
    * @affinity: IRQ affinity on SMP
    * @node: node index useful for balancing
    * @pending_mask: pending rebalanced interrupts
    * @threads_active: number of irqaction threads currently running
    * @wait_for_threads: wait queue for sync_irq to wait for threaded handlers
    * @dir: /proc/irq/ procfs entry
    * @name: flow handler name for /proc/interrupts output
    */
    struct irq_desc {
    unsigned int irq;
    struct timer_rand_state *timer_rand_state;
    unsigned int *kstat_irqs;
    #ifdef CONFIG_INTR_REMAP
    struct irq_2_iommu *irq_2_iommu;
    #endif
    irq_flow_handler_t handle_irq;
    struct irq_chip *chip; /* CHIP-Level*/
    struct msi_desc *msi_desc;
    void *handler_data;
    void *chip_data; /* Flow-level */
    struct irqaction *action; /* IRQ action list */
    unsigned int status; /* IRQ status */

    unsigned int depth; /* nested irq disables */
    unsigned int wake_depth; /* nested wake enables */
    unsigned int irq_count; /* For detecting broken IRQs */
    unsigned long last_unhandled; /* Aging timer for unhandled count */
    unsigned int irqs_unhandled;
    raw_spinlock_t lock;
    #ifdef CONFIG_SMP
    cpumask_var_t affinity;
    const struct cpumask *affinity_hint;
    unsigned int node;
    #ifdef CONFIG_GENERIC_PENDING_IRQ
    cpumask_var_t pending_mask;
    #endif
    #endif
    atomic_t threads_active;
    wait_queue_head_t wait_for_threads;
    #ifdef CONFIG_PROC_FS
    struct proc_dir_entry *dir;
    #endif
    const char *name;
    };

    irq_desc 用于描述一个 IRQ 。其中和前面的三个抽象层相关的域, 包括:

    • handler_data:
      抽象层中的 Flow-level ,指向一些与 IRQ , 处理函数相关的数据。 当中断发生的时候,结构中的 handle_irq 被调用,该函数进而调用 chip 中提供的函数进行一些中断处理所需要的底层操作。
    • action:
      这个数据结构提供了当中断发生后需要执行的 action ,在这个结构中,设备驱动可以设置自己特定的处理函数。 属于抽象层中的 High-level 。
    • chip:
      前面已经提到了这个数据结构,它里面封装了 Flow handling 以及 Chip-specific 操作。属于前面提到的抽象层中的 Chip-level 。
    • name:
      这是 flow-handler 的名字,可以显示在 /proc/interrupts 中,可能为 "edge" (边沿触发), 或者 "level" (水平触发)。属于抽象层中的 Flow-level 。
1.4.2 IRQ 控制器抽象 —— irq_chip

irq_chip 是一个对于 IRQ 控制器的抽象,其定义和说明如下:

/**
* struct irq_chip - hardware interrupt chip descriptor
*
* @name: name for /proc/interrupts
* @startup: start up the interrupt (defaults to ->enable if NULL)
* @shutdown: shut down the interrupt (defaults to ->disable if NULL)
* @enable: enable the interrupt (defaults to chip->unmask if NULL)
* @disable: disable the interrupt
* @ack: start of a new interrupt
* @mask: mask an interrupt source
* @mask_ack: ack and mask an interrupt source
* @unmask: unmask an interrupt source
* @eoi: end of interrupt - chip level
* @end: end of interrupt - flow level
* @set_affinity: set the CPU affinity on SMP machines
* @retrigger: resend an IRQ to the CPU
* @set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
* @set_wake: enable/disable power-management wake-on of an IRQ
*
* @bus_lock: function to lock access to slow bus (i2c) chips
* @bus_sync_unlock: function to sync and unlock slow bus (i2c) chips
*
* @release: release function solely used by UML
* @typename: obsoleted by name, kept as migration helper
*/
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq);
void (*shutdown)(unsigned int irq);
void (*enable)(unsigned int irq);
void (*disable)(unsigned int irq);

void (*ack)(unsigned int irq);
void (*mask)(unsigned int irq);
void (*mask_ack)(unsigned int irq);
void (*unmask)(unsigned int irq);
void (*eoi)(unsigned int irq);

void (*end)(unsigned int irq);
int (*set_affinity)(unsigned int irq,
const struct cpumask *dest);
int (*retrigger)(unsigned int irq);
int (*set_type)(unsigned int irq, unsigned int flow_type);
int (*set_wake)(unsigned int irq, unsigned int on);

void (*bus_lock)(unsigned int irq);
void (*bus_sync_unlock)(unsigned int irq);

/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
void (*release)(unsigned int irq, void *dev_id);
#endif
/*
* For compatibility, ->typename is copied into ->name.
* Will disappear.
*/
const char *typename;
};
1.4.3 中断处理函数的 Representation —— irqaction

irqaction 的定义如下:

/**
* struct irqaction - per interrupt action descriptor
* @handler: interrupt handler function
* @flags: flags (see IRQF_* above)
* @name: name of the device
* @dev_id: cookie to identify the device
* @next: pointer to the next irqaction for shared interrupts
* @irq: interrupt number
* @dir: pointer to the proc/irq/NN/name entry
* @thread_fn: interupt handler function for threaded interrupts
* @thread: thread pointer for threaded interrupts
* @thread_flags: flags related to @thread
*/
struct irqaction {
irq_handler_t handler;
unsigned long flags;
const char *name;
void *dev_id;
struct irqaction *next;
int irq;
struct proc_dir_entry *dir;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
};

其中:

  • handler:
    是中断处理程序的函数指针,中断发生后会被内核调用。
  • name 和 devid:
    这两个东西用来唯一的标识一个 handler 。
    • name:
      表示设备的一个字符串,如 "e1000", "ncr53c8xx" 等等;
    • dev_id:
      一个指针,用于从内核的所有数据结构中唯一的标识该设备。
  • flags:
    用于表示该 handler 的若干特性,包括:
    • IRQF_SHARED: 共享 IRQ 。
    • IRQF_SAMPLE)RANDOM: IRQ 对内核的熵池产生作用。
    • IRQF_DISABLED: 该 IRQ handler 运行时,必须禁用其他的中断。
    • IRQF_TIMER : 表明该中断一个计时器中断。
  • next:
    next 用于实现 IRQ 的共享。 前面提到, IRQ 是可以共享的, 共享同样的 IRQ 的每个 handler 通过 next 来形成一个链表。 中断发生后,内核扫描这个 IRQ 指定的链表上的所有 irqaction ,进而找到真正应该执行的 handler 。

1.5 Interrupt Flow Handling (中断流处理)

前面提到了 IRQ 子系统的三个 抽象层: High-level interrupt Service routine, Interrupt Flow Handling, Chip-level hardware Encapsulation. 本节描述 Interrupt Flow Handling 的过程, 即, 处理边沿触发、水平触发等等的过程。

1.5.1 设置控制硬件 (Setting Controller Hardware)

内核提供了一些标准函数用于注册 irq_chips 和设置 flow handlers ,如下:

int set_irq_chip(unsigned int irq, struct irq_chip *chip);
int set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
int set_irq_chip_data(unsigned int irq, void *data);
void set_irq_chip_and_handler(unsigned int irq, struct irq_chip *chip,
irq_flow_handler_t handle);
void set_irq_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
irq_flow_handler_t handle, const char *name);
  • set_irq_chip:
    该函数用于将一个 IRQ Chip (irq_chip 的实例) 与一个特定的中断相关联。 除了从 irq_desc 中选择合适的元素外,该函数还负责设置 chip 中的指针, 并且设置默认的中断处理函数。
  • set_irq_handler 和 set_irq_chained_handler:
    为指定的 IRQ 设置 flow handler function, 两者在内部均使用 __set_irq_handler , __set_irq_handler 进行一些检查,并设置 irq_desc[irq]->handle_irq.
  • set_chip_and_handler:
    一个辅助函数,使用它可以避免逐个的使用前面提到的三个函数。
1.5.2 Flow Handling

前面提到,中断有不同的触发方式: edge-trggering 或者 level-triggering, 内核对他们的处理方式有所不同。 但相同的是,这两个处理中,都需要在 flow-handling 完成后负责调用 high-level 的中断处理函数。

  • 边沿触发中断 (Edge-Triggered Interrupts)

    边沿触发方式在现代的硬件中是最常见的一种方式,该方式的默认处理函数为 handle_edge_irq .

    边沿触发中断,在处理时候一般无需禁用该中断源。这样,在多处理器系统上,可能会出现这种情况: 一个处理器上正在处理这个Flow-Handnle,此时在这个处理过程尚未完成的时候,又产生了另外一个边沿触发的中断。 对于这种情况, handler_edge_irq 的处理方法是:先更新这个 irq 的 irq_desc 上的状态为 PENDING, 然后暂时将这个中断 mask 掉(这里的 mask 是通过 irq_chip 结构提供的函数在硬件上操作,而不是内核中软件意义上的 mask); 而前面负责处理这个 irq 的 CPU , 在处理完了一个 IRQ 后,检查这个 irq_desc 的状态,如果为 PENDING, 则表示在前面的处理过程中又有新的中断信号产生了,需要继续处理,直到这个 irq_desc 的状态不是 PENDING 为止。

    在 Flow-Handle 的每个循环的结尾, hander_edge_irq 都会以 iqd_desc->action 为参数来通过 handleIRQevent 调用 High-level 处理函数 。

    整体流程如下图所示:

                      handle_edge_irq 流程

  • 水平触发中断 (Level-Triggered Interrupts)

    Level-Triggered Interrups 由函数 handle_level_irq 负责处理,他的流程比 handle_edge_irq 要简单一些, 如下图所示:

    handle_level_irq 流程

    需要注意的是, Level-Triggered Interrups 在处理的时候必须要将其 Mask 掉,这个 Mask 的过程通过函数 mask_ack_irq 来完成。 maks_ack_irq 不但将 irq_desc 的状态设置为 mask ,同时还调用了 chip->mask_ack 来设置硬件的 mask , 并向硬件发送 ACK 。 对于多核 CPU 所潜在的竞争冒险1,可以通过检测 irq_desc 的状态来避免 —— 一旦检测到 irq_desc->status 中包含了 IRQ_INPROGRESS , 则表明该 irq 正在被处理,直接退出即可。

    同边沿触发的处理一样,这里也通过 handle_IRQ_event 来调用了 High-level 的处理函数。

  • 其他类型的中断

    除了上述两种类型的的中断之外,还有一些其他不常用的中断,内核为他们提供了默认的 handler 。

    • handle_fasteoi_irq

      很多现代的 IRQ 硬件仅需要做一点点流处理工作 (Flow-Handle),对他们来讲,在 IRQ 处理完毕之后, 只需要调用一个 Chip-specific 的函数: chip->eoi 就可以了, 函数 handle_fasteoi_irq 负责这个工作。

    • handle_simple_irq

      一些实在是很简单的中断根本就不需要流控制,内核为他们提供了 handle_simple_irq 。

    • handle_percpu_irq

      有些 IRQ 只能发上在 SMP 上的某个指定的 CPU 上,这种 IRQ 被成为 Per-CPU IRQ , 内核提供了 handle_percpu_irq 来处理他们。该函数负责在中断处理完成之后向硬件汇报中断的接收,并调用 EOI 。

1.6 IRQ 的初始化及预留
1.6.1 IRQ 的注册

函数 request_irq 用于注册 IRQ , 其定义为:

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);
}

可见,该函数是 request_threaded_irq 的一个 wrapper ,并将 request_threaded_irq 的参数 thread_fn 设置为空。 request_threaded_irq 的流程图如下图所示:

request_threaded_irq

该函数首先从内核中获取了这个 irq 对应的 irq_desc, 然后创建 irqaction, 设置这个 action 的 handler, flags, name, 和 dev_id 。 随后,将其余的工作交给了函数 __setup_irq, 由 __setup_irq 完后后续的工作。

__setup_irq 的主要作用如下:

  • 设置内核熵池

    前面提到过,内核通过一系列的事件的相关信息来生成随机数 (/dev/random),这里,如果传入的 flags 中声明了 IRQF_SAMPLE_RANDOM ,则调用 rand_initialize_irq 将 IRQ 添加到熵池所需的相关数据结构中。

  • 添加 irqaction

    前面提到,内核有一个全局的 irq_desc Array, 从这个 Array 中可以根据 IRQ Number 来找到相应的 irq_desc ; 此外,每个 irq_desc 中有一个 irqaction list ,这个 list 中记录了该 irq 中断发生时候需要调用的每一个 irqaction 。 __setup_irq 需要将传入的 irqaction 添加到这个 list 的队尾。

  • 其他的 irq flags 检查

    根据传入参数的flags, 做一些别的检查和设置,没具体细看。

  • 注册 proc 文件系统

    __setup_irq 的最后,调用 register_irq_proc ,在 proc/irq 中为相应的 IRQ 创建了节点。 然后又调用 register_handler_proc , 生成/proc/irq/NUM/NAME 。从而使用户可以从 procfs 中得到该 IRQ 的信息。

1.6.2 释放 IRQ

在释放 IRQ 的时候,仅提供一个 IRQ Number 不行的,还必须提供这个 IRQ 对应的 dev_id. 该过程由函数 free_irq 来完成, free_irq 是 __free_irq 的一个包装,调 用 __free_irq 来完成 irqaction 的注销。

__free_irq 根据传入的 dev_id 从这个 IRQ 的所有 irqaction 上找到设备对应的那个 irqaction , 并将其从 IRQ 的 irqaction List 中移走; 如果移走的这个 handler 是这个 IRQ Line 上唯一的一个, 那么还需要将这个 IRQ Line Diable。 随后,清理 proc 文件系统中的结点, 并将 action 这个结构返回给 free_irq, 由 free_irq 负责将数据结构 free 。

1.6.3 中断的注册 (Registering Interrupts, 指系统中断)

前面提到的 irq_request 仅用于外设的 IRQ 申请, 而对于 CPU 本身已经软件上的中断,处理的流程与此不同。 诸如软件发出的中断、异常以及陷阱,这些东西的注册是在系统初始化的时候执行的,并且初始化完成之后,在整个系统的活动周期内不会发生改变。 由于系统的中断是不能共享的(也没有必要共享,资源多得很),内核所需要做的,仅仅是将中断号和处理函数相关联。

一般来讲,内核对于这种系统中断的响应有如下两种:

  • 当错误发生的时候,向当前用户进程发送信号:

    例如,在 IA-32 和 AMD64 系统,当一个数字被0除的时候,会产生中断 0 , 这个中断会调用处理函数 divide_error ,并向用户进程发送信号 —— SIGPFE 。

  • 错误发生后, 内核自动更正错误:

    例如 IA-32 系统下, 中断 14 被用来作为 page fault (内存却页或者页面错误)的信号,当这个错误发生的时候,内核可以自动对该错误进行纠正。

Footnotes:

1 竞争冒险和前面提到的边沿触发中断的嵌套不同,由于处理 Level-Triggered Interrups 时候, 中断源那个硬件已经被暂时 Mask 掉,因此在一个水平触发中断的处理期间,同一个设备不会产生另外一个水平触发中断。 因此,竞争冒险指的是多个 CPU 同时选定要处理同一个 irq 。

阅读(956) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~