Chinaunix首页 | 论坛 | 博客
  • 博客访问: 117275
  • 博文数量: 64
  • 博客积分: 186
  • 博客等级: 入伍新兵
  • 技术积分: 120
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-24 17:11
文章分类

全部博文(64)

文章存档

2014年(54)

2013年(2)

2012年(8)

我的朋友

分类: LINUX

2014-06-04 14:12:35

转载请注明出处:wloveg.blog.chinaunix.net

1.中断和异常

中断可分为同步(synchronous)中断和异步(asynchronous)中断:

(1)同步中断又称为异常,是当指令执行时由CPU控制单元产生,之所以称为同步,是因为只有在一条指令执行完成后CPU才会发出中断。典型的异常有缺页和除0。

(2)异步中断是指由其他硬件设备依照CPU时钟信号随机产生。

1.1 ARM异常与向量表

当一个异常或中断发生时,处理器会把pc设置为一个特定的存储器地址。这个地址放在一个被称为向量表(vector table)的特定的地址范围内。向量表的入口是一些跳转指令,跳转到专门处理某个异常或中断的子程序。

存储器映射地址0x00000000是为向量表保留的。在有些处理器中,向量表可以选择定位在存储空间的更高地址(从偏移量0xffff0000开始)。操作系统,如Linux和Microsoft的嵌入式操作系统,就可以利用这一特性。

当一个异常或中断发生时,处理器挂起正常的执行转而从向量表(见表1-1)装载指令。每一个向量表入口包含一条指向一个特定子程序的跳转指令。 


表1-1 ARM异常向量表
  • 复位向量是处理器上电后执行的第一条指令的位置。这条指令使处理器转到初始化代码处。
  • 未定义指令向量是在处理器不能对一条指令译码时使用的。
  • 软件中断向量是执行SWI指令时被调用的。SWI指令经常被用作调用一个操作系统例程的机制。
  • 预取指中止向量是发生在处理器试图从一个未获得正确访问权限的地址去取指时,实际上中止发生在“译码”阶段。
  • 数据中止向量是与预取指中止类似,发生在一条指令试图访问未获得正确访问权限的数据存储器时。
  • 中断请求向量是用于外部硬件中断处理器的正常执行流。只有当cpsr中的IRQ位未被屏蔽时才能发生。
  • 快速中断请求向量类似于中断请求,是为要求更短的中断响应时间的硬件保留的。只有当cpsr中的FIQ位未被屏蔽时才能发生。

2.相关数据结构

与中断相关的主要数据结构有三个,分别是:irq_descirq_chipirqaction。下面将介绍一下这三个数据结构的实现以及它们之间的关系。

内核通过irq_desc来描述一个中断,irq_desc结构体在include/linux/irq.h中定义:

  1. struct irq_desc {
  2.     unsigned int        irq; /*中断号*/
  3.     ……
  4.     irq_flow_handler_t     handle_irq; /*电流层中断处理函数 */
  5.     struct irq_chip        *chip; /*包含处理器相关的处理函数 */
  6.     struct msi_desc        *msi_desc;
  7.     void            *handler_data;
  8.     void            *chip_data;
  9.     struct irqaction    *action;     /* IRQ操作链表 */
  10.     unsigned int        status;     /* IRQ状态 */

  11.     unsigned int        depth;    
  12.     ……
  13.     const char        *name;
  14. } ____cacheline_internodealigned_in_smp;

其中,handle_irq指向电流层中断处理函数,主要用于对不同触发方式(电平、边沿)的处理。handler_data可以指向任何数据,供handle_irq函数使用。每当发生中断时,体系结构相关的代码都会调用handle_irq,该函数负责使用chip中提供的处理器相关的方法,来完成处理中断所必需的一些底层操作。用于不同中断类型的默认函数由内核提供,例如handle_level_irqhandle_edge_irq等。

中断控制器相关操作被封装到irq_chip,该结构体在include/linux/irq.h中定义:

  1. struct irq_chip {
  2.     const char    *name;
  3.     unsigned int    (*startup)(unsigned int irq);
  4.     void        (*shutdown)(unsigned int irq);
  5.     void        (*enable)(unsigned int irq);
  6.     void        (*disable)(unsigned int irq);

  7.     void        (*ack)(unsigned int irq);
  8.     void        (*mask)(unsigned int irq);
  9.     void        (*mask_ack)(unsigned int irq);
  10.     void        (*unmask)(unsigned int irq);
  11.     void        (*eoi)(unsigned int irq);

  12.     void        (*end)(unsigned int irq);
  13.     int        (*set_affinity)(unsigned int irq, const struct cpumask *dest);
  14.     int        (*retrigger)(unsigned int irq);
  15.     int        (*set_type)(unsigned int irq, unsigned int flow_type);
  16.     int        (*set_wake)(unsigned int irq, unsigned int on);
  17.     void        (*bus_lock)(unsigned int irq);
  18.     void        (*bus_sync_unlock)(unsigned int irq);
  19.     ……
  20.     const char    *typename;
  21. };

action指向一个操作链表,在中断发生时执行。由中断通知设备驱动程序,可以将与之相关的处理函数放置在此处。irqaction结构体在include/linux/interrupt.h中定义:

点击(此处)折叠或打开

  1. struct irqaction {
  2.     irq_handler_t handler;
  3.     unsigned long flags;
  4.     const char *name;
  5.     void *dev_id;
  6.     struct irqaction *next;
  7.     int irq;
  8.     struct proc_dir_entry *dir;
  9.     irq_handler_t thread_fn;
  10.     struct task_struct *thread;
  11.     unsigned long thread_flags;
  12. };

每个处理函数都对应该结构体的一个实例irqaction,该结构体中最重要的成员是handler,这是个函数指针。该函数指针就是通过request_irq来初始化的,在后续小节中将详细介绍。当系统产生中断,内核将调用该函数指针所指向的处理函数。namedev_id唯一地标识一个irqaction实例,name用于标识设备名,dev_id用于指向设备的数据结构实例。

      请注意irq_desc中的handle_irq不同于irqaction中的handler,这两个函数指针分别定义为:

点击(此处)折叠或打开

  1. typedef    void (*irq_flow_handler_t)(unsigned int irq, struct irq_desc *desc);
  2. typedef irqreturn_t (*irq_handler_t)(int, void *);

内核通过维护一个irq_desc的全局数组来管理所有的中断。该数组在kernel/irq/handle.c中定义:

点击(此处)折叠或打开

  1. struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
  2.     [0 ... NR_IRQS-1] = {
  3.         .status = IRQ_DISABLED,
  4.         .chip = &no_irq_chip,
  5.         .handle_irq = handle_bad_irq,
  6.         .depth = 1,
  7.         .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
  8.     }
  9. };

其中NR_IRQS表示支持中断的最大数,一般在arch/arm/mach-XXX/include/mach/irqs.h文件中定义。

       图2-1描述了以上三个数据结构之间的关系。所有irq_desc的chip成员都指向同一irq_chip实例。

图2-1 IRQ相关数据结构关系

3.中断初始化

ARM Linux中断初始化主要通过三个函数完成:early_trap_init early_irq_initinit_IRQ

整个Linux中断初始化流程如图3-1所示:


3-1 Linux中断初始化流程

early_trap_init函数定义在arch/arm/kernel/traps.c文件中,在setup_arch函数最后调用的,主要完成ARM异常向量表和异常处理程序的重定位。

点击(此处)折叠或打开

  1. void __init early_trap_init(void)
  2. {
  3.     unsigned long vectors = CONFIG_VECTORS_BASE;
  4.     extern char __stubs_start[], __stubs_end[];
  5.     extern char __vectors_start[], __vectors_end[];
  6.     extern char __kuser_helper_start[], __kuser_helper_end[];
  7.     int kuser_sz = __kuser_helper_end - __kuser_helper_start;

  8.     /*
  9.      * 拷贝异常向量表到vectors地址处,通常是0xffff0000。
  10.      * 拷贝异常处理程序到vectors+0x200地址处。
  11.      */
  12.     memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
  13.     memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
  14.     memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

  15.     /*
  16.      * 拷贝信号处理函数
  17.      */
  18.     memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
  19.      sizeof(sigreturn_codes));
  20. /*
  21.      * 拷贝信号处理函数
  22.      */
  23.     flush_icache_range(vectors, vectors + PAGE_SIZE);
  24.     modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
  25. }

这个函数把定义在 arch/arm/kernel/entry-armv.S 中的异常向量表和异常处理程序的 stub 进行重定位:异常向量表拷贝到 0xFFFF_0000,异常向量处理程序的 stub 拷贝到 0xFFFF_0200。然后调用 modify_domain()修改了异常向量表所占据的页面的访问权限,这使得用户态无法访问该页,只有核心态才可以访问。arm 处理器发生异常时总会跳转到 0xFFFF_0000(设为“高端向量配置”时)处的异常向量表,所以要进行这个重定位工作。

early_irq_init函数定义在kernel/irq/handle.c文件中:

点击(此处)折叠或打开

  1. struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
  2.     [0 ... NR_IRQS-1] = {
  3.         .status = IRQ_DISABLED,
  4.         .chip = &no_irq_chip,
  5.         .handle_irq = handle_bad_irq,
  6.         .depth = 1,
  7.         .lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock),
  8.     }
  9. };

  10. static unsigned int kstat_irqs_all[NR_IRQS][NR_CPUS];
  11. int __init early_irq_init(void)
  12. {
  13.     struct irq_desc *desc;
  14.     int count;
  15.     int i;

  16.     init_irq_default_affinity();

  17.     printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);

  18.     desc = irq_desc;
  19.     count = ARRAY_SIZE(irq_desc);

  20.     for (i = 0; i < count; i++) {
  21.         desc[i].irq = i;
  22.         alloc_desc_masks(&desc[i], 0, true);
  23.         init_desc_masks(&desc[i]);
  24.         desc[i].kstat_irqs = kstat_irqs_all[i];
  25.     }
  26.     return arch_early_irq_init();
  27. }

kernel/irq/handle.c文件中,根据内核的配置选项CONFIG_SPARSE_IRQ,可以选择两个不同版本的early_irq_init()中的一个进行编译。CONFIG_SPARSE_IRQ配置项,用于支持稀疏irq号,对于发行版的内核很有用,它允许定义一个高CONFIG_NR_CPUS值,但仍然不希望消耗太多内存的情况。通常情况下,我们并不配置该选项。

early_irq_init的主要工作是初始化用于管理中断的irq_desc[NR_IRQS]数组的每个元素(NR_IRQS表示中断的总数,在irqs.h文件中定义),它主要设置数组中每一个成员的中断号,使得数组中每一个元素的kstat_irqs字段(irq stats per cpu),指向定义的二维数组kstat_irqs_all中的对应的行。alloc_desc_masks(&desc[i], 0, true)init_desc_masks(&desc[i])函数在非SMP平台上为空函数。arch_early_irq_init()在主要用于x86平台和PPC平台,其他平台上为空函数。

init_IRQ函数定义在arch/arm/kernel/irq.c文件中:

点击(此处)折叠或打开

  1. void __init init_IRQ(void)
  2. {
  3.     int irq;

  4.     for (irq = 0; irq < NR_IRQS; irq++)
  5.         irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

  6.     init_arch_irq();
  7. }

init_IRQ函数首先遍历irq_desc中断描述符表,并初始化每一个中断的状态为IRQ_NOREQUESTIRQ_NOPROBE。然后调用板级相关函数init_arch_irq,该函数在setup_arch函数中被初始化为mdesc->init_irq,而mdesc->init_irq通常在arch/arm/mach-xxx目录下的板级文件中定义的。init_arch_irq函数完成对cpu中断控制器初始化,通常还会通过以下for循环来初始化irq_desc数组:

点击(此处)折叠或打开

  1. for (irq = IRQ_TIMER0; irq <= IRQ_TIMER4; irq++) {
  2.         set_irq_chip(irq, &arch_chip);//将中断控制器操作结构体irq_chip的实例注册到irq_desc[irq].chip上
  3.         set_irq_handler(irq, handle_level_irq);
  4.         set_irq_flags(irq, IRQF_VALID);
  5.     }

4.注册中断处理程序

本节将主要讲述如何注册中断处理程序。Linux中用于注册中断处理程序的函数是request_irq,该函数的实现为:

点击(此处)折叠或打开

  1. static inline int __must_check
  2. request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
  3.      const char *name, void *dev)
  4. {
  5.     return request_threaded_irq(irq, handler, NULL, flags, name, dev);
  6. }
  7. int request_threaded_irq(unsigned int irq, irq_handler_t handler,
  8.              irq_handler_t thread_fn, unsigned long irqflags,
  9.              const char *devname, void *dev_id)
  10. {
  11.     struct irqaction *action;
  12.     struct irq_desc *desc;
  13.     int retval;

  14.     /*
  15.      * handle_IRQ_event() always ignores IRQF_DISABLED except for
  16.      * the _first_ irqaction (sigh). That can cause oopsing, but
  17.      * the behavior is classified as "will not fix" so we need to
  18.      * start nudging drivers away from using that idiom.
  19.      */
  20.     if ((irqflags & (IRQF_SHARED|IRQF_DISABLED)) ==
  21.                     (IRQF_SHARED|IRQF_DISABLED)) {
  22.         pr_warning(
  23.          "IRQ %d/%s: IRQF_DISABLED is not guaranteed on shared IRQs\n",
  24.             irq, devname);
  25.     }

  26. #ifdef CONFIG_LOCKDEP
  27.     /*
  28.      * Lockdep wants atomic interrupt handlers:
  29.      */
  30.     irqflags |= IRQF_DISABLED;
  31. #endif
  32.     /*
  33.      * Sanity-check: shared interrupts must pass in a real dev-ID,
  34.      * otherwise we'll have trouble later trying to figure out
  35.      * which interrupt is which (messes up the interrupt freeing
  36.      * logic etc).
  37.      */
  38.     if ((irqflags & IRQF_SHARED) && !dev_id)
  39.         return -EINVAL;

  40.     desc = irq_to_desc(irq);
  41.     if (!desc)
  42.         return -EINVAL;

  43.     if (desc->status & IRQ_NOREQUEST)
  44.         return -EINVAL;

  45.     if (!handler) {
  46.         if (!thread_fn)
  47.             return -EINVAL;
  48.         handler = irq_default_primary_handler;
  49.     }

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

  53.     action->handler = handler;
  54.     action->thread_fn = thread_fn;
  55.     action->flags = irqflags;
  56.     action->name = devname;
  57.     action->dev_id = dev_id;

  58.     chip_bus_lock(irq, desc);
  59.     retval = __setup_irq(irq, desc, action);
  60.     chip_bus_sync_unlock(irq, desc);

  61.     if (retval)
  62.         kfree(action);
  63.     return retval;
  64. }
图3-1给出了request_irq代码流程图。

图3-1 request_irq代码流程图

内核首先生成一个新的irqaction实例,然后用函数参数填充其内容。

所有进一步的工作都委托给__setup_irq函数,它将执行以下步骤:

1)如果设置了IRQF_SAMPLE_RANDOM,则该中断将对内核熵池有所贡献,熵池用于随机数发生器/dev/random

2)由request_irq生成的irqaction实例被添加到所属IRQ编号对应的链表尾部,该链表表头为desc->action。在处理共享中断时,内核就通过这种方式来确保中断发生时调用处理程序的顺序与其注册顺序相同。

3register_irq_procproc文件系统中建立目录/proc/irq/NUM。而register_handler_proc生成/proc/irq/NUM/name



参考资料:

[1] Andrew N.Sloss, Dominic Symes, Chris Wright. ARM嵌入式系统开发——软件设计与优化. 沈建华, .

[2] Daniel P.Bovet, Marco Cesati. 深入理解Linux内核(第三版). 陈莉君, 张琼声, 张宏伟, .

[3] Pobert Love. Linux内核设计与实现. 陈莉君, 康华, 张波, .

[4] Wolfgang Mauerer. 深入Linux内核架构. 郭旭, .

[5] 陈学松. 深入Linux设备驱动程序内核机制.

[6] ARM Architecture Reference Manual

[7] The ARM-THUMB Procedure Call Standard
















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