Linux异常处理体系结构

Posted 昨天注的册

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux异常处理体系结构相关的知识,希望对你有一定的参考价值。

更新记录

version status description date author
V1.0 C Create Document 2019.1.1 John Wan
V2.0 A 添加案例 2019.1.13 John Wan

status:
C―― Create,
A—— Add,
M—— Modify,
D—— Delete。

注:内核版本 3.0.15

1、异常处理概述

1.1 异常的作用

??异常:就是可以打断CPU正常运行流程的一些事情,比如外部中断、未定义的指令、试图修改只读的数据、执行 swi 指令(Software Interrupt Instruction, 软件中断指令)等。当这些事情发生时,CPU暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。

1.2 常见的异常类型

??技术图片

2、异常处理流程

2.1 异常处理框架

??当异常来临时,处理流程是这样:1)保护现场;2)异常处理;3)恢复现场。

??那么 LInux内核为应对这么多的硬件环境,是如何找到对应的异常处理函数?

??硬件环境:exynos4412

??内核版本:linux-kernel 3.0.15

??内核要进行异常处理,那么首先就要配置对应硬件的异常向量表

2.1.1 设置异常向量表

??在内核的初始化时,就应配置好异常向量表。

??内核的初始化函数:init/main.c中的 start_kernel()

??板级的初始化函数:内核初始化中的setup_arch(&command_line);

??向量表的初始化函数:板级初始化中的early_trap_init();

??early_trap_init()被用来设置各种异常的处理向量,包括中断向量。所谓的“向量”,就是一些被安放在固定位置的代码,当发生异常时,CPU 会自动执行这些固定位置上的指令。ARM 架构 CPU 的异常向量基址可以是 0x00000000,也可以是0xffff0000,LInux内核使用后者。该函数将异常向量表复制到 0xffff0000处,部分代码如下:

void __init early_trap_init(void)
{
#if defined(CONFIG_CPU_USE_DOMAINS)
    unsigned long vectors = CONFIG_VECTORS_BASE;
#else
    unsigned long vectors = (unsigned long)vectors_page;
#endif
......

    /*
     * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
     * into the vector page, mapped at 0xffff0000, and ensure these
     * are visible to the instruction stream.
     */
    memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
    memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
    memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
......
}

??其中 变量vectors等于 CONFIG_VECTORS_BASE,等于 0xffff0000。地址 __vectors_end ~ __vectors_start之间就是异常向量。

2.1.2 寻找异常处理函数(C函数)

??异常向量的代码很简单,它们只是一些跳转指令。发生异常时,CPU 自动执行这些指令,跳转去执行更复杂的代码,比如保存被中断程序的执行环境,调用异常处理函数,恢复被中断程序的执行环境并重新运行。这些"更复杂的代码"在地址_stubs_end ~ __stubs_start之间,它们被复制到0xffff0000 + 0x200处

??arch/arm/kernel/entry-armv.S中:

    .equ    stubs_offset, __vectors_start + 0x200 - __stubs_start

    .globl  __vectors_start
__vectors_start:
 ARM(   swi SYS_ERROR0  )               /*复位时,CPU将执行这条指令*/
 THUMB( svc #0      )
 THUMB( nop         )
    W(b)    vector_und + stubs_offset   /*未定义异常时,CPU执行这*/
    W(ldr)  pc, .LCvswi + stubs_offset  /*swi异常*/
    W(b)    vector_pabt + stubs_offset  /*指令预取中止*/
    W(b)    vector_dabt + stubs_offset  /*数据访问中止*/
    W(b)    vector_addrexcptn + stubs_offset    /*没有用到*/
    W(b)    vector_irq + stubs_offset   /*中断*/
    W(b)    vector_fiq + stubs_offset   /*快速中断*/

    .globl  __vectors_end
__vectors_end:

??其中的"stubs_offset"用来重新定位跳转的位置(向量被复制到地址0xffff0000处,跳转的目的代码被复制到地址0xffff0000 + 0x200处)。

??其中 vector_und、vector_pabt等表示要跳转去执行的代码。以vector_irq为例,它仍处于该文件中,通过vector_stub宏来定义,代码如下:

/*
 * Interrupt dispatcher
 */
    vector_stub irq, IRQ_MODE, 4

    .long   __irq_usr           @  0  (USR_26 / USR_32),在用户模式执行了未定义指令
    .long   __irq_invalid           @  1  (FIQ_26 / FIQ_32),在FIQ模式执行了未定义指令
    .long   __irq_invalid           @  2  (IRQ_26 / IRQ_32),在IRQ模式执行了未定义指令
    .long   __irq_svc           @  3  (SVC_26 / SVC_32),在管理模式执行了未定义指令
    .long   __irq_invalid           @  4
    .long   __irq_invalid           @  5
    .long   __irq_invalid           @  6
    .long   __irq_invalid           @  7
    .long   __irq_invalid           @  8
    .long   __irq_invalid           @  9
    .long   __irq_invalid           @  a
    .long   __irq_invalid           @  b
    .long   __irq_invalid           @  c
    .long   __irq_invalid           @  d
    .long   __irq_invalid           @  e
    .long   __irq_invalid           @  f

??vector_stub是一个宏,它根据后面的参数"irq, IRQ_MODE, 4"定义了以vector_irq为标号的一段代码,代入以下代码。

/*
 * Vector stubs.
 *
 * This code is copied to 0xffff0200 so we can use branches in the
 * vectors, rather than ldr's.  Note that this code must not
 * exceed 0x300 bytes.
 *
 * Common stub entry macro:
 *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 *
 * SP points to a minimal amount of processor-private memory, the address
 * of which is copied into r0 for the mode specific abort handler.
 */
    .macro  vector_stub, name, mode, correction=0
    .align  5

vector_
ame:
    .if correction
    sub lr, lr, #correction
    .endif

    @
    @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
    @ (parent CPSR)
    @
    stmia   sp, {r0, lr}        @ save r0, lr
    mrs lr, spsr
    str lr, [sp, #8]        @ save spsr

    @
    @ Prepare for SVC32 mode.  IRQs remain disabled.
    @
    mrs r0, cpsr
    eor r0, r0, #(mode ^ SVC_MODE | PSR_ISETSTATE)
    msr spsr_cxsf, r0

    @
    @ the branch table must immediately follow this code
    @
    and lr, lr, #0x0f
 THUMB( adr r0, 1f          )
 THUMB( ldr lr, [r0, lr, lsl #2]    )
    mov r0, sp
 ARM(   ldr lr, [pc, lr, lsl #2]    )
    movs    pc, lr          @ branch to handler in SVC mode
ENDPROC(vector_
ame)

??vector_stub宏的功能:

1)计算处理完异常后的返回地址;

2)保存一些寄存器(比如:r0,lr,spsr);

3)进入管理模式;

4)最后根据被中断的工作模式调用vector_irq中的某个跳转分支。

??vector_irq中的代码表示在各个工作模式下执行中断指令时,发生的异常的处理分支。比如"__irq_usr"表示在用户模式下执行了中断指令时,所发生的中断异常将由它来处理,在其它工作模式下不可能发生中断指令异常,否则使用"__irq_invalid"来处理错误。

??对应的vector_und中的代码表示在各个工作模式下执行未定义指令时,发生的异常的处理分支。

??ARM架构CPU中使用4位数据来表示工作模式(目前只有7种工作模式),所以共有16个跳转分支。不同的跳转分支(比如 __irq_usr、__irq_svc)只是在它们的入口处(比如保存被中断程序的寄存器)稍有差别,后续的处理大体相同,都是调用C函数。

??以外部中断为例:

??通过前面的说明调用"__irq_usr"

__irq_usr:
    usr_entry
    kuser_cmpxchg_check

#ifdef CONFIG_IRQSOFF_TRACER
    bl  trace_hardirqs_off
#endif

    get_thread_info tsk
#ifdef CONFIG_PREEMPT
    ldr r8, [tsk, #TI_PREEMPT]      @ get preempt count
    add r7, r8, #1          @ increment it
    str r7, [tsk, #TI_PREEMPT]
#endif

    irq_handler
#ifdef CONFIG_PREEMPT
    ldr r0, [tsk, #TI_PREEMPT]
    str r8, [tsk, #TI_PREEMPT]
    teq r0, r7
 ARM(   strne   r0, [r0, -r0]   )
 THUMB( movne   r0, #0      )
 THUMB( strne   r0, [r0]    )
#endif

    mov why, #0
    b   ret_to_user_from_irq
 UNWIND(.fnend      )
ENDPROC(__irq_usr)

??其中"usr_entry":保存相关寄存器;

??其中"irq_handler":就是异常处理函数入口;

??其中"b ret_to_user_from_irq":就是返回。

??那么通过"irq_handler"找到"arch_irq_handler_default",该函数的原型在"arch/arm/include/asm/entry-macro-multi.S"中:

/*
 * Interrupt handling.  Preserves r7, r8, r9
 */
    .macro  arch_irq_handler_default
    get_irqnr_preamble r5, lr
1:  get_irqnr_and_base r0, r6, r5, lr
    movne   r1, sp
    @
    @ routine called with r0 = irq number, r1 = struct pt_regs *
    @
    adrne   lr, BSYM(1b)
    bne asm_do_IRQ

??其中"asm_do_IRQ"就是该中断对应的异常处理函数(C函数)。

技术图片

注意:上面的图是基于linux-kernel 2.6,原理相同,但所处文件位置有差异

??小结"early_trap_init()"函数搭建了 Linux 异常的处理框架,当异常发生时,内核是如何找到对应异常类型的C处理函数(比如上面的中断,最终找到"asm_do_IRQ")。

??前面了解了内核是如何找到对应异常的异常处理函数(C函数),那么该函数是如何进行处理的呢?

2.1.3 “asm_do_IRQ()”的作用

??"arch/arm/kernel/irq.c"中函数的原型:

/*
 * do_IRQ handles all hardware IRQ's.  Decoded IRQs should not
 * come via this function.  Instead, they should provide their
 * own 'handler'
 */
asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);

    irq_enter();

    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (unlikely(irq >= nr_irqs)) {
        if (printk_ratelimit())
            printk(KERN_WARNING "Bad IRQ%u
", irq);
        ack_bad_irq(irq);
    } else {
        generic_handle_irq(irq);
    }

    /* AT91 specific workaround */
    irq_finish(irq);

    irq_exit();
    set_irq_regs(old_regs);
}

??通过该函数进入"generic_handle_irq()",再进入"generic_handle_irq_desc()"

/*
 * Architectures call this to let the generic IRQ layer
 * handle an interrupt. If the descriptor is attached to an
 * irqchip-style controller then we call the ->handle_irq() handler,
 * and it calls __do_IRQ() if it's attached to an irqtype-style controller.
 */
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
    desc->handle_irq(irq, desc);
}

??最终是根据中断号,调用以该中断号为下标的数组数据类型成员中的handle_irq:irq_desc[irq].handle_irq(irq, &irq_desc[irq])。那么 struct irq_desc是什么东西?

2.1.5 irq_desc结构数组

??Linux 内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断:每个数组项对应一个中断(也可能是一组中断,共用相同的中断号),里面记录了中断的名称、中断状态、中断标记(比如中断类型,是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断)、提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。原型在include/linux/irqdesc.h

/**
 * struct irq_desc - interrupt descriptor
 * @irq_data:       per irq and chip data passed down to chip functions
 * @timer_rand_state:   pointer to timer rand state struct
 * @kstat_irqs:     irq stats per cpu
 * @handle_irq:     highlevel irq-events handler
 * @preflow_handler:    handler called before the flow handler (currently used by sparc)
 * @action:     the irq action chain
 * @status:     status information
 * @core_internal_state__do_not_mess_with_it: core internal status information
 * @depth:      disable-depth, for nested irq_disable() calls
 * @wake_depth:     enable depth, for multiple irq_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_hint:  hint to user space for preferred irq affinity
 * @affinity_notify:    context for notification of affinity changes
 * @pending_mask:   pending rebalanced interrupts
 * @threads_oneshot:    bitfield to handle shared oneshot threads
 * @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 {
    struct irq_data     irq_data;
    struct timer_rand_state *timer_rand_state;
    unsigned int __percpu   *kstat_irqs;
    irq_flow_handler_t  handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
    irq_preflow_handler_t   preflow_handler;
#endif
    struct irqaction    *action;    /* IRQ action list */
    unsigned int        status_use_accessors;
    unsigned int        core_internal_state__do_not_mess_with_it;
    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
    const struct cpumask    *affinity_hint;
    struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
    cpumask_var_t       pending_mask;
#endif
#endif
    unsigned long       threads_oneshot;
    atomic_t        threads_active;
    wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PROC_FS
    struct proc_dir_entry   *dir;
#endif
    const char      *name;
} ____cacheline_internodealigned_in_smp;

??内核与驱动程序关于中断的联系就是通过中断对应的irq_desc结构来沟通的。

2.1.5.1 成员irq_data

include/linux/irq.h

/**
 * struct irq_data - per irq and irq chip data passed down to chip functions
 * @irq:        interrupt number
 * @node:       node index useful for balancing
 * @state_use_accessors: status information for irq chip functions.
 *          Use accessor functions to deal with it
 * @chip:       low level interrupt hardware access
 * @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
 * @msi_desc:       MSI descriptor
 * @affinity:       IRQ affinity on SMP
 *
 * The fields here need to overlay the ones in irq_desc until we
 * cleaned up the direct references and switched everything over to
 * irq_data.
 */
struct irq_data {
    unsigned int        irq;
    unsigned int        node;
    unsigned int        state_use_accessors;
    struct irq_chip     *chip;
    void            *handler_data;
    void            *chip_data;
    struct msi_desc     *msi_desc;
#ifdef CONFIG_SMP
    cpumask_var_t       affinity;
#endif
};

??其中irq_chip结构类型中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断、使能中断、清除中断等:

/**
 * struct irq_chip - hardware interrupt chip descriptor
 *
 * @name:       name for /proc/interrupts
 * @irq_startup:    start up the interrupt (defaults to ->enable if NULL)
 * @irq_shutdown:   shut down the interrupt (defaults to ->disable if NULL)
 * @irq_enable:     enable the interrupt (defaults to chip->unmask if NULL)
 * @irq_disable:    disable the interrupt
 * @irq_ack:        start of a new interrupt
 * @irq_mask:       mask an interrupt source
 * @irq_mask_ack:   ack and mask an interrupt source
 * @irq_unmask:     unmask an interrupt source
 * @irq_eoi:        end of interrupt
 * @irq_set_affinity:   set the CPU affinity on SMP machines
 * @irq_retrigger:  resend an IRQ to the CPU
 * @irq_set_type:   set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
 * @irq_set_wake:   enable/disable power-management wake-on of an IRQ
 * @irq_bus_lock:   function to lock access to slow bus (i2c) chips
 * @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
 * @irq_cpu_online: configure an interrupt source for a secondary CPU
 * @irq_cpu_offline:    un-configure an interrupt source for a secondary CPU
 * @irq_suspend:    function called from core code on suspend once per chip
 * @irq_resume:     function called from core code on resume once per chip
 * @irq_pm_shutdown:    function called from core code on shutdown once per chip
 * @irq_print_chip: optional to print special chip info in show_interrupts
 * @flags:      chip specific flags
 *
 * @release:        release function solely used by UML
 */
struct irq_chip {
    const char  *name;
    unsigned int    (*irq_startup)(struct irq_data *data);
    void        (*irq_shutdown)(struct irq_data *data);
    void        (*irq_enable)(struct irq_data *data);
    void        (*irq_disable)(struct irq_data *data);

    void        (*irq_ack)(struct irq_data *data);
    void        (*irq_mask)(struct irq_data *data);
    void        (*irq_mask_ack)(struct irq_data *data);
    void        (*irq_unmask)(struct irq_data *data);
    void        (*irq_eoi)(struct irq_data *data);

    int     (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
    int     (*irq_retrigger)(struct irq_data *data);
    int     (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
    int     (*irq_set_wake)(struct irq_data *data, unsigned int on);

    void        (*irq_bus_lock)(struct irq_data *data);
    void        (*irq_bus_sync_unlock)(struct irq_data *data);

    void        (*irq_cpu_online)(struct irq_data *data);
    void        (*irq_cpu_offline)(struct irq_data *data);

    void        (*irq_suspend)(struct irq_data *data);
    void        (*irq_resume)(struct irq_data *data);
    void        (*irq_pm_shutdown)(struct irq_data *data);

    void        (*irq_print_chip)(struct irq_data *data, struct seq_file *p);

    unsigned long   flags;

    /* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
    void        (*release)(unsigned int irq, void *dev_id);
#endif
};
2.1.5.2 成员*action

??include/linux/interrupt.h:用户注册的每个中断处理函数用一个irqaction结构来表示,一个中断(比如共享中断)可以有多个处理函数,它们的irqaction结构链接成一个链表,以action为表头。

/**
 * 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:  interrupt handler function for threaded interrupts
 * @thread: thread pointer for threaded interrupts
 * @thread_flags:   flags related to @thread
 * @thread_mask:    bitmask for keeping track of @thread activity
 */
struct irqaction {
    irq_handler_t handler;
    unsigned long flags;
    void *dev_id;
    struct irqaction *next;
    int irq;
    irq_handler_t thread_fn;
    struct task_struct *thread;
    unsigned long thread_flags;
    unsigned long thread_mask;
    const char *name;
    struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
2.1.5.3 成员handle_irq
typedef void (*irq_flow_handler_t)(unsigned int irq, struct irq_desc *desc);

??handle_irq是这个或这组中断的处理函数入口。发生中断时,总入口函数asm_do_IRQ将根据中断号调用相应irq_desc数组项中的handle_irqhandle_irq使用chip结构中的函数来清除、屏蔽或重新使能中断,还一一调用用户在action链表中注册的中断处理函数。

2.1.5.4 小结

??irq_desc结构数组、它的成员"struct irq_chip *chip""struct irqaction *action",这3种数据结构构成了异常处理体系的框架。3者的关系如下:

技术图片

2.1.6 exynos_irq_eint()外部中断初始化

??前面了解整个架构,及其架构中重要结构、重要函数的功能,那么内核是在哪里对这些进行初始化的呢?

??1)由kernel/irq/chip.c中的__irq_set_handler()中向该中断对应的irq_desc结构成员handle_irq赋值了传入handle参数。

??2)由kernel/irq/chip.c中的irq_set_chip_and_handler_name()调用了__irq_set_handler(),并且还调用了irq_set_chip(),而该函数是向该中断对应的irq_desc结构成员chip赋值了传入chip参数。

??3)由include/linux/irq.h中的irq_set_chip_and_handler()调用irq_set_chip_and_handler_name()

??4)由arch/arm/mach-exynos/irq-eint.c中的exynos_init_irq_eint()调用了irq_set_chip_and_handler()

??那么分析exynos_init_irq_eint()函数:

??1)arch_initcall(exynos_init_irq_eint);与驱动加载的类似,有了这句,那么在内核进行初始化时,自动调用exynos_init_irq_eint()函数。

??2)调用irq_set_chip_and_handler()初始化外部中断的irq_desc结构成员.chipexynos_irq_eint,其为外部中断对应的底层硬件操作。

??3)调用irq_set_chip_and_handler()初始化外部中断的irq_desc结构成员.handle_irqhandle_level_irq(),其为中断入口函数。

static struct irq_chip exynos_irq_eint = {
    .name       = "exynos-eint",
    .irq_mask   = exynos_irq_eint_mask,
    .irq_unmask = exynos_irq_eint_unmask,
    .irq_mask_ack   = exynos_irq_eint_maskack,
    .irq_ack    = exynos_irq_eint_ack,
    .irq_set_type   = exynos_irq_eint_set_type,
#ifdef CONFIG_PM
    .irq_set_wake   = s3c_irqext_wake,
#endif
};

int __init exynos_init_irq_eint(void)
{
    int irq;

    for (irq = 0 ; irq <= 31 ; irq++) {
        irq_set_chip_and_handler(IRQ_EINT(irq), &exynos_irq_eint,
                     handle_level_irq);
        set_irq_flags(IRQ_EINT(irq), IRQF_VALID);
    }

    ......
}

arch_initcall(exynos_init_irq_eint);

??由handle_level_irq()函数找到handle_irq_event()函数,进而找到handle_irq_event_percpu()函数(kernel/irq/handle.c)。

??handle_irq_event()函数:1)清中断;2)设置中断状态;3)调用handle_irq_event_percpu()函数;4)清除中断状态。

??handle_irq_event_percpu()函数:用一个循环,遍历中断对应irq_desc结构成员action链表中的所有节点,节点即用户注册的中断服务函数。

irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
    unsigned int random = 0, irq = desc->irq_data.irq;

    ......
    do {
        irqreturn_t res;

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

        retval |= res;
        action = action->next;
    } while (action);
    
    ......
}

??dev_id的功能:一个中断号,可以是一个中断,也可以是一组中断(例如外部中断16~31),那么只使用中断号,不能区分同一组中断中的哪个,因此引入dev_id。例如在action时需传入dev_id,用来区分同一中断号中挂载在action链表中的的多个中断服务函数。

2.2 中断处理流程

??1)发生中断时,CPU执行异常向量 vector_irq的代码。

??2)在vector_irq里面,最终会调用中断处理的总入口函数asm_do_IRQ

??3)asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq

??4)handle_irq会使用chip成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等。

??5)handle_irq逐个调用用户在action链表中注册的处理函数。

??可见,中断体系结构的初始化就是构造这些数据结构,比如irq_desc数组项中的handle_irq、chip等成员。用户注册中断时就是构造action链表;用户卸载时就是从action链表中去除不需要的项。

2.3 中断的注册与卸载

2.3.1 “request_irq()”注册中断

??include/linux/interrupt.h

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

??kernel/irq/manage.c

/**
 *  request_threaded_irq - allocate an interrupt line
 *  @irq: Interrupt line to allocate
 *  @handler: Function to be called when the IRQ occurs.
 *        Primary handler for threaded interrupts
 *        If NULL and thread_fn != NULL the default
 *        primary handler is installed
 *  @thread_fn: Function called from the irq handler thread
 *          If NULL, no irq thread is created
 *  @irqflags: Interrupt type flags
 *  @devname: An ascii name for the claiming device
 *  @dev_id: A cookie passed back to the handler function
 *
 *  This call allocates interrupt resources and enables the
 *  interrupt line and IRQ handling. From the point this
 *  call is made your handler function may be invoked. Since
 *  your handler function must clear any interrupt the board
 *  raises, you must take care both to initialise your hardware
 *  and to set up the interrupt handler in the right order.
 *
 *  If you want to set up a threaded irq handler for your device
 *  then you need to supply @handler and @thread_fn. @handler ist
 *  still called in hard interrupt context and has to check
 *  whether the interrupt originates from the device. If yes it
 *  needs to disable the interrupt on the device and return
 *  IRQ_WAKE_THREAD which will wake up the handler thread and run
 *  @thread_fn. This split handler design is necessary to support
 *  shared interrupts.
 *
 *  Dev_id must be globally unique. Normally the address of the
 *  device data structure is used as the cookie. Since the handler
 *  receives this value it makes sense to use it.
 *
 *  If your interrupt is shared you must pass a non NULL dev_id
 *  as this is required when freeing the interrupt.
 *
 *  Flags:
 *
 *  IRQF_SHARED     Interrupt is shared
 *  IRQF_SAMPLE_RANDOM  The interrupt can be used for entropy
 *  IRQF_TRIGGER_*      Specify active edge(s) or level
 *
 */
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
             irq_handler_t thread_fn, unsigned long irqflags,
             const char *devname, void *dev_id)
{
    struct irqaction *action;
    struct irq_desc *desc;
    int retval;

    /*
     * Sanity-check: shared interrupts must pass in a real dev-ID,
     * otherwise we'll have trouble later trying to figure out
     * which interrupt is which (messes up the interrupt freeing
     * logic etc).
     */
    if ((irqflags & IRQF_SHARED) && !dev_id)
        return -EINVAL;

    desc = irq_to_desc(irq);
    if (!desc)
        return -EINVAL;

    if (!irq_settings_can_request(desc))
        return -EINVAL;

    if (!handler) {
        if (!thread_fn)
            return -EINVAL;
        handler = irq_default_primary_handler;
    }

    action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
    if (!action)
        return -ENOMEM;

    action->handler = handler;
    action->thread_fn = thread_fn;
    action->flags = irqflags;
    action->name = devname;
    action->dev_id = dev_id;

    chip_bus_lock(desc);
    retval = __setup_irq(irq, desc, action);
    chip_bus_sync_unlock(desc);

    if (retval)
        kfree(action);

#ifdef CONFIG_DEBUG_SHIRQ_FIXME
    if (!retval && (irqflags & IRQF_SHARED)) {
        /*
         * It's a shared IRQ -- the driver ought to be prepared for it
         * to happen immediately, so let's make sure....
         * We disable the irq to make sure that a 'real' IRQ doesn't
         * run in parallel with our fake.
         */
        unsigned long flags;

        disable_irq(irq);
        local_irq_save(flags);

        handler(irq, dev_id);

        local_irq_restore(flags);
        enable_irq(irq);
    }
#endif
    return retval;
}

??传入的参数:

??1)irq:中断号,寻找对应的irq_desc结构。

??2)handler:中断服务函数,申请新的irqaction,并添加到action的链表中。

??3)flags:标志,是否共享、触发方式等等。

??4)name

??5)devdev_id,用来区分同一中断中的不同中断服务函数(action节点)。

??__setup_irq()函数:

??1)将新建的irqaction结构链入irq_desc[irq]结构成员的action链表中,有链表为空不为空两种可能。

??2)设置irq_desc[irq]结构中chip成员的还没设置的指针,让它们指向一些默认函数。

??3)设置中断的触发方式。

??4)启动中断。

2.3.2 "free_irq()"卸载中断

??中断是一种很稀缺的资源,当不再使用一个设备时,应该释放它占据的中断。

??kernel/irq/manage.c

/**
 *  free_irq - free an interrupt allocated with request_irq
 *  @irq: Interrupt line to free
 *  @dev_id: Device identity to free
 *
 *  Remove an interrupt handler. The handler is removed and if the
 *  interrupt line is no longer in use by any driver it is disabled.
 *  On a shared IRQ the caller must ensure the interrupt is disabled
 *  on the card it drives before calling this function. The function
 *  does not return until any executing interrupts for this IRQ
 *  have completed.
 *
 *  This function must not be called from interrupt context.
 */
void free_irq(unsigned int irq, void *dev_id)
{
    struct irq_desc *desc = irq_to_desc(irq);

    if (!desc)
        return;

#ifdef CONFIG_SMP
    if (WARN_ON(desc->affinity_notify))
        desc->affinity_notify = NULL;
#endif

    chip_bus_lock(desc);
    kfree(__free_irq(irq, dev_id));
    chip_bus_sync_unlock(desc);
}

??需要两个参数:irqdev_id,使用中断号irq定位action链表,再使用dev_idaction链表中找到要卸载的表项,将其移除。

3、案例:按键

3.1 轮询方式

3.2 中断方式

??驱动源码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>

/*驱动注册的头文件,包含驱动的结构体和注册和卸载的函数*/
#include <linux/platform_device.h>
/*Linux中申请GPIO的头文件*/
#include <linux/gpio.h>
/*三星平台的GPIO配置函数头文件*/
/*三星平台EXYNOS系列平台,GPIO配置参数宏定义头文件*/
#include <plat/gpio-cfg.h>
#include <mach/gpio.h>
/*三星平台4412平台,GPIO宏定义头文件*/
#include <mach/gpio-exynos4.h>

#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/interrupt.h>
#include <linux/sched.h>


#define DEVICE_NAME "buttons_irq"

static struct class *buttons_irq_class;
static struct device *buttons_irq_class_dev;

struct pin_desc {
    unsigned int pin;
    unsigned int key_val;
};

struct pin_desc pins_desc[5] = {
    {EXYNOS4_GPX1(1), 1},
    {EXYNOS4_GPX1(2), 2},
    {EXYNOS4_GPX3(3), 3},
    {EXYNOS4_GPX2(1), 4},
    {EXYNOS4_GPX2(0), 5},
};

static unsigned char key_val = 0;

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);   //声明一个队列

/* 中断事件标志, 中断服务程序将它置1,third_drv_read将它清0 */
static volatile int ev_press = 0;


static irqreturn_t buttons_irq(int irq, void *dev_id)
{
    struct pin_desc *pindesc = (struct pin_desc *)dev_id;
    unsigned int pinval;

    pinval = gpio_get_value(pindesc->pin);

    if (pinval)
        key_val = 0x80 | pindesc->key_val;
    else
        key_val = pindesc->key_val;

    printk(DEVICE_NAME " key press1
");

    ev_press = 1;   //中断发生
    wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
  
    printk(DEVICE_NAME " key press2
");

    return IRQ_RETVAL(IRQ_HANDLED);
}


static int buttons_irq_open(struct inode *pinode, struct file *pfile)
{
    /* 配置各按键引脚为外部中断 */
    request_irq(IRQ_EINT(9), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S1_Home", &pins_desc[0]);
    request_irq(IRQ_EINT(10), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S2_Back", &pins_desc[1]);
    request_irq(IRQ_EINT(27), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S3_Sleep", &pins_desc[2]);
    request_irq(IRQ_EINT(17), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S4_Vol+", &pins_desc[3]);
    request_irq(IRQ_EINT(16), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S5_Vol-", &pins_desc[4]);

    printk(DEVICE_NAME " I'm open!
");

    return 0;
}

static ssize_t buttons_irq_read(struct file *pfile, char __user *pbuf,
                                    size_t count, loff_t *ploff)
{
    if (count != 1)
        return -EINVAL;

    //如果没有按键动作,休眠
    wait_event_interruptible(button_waitq, ev_press);

    //如果有按键动作,返回键值
    copy_to_user(pbuf, &key_val, 1);
    ev_press = 0;

    printk(DEVICE_NAME " I'm read key_val %d!
", key_val);

    return 1;
}


static int buttons_irq_release(struct inode *pinode, struct file *pfile)
{
    free_irq(IRQ_EINT(9), &pins_desc[0]);
    free_irq(IRQ_EINT(10), &pins_desc[1]);
    free_irq(IRQ_EINT(27), &pins_desc[2]);
    free_irq(IRQ_EINT(17), &pins_desc[3]);
    free_irq(IRQ_EINT(16), &pins_desc[4]);

    printk(DEVICE_NAME " I'm release
");

    return 0;
}


static struct file_operations buttons_irq_fpos = {
    .owner = THIS_MODULE,
    .open = buttons_irq_open,
    .read = buttons_irq_read,
    .release = buttons_irq_release,
};

int major;
static int __init buttons_irq_init(void)
{
    /*注册主设备号*/
    major = register_chrdev(0, "buttons_irq", &buttons_irq_fpos);

    /*注册次设备号*/
    buttons_irq_class = class_create(THIS_MODULE, "buttons_irq");
    if (IS_ERR(buttons_irq_class))
        return PTR_ERR(buttons_irq_class);

    buttons_irq_class_dev = device_create(buttons_irq_class, NULL,
                                MKDEV(major, 0), NULL, "buttons_irq_minor");

    printk(DEVICE_NAME " initialized
");

    return 0;
}

static void __exit buttons_irq_exit(void)
{
    unregister_chrdev(major, "buttons_irq");

    device_unregister(buttons_irq_class_dev);

    class_destroy(buttons_irq_class);

    //return 0;
}

module_init(buttons_irq_init);
module_exit(buttons_irq_exit);

MODULE_LICENSE("GPL");

疑问:

?? 外部中断引脚的配置 IRQ_EINT(16) 原型处于哪个文件?猜测:需要详细了解 linux 移植的过程,了解各文件的功能。这里也牵涉到一个大问题,如何使用、查找硬件对应的文件。

?测试源码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>


int main(int argc, char **argv)
{
    int fd;
    unsigned char key_val;

    fd = open("/dev/buttons_irq_minor", O_RDWR);
    if (fd < 0)
        printf("can't open is!
");

    while (1) {
        read(fd, &key_val, sizeof(key_val));
        printf("key_val = 0x%x
", key_val);
    }

    return 0;
}

测试:

[[email protected]]# insmod buttons_irq.ko
[   28.776606] buttons_irq initialized
[[email protected]]# lsmod
buttons_irq 2690 0 - Live 0xbf000000
[[email protected]]# ./buttons_irq_test
[   38.455867] buttons_irq I'm open!
[   46.710833] buttons_irq key press1
[   46.712806] buttons_irq key press2
[   46.716404] buttons_irq I'm read key_val 1!
key_val = 0x1
[   46.929165] buttons_irq key press1
[   46.931133] buttons_irq key press2
[   46.934565] buttons_irq I'm read key_val 129!
key_val = 0x81
[   56.005297] buttons_irq key press1
[   56.007275] buttons_irq key press2
[   56.010717] buttons_irq I'm read key_val 2!
key_val = 0x2
[   56.216146] buttons_irq key press1
[   56.218121] buttons_irq key press2
[   56.221510] buttons_irq key press1
[   56.224874] buttons_irq key press2
[   56.228491] buttons_irq I'm read key_val 130!
key_val = 0x82
[   58.030606] buttons_irq key press1
[   58.032585] buttons_irq key press2
[   58.036019] buttons_irq I'm read key_val 3!
key_val = 0x3
[   58.255054] buttons_irq key press1
[   58.257035] buttons_irq key press2
[   58.260490] buttons_irq I'm read key_val 131!
key_val = 0x83
[   60.100599] buttons_irq key press1
[   60.102590] buttons_irq key press2
[   60.106080] buttons_irq I'm read key_val 4!
key_val = 0x4
[   60.301443] buttons_irq key press1
[   60.303423] buttons_irq key press2
[   60.306873] buttons_irq I'm read key_val 132!
key_val = 0x84
[   61.937725] buttons_irq key press1
[   61.939703] buttons_irq key press2
[   61.943151] buttons_irq I'm read key_val 5!
key_val = 0x5
[   62.161527] buttons_irq key press1
[   62.163515] buttons_irq key press2
[   62.167010] buttons_irq I'm read key_val 133!
key_val = 0x85
^C
[   84.825125] buttons_irq I'm release

双边沿触发,按下对应按键的 1~5,松开将最高位置位0x81~0x85。

也可以:

① 加载驱动 "insmod buttons_irq.ko"

② 后台运行测试程序 "./buttons_irq_test &"

③ 通过命令 “ps”,查看运行的进程;

④ 通过命令 "cat /proc/interrupts",查看注册的中断;

⑤ 然后,通过命令 "kill -9 PID",杀死进程,PID通过 ③ 查看。

⑥ 最后,通过命令 "rmmod buttons_irq",卸载驱动模块。

参考

  1. 嵌入式Linux应用开发完全手册 - 韦东山,20章
  2. 韦东山第一期视频,第十二课
  3. 迅为iTop4412资料

以上是关于Linux异常处理体系结构的主要内容,如果未能解决你的问题,请参考以下文章

linux异常处理体系结构

Linux驱动之异常处理体系结构简析

Linux异常处理体系结构

PCL异常处理:pcl 1.8.13rdpartyoostincludeoost-1_64oost ypeofmsvc ypeof_impl.hpp(125): error(代码片段

使用片段中的处理程序时出现非法状态异常

Java异常处理机制