linux中断和异常机制
1. 中断处理流程
当中断发生时,Linux系统会跳转到asm_do_IRQ()函数(所有中断程序的总入口函数),并且把中断号irq传进来。根据中断号,找到中断号对应的irq_desc结构(irq_desc结构为内核中中断的描述结构,内核中有一个irq_desc结构的数组irq_desc_ptrs[NR_IRQS]),然后调用irq_desc中的handle_irq函数,即中断入口函数。我们编写中断的驱动,即填充并注册irq_desc结构。
2. 中断处理数据结构:irq_desc
Linux内核将所有的中断统一编号,使用一个irq_desc[NR_IRQS]的结构体数组来描述这些中断:每个数组项对应着一个中断源(也可能是一组中断源),记录中断入口函数、中断标记,并提供了中断的底层硬件访问函数(中断清除、屏蔽、使能)。另外通过这个结构体数组项中的action,能够找到用户注册的中断处理函数。
struct irq_desc { unsigned int irq; irq_flow_handler_t handle_irq; struct irq_chip *chip; struct msi_desc *msi_desc; void *handler_data; void *chip_data; 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; spinlock_t lock;
const char *name; } ____cacheline_internodealigned_in_smp;
(1)handle_irq:中断的入口函数
(2)chip:包含这个中断的清除、屏蔽、使能等底层函数
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); void (*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); /* 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; };
(3)action:记录用户注册的中断处理函数、中断标志等内容
struct irqaction { irq_handler_t handler; unsigned long flags; cpumask_t mask; const char *name; void *dev_id; struct irqaction *next; int irq; struct proc_dir_entry *dir; };
3. 中断处理流程总结
(1) 发生中断后,CPU执行异常向量vector_irq的代码;
(2)在vector_irq里面,最终会调用中断处理C程序总入口函数asm_do_IRQ();
(3)asm_do_IRQ()根据中断号调用irq_des[NR_IRQS]数组中的对应数组项中的handle_irq();
(4)handle_irq()会使用chip的成员函数来设置硬件,例如清除中断,禁止中断,重新开启中断等;
(5)handle_irq逐个调用用户在action链表中注册的处理函数。
可见,中断体系结构的初始化,就是构造irq_desc[NR_IRQS]这个数据结构;用户注册中断就是构造action链表;用户卸载中断就是从action链表中去除对应的项。
4. Linux操作系统中断初始化
(1)init_IRQ()函数用来初始化中断体系结构,代码位于arch/arm/kernel/irq.c
void __init init_IRQ(void) { int irq; for (irq = 0; irq < NR_IRQS; irq++) irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE; #ifdef CONFIG_SMP bad_irq_desc.affinity = CPU_MASK_ALL; bad_irq_desc.cpu = smp_processor_id(); #endif init_arch_irq(); }
(2)init_arch_irq()函数,就是用来初始化irq_desc[NR_IRQS]的,与硬件平台紧密相关。init_arch_irq其实是一个函数指针,我们移植Linux内核时,以S3C2440平台为例,把init_arch_irq指向函数s3c24xx_init_irq()。
(3)s3c24xx_init_irq()函数在arch/arm/plat-s3c24xx/irq.c中定义,它为所有的中断设置了芯片相关的数据结构irq_desc[irq].chip,设置了处理函数入口irq_desc[irq].handle_irq。
(4)以外部中断EINT0为例:
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) { irqdbf("registering irq %d (ext int)\n", irqno); set_irq_chip(irqno, &s3c_irq_eint0t4); set_irq_handler(irqno, handle_edge_irq); set_irq_flags(irqno, IRQF_VALID); }
① set_irq_chip()的作用就是"irq_desc[irqno].chip = &s3c_irq_eint0t4",s3c_irq_eint0t4为系统提供了一套操作EINT0~EINT4的中断底层函数集,内容如下
static struct irq_chip s3c_irq_eint0t4 = { .name = "s3c-ext0", .ack = s3c_irq_ack, .mask = s3c_irq_mask, .unmask = s3c_irq_unmask, .set_wake = s3c_irq_wake, .set_type = s3c_irqext_type, };
② set_irq_handler()函数的作用就是“irq_desc[irqno].handle_irq = handle_edge_irq”。发生中断后,asm_do_IRQ()函数会调用中断入口函数handle_edge_irq(),而handle_edge_irq()函数会调用用户注册的处理函数(即irq_desc[irqno].action)。
5. 用户注册中断时带来的中断初始化
(1)用户(驱动程序)通过request_irq()函数向内核注册中断处理函数,request_irq()函数根据中断号找到数组irq_desc[irqno]对应的数组项,然后在它的action链表中添加一个action表项。该函数定义于:kernel/irq/manage.c,内容如下
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) { struct irqaction *action; struct irq_desc *desc; int retval; /* * handle_IRQ_event() always ignores IRQF_DISABLED except for * the _first_ irqaction (sigh). That can cause oopsing, but * the behavior is classified as "will not fix" so we need to * start nudging drivers away from using that idiom. */ if ((irqflags & (IRQF_SHARED|IRQF_DISABLED)) == (IRQF_SHARED|IRQF_DISABLED)) pr_warning("IRQ %d/%s: IRQF_DISABLED is not " "guaranteed on shared IRQs\n", irq, devname); #ifdef CONFIG_LOCKDEP /* * Lockdep wants atomic interrupt handlers: */ irqflags |= IRQF_DISABLED; #endif /* * 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 (desc->status & IRQ_NOREQUEST) return -EINVAL; if (!handler) return -EINVAL; action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC); if (!action) return -ENOMEM; action->handler = handler; action->flags = irqflags; cpus_clear(action->mask); action->name = devname; action->next = NULL; action->dev_id = dev_id; retval = __setup_irq(irq, desc, action); if (retval) kfree(action); #ifdef CONFIG_DEBUG_SHIRQ if (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; }
(2) request_irq()函数首先使用4个参数构造一个irqaction结构,然后调用__setup_irq函数将它链入链表中,简要代码如下:
static int __setup_irq(unsigned int irq, struct irqaction *new) { /* 判断是否没有注册过,如果已经注册了就判断是否是可共享的中断 */ p = &desc->action; old = *p; if (old) { if (!((old->flags & new->flags) & IRQF_SHARED) || ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) { old_name = old->name; goto mismatch; } /* add new interrupt at end of irq queue */ do { p = &old->next; old = *p; } while (old); shared = 1; } /* 链入新表项 */ *p = new; /* 如果在链入之前不是空链,那么之前的共享中断已经设置了中断触发方式,没有必要重复设置 */ /* 如果链入之前是空链,那么就需要设置中断触发方式 */ if (!shared) { irq_chip_set_defaults(desc->chip); /* Setup the type (level, edge polarity) if configured: */ if (new->flags & IRQF_TRIGGER_MASK) { if (desc->chip && desc->chip->set_type) desc->chip->set_type(irq, new->flags & IRQF_TRIGGER_MASK); else printk(KERN_WARNING "No IRQF_TRIGGER set_type " "function for IRQ %d (%s)\n", irq, desc->chip ? desc->chip->name : "unknown"); } else compat_irq_chip_set_default_handler(desc); desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING | IRQ_INPROGRESS); if (!(desc->status & IRQ_NOAUTOEN)) { desc->depth = 0; desc->status &= ~IRQ_DISABLED; /* 启动中断 */ if (desc->chip->startup) desc->chip->startup(irq); else desc->chip->enable(irq); } else /* Undo nested disables: */ desc->depth = 1; } /* Reset broken irq detection when installing new handler */ desc->irq_count = 0; desc->irqs_unhandled = 0; new->irq = irq; register_irq_proc(irq); new->dir = NULL; register_handler_proc(irq, new); }
(3) __setup_irq()函数主要完成功能如下
① 将新建的irqaciton结构链入irq_desc[irq]结构体的action链表中
* 如果action链表为空,则直接链入
* 如果非空,则要判断新建的irqaciton结构和链表中的irqaciton结构所表示的中断类型是否一致:即是都声明为“可共享的”,是否都是用相同的触发方式,如果一致,则将新建的irqaciton结构链入
② 设置中断的触发方式;
③ 启动中断
6. 卸载中断
卸载中断使用函数free_irq()函数,该函数定义在kernel/irq/manage.c中,需要用到的两个参数irq、dev_id。通过参数irq可以定位到action链表,再使用dev_id在链表中找到要卸载的表项(共享中断的情况)。如果它是唯一表项,那么删除中断,还需要调用irq_desc[irq].chip->shutdown()或者irq_desc[irq].chip->disable()来关闭中断
void free_irq(unsigned int irq, void *dev_id) { struct irq_desc *desc = irq_to_desc(irq); struct irqaction **p; unsigned long flags; WARN_ON(in_interrupt()); if (!desc) return; spin_lock_irqsave(&desc->lock, flags); p = &desc->action; for (;;) { struct irqaction *action = *p; if (action) { struct irqaction **pp = p; p = &action->next; if (action->dev_id != dev_id) continue; /* Found it - now remove it from the list of entries */ *pp = action->next; /* Currently used only by UML, might disappear one day.*/ #ifdef CONFIG_IRQ_RELEASE_METHOD if (desc->chip->release) desc->chip->release(irq, dev_id); #endif if (!desc->action) { desc->status |= IRQ_DISABLED; if (desc->chip->shutdown) desc->chip->shutdown(irq); else desc->chip->disable(irq); } spin_unlock_irqrestore(&desc->lock, flags); unregister_handler_proc(irq, action); /* Make sure it's not being used on another CPU */ synchronize_irq(irq); #ifdef CONFIG_DEBUG_SHIRQ /* * It's a shared IRQ -- the driver ought to be * prepared for it to happen even now it's * being freed, so let's make sure.... We do * this after actually deregistering it, to * make sure that a 'real' IRQ doesn't run in * parallel with our fake */ if (action->flags & IRQF_SHARED) { local_irq_save(flags); action->handler(irq, dev_id); local_irq_restore(flags); } #endif kfree(action); return; } printk(KERN_ERR "Trying to free already-free IRQ %d\n", irq); #ifdef CONFIG_DEBUG_SHIRQ dump_stack(); #endif spin_unlock_irqrestore(&desc->lock, flags); return; } }
7. Linux中断处理流程分析
① 中断总入口函数:asm_do_IRQ() (定义在:arch/arm/kernel/irq.c)
asmlinkage void __exception 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 (irq >= NR_IRQS) handle_bad_irq(irq, &bad_irq_desc); else generic_handle_irq(irq); /* AT91 specific workaround */ irq_finish(irq); irq_exit(); set_irq_regs(old_regs); }
② generic_handle_irq()会调用相应中断号描述结构的handle_irq,等价于irq_desc[irq].handle_irq(irq, desc)
③ 普通中断流程(以EINT0为例)
(1)irq_desc[IRQ_EINT0].handle_irq函数指针指向handle_edge_irq()(定义在:kernel/irq/chip.c),用来处理边沿触发的中断,内容如下
void fastcall handle_edge_irq(unsigned int irq, struct irq_desc *desc) { kstat_cpu(cpu).irqs[irq]++; /* Start handling the irq */ desc->chip->ack(irq); /* Mark the IRQ currently in progress.*/ desc->status |= IRQ_INPROGRESS; action_ret = handle_IRQ_event(irq, action); }
(2)通过函数调用desc->chip->ack(irq)来响应中断,实际上就是清除中断以使得可以接受下一个中断,有了之前数据结构初始化的前提了解,可以知道实际上执行的就是s3c_irq_eint0t4.ack函数
(3)handle_IRQ_event函数逐个执行action链表中用户注册的中断处理函数,它在kernel/irq/handle.c中定义,关键代码如下:
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action) { do { ret = action->handler(irq, action->dev_id); if (ret == IRQ_HANDLED) status |= action->flags; retval |= ret; action = action->next; } while (action); }
(4)用户通过函数request_irq()函数注册中断处理函数时候,传入参数irq和dev_id,在这里这两个参数被用户注册的中断处理函数action->handler()所使用。可见用户可以在注册中断处理函数的时候,指定参数dev_id,然后将来再由注册的中断处理函数使用这个参数。
④ 特殊处理流程(以外部中断EINT5为例)
(1)在S3C2440处理器架构中,EINT5中断属于EINT4t7中断集合,是一个子中断。当EINT5中断线发生中断事件,那么将先跳转到EINT4t7中断号对应的中断入口处理函数,也即是irq_desc[EINT4t7].handle_irq(irq,desc),进行具体子中断确定,然后再跳转到真正发生中断的中断入口处理函数执行。
(2)EINT5中断注册函数调用:
request_irq(IRQ_EINT5, eint5_irq, IRQT_BOTHEDGE, "S2", NULL);
其实我们在没有注册EINT5中断源的时候,系统已经注册了EINT4t7的中断入口处理函数。中断集合EINT4t7的中断入口处理函数,是在arch/arm/plat-s3c24xx/irq.c中的函数s3c24xx_init_irq()来初始化的,内容如下:
set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
(3)当发生EINT5中断事件,汇编阶段根据INTOFFSET确定中断号为IRQ_EINT4t7,asm_do_IRQ函数通过传入的这个参数,将跳转到irq_desc[EINT4t7].handle_irq(irq,desc)函数执行,也就是函数s3c_irq_demux_extint4t7(irq, desc),该函数的主要内容如下:
static void s3c_irq_demux_extint4t7(unsigned int irq, struct irq_desc *desc) { unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND); unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK); eintpnd &= ~eintmsk; eintpnd &= 0xff; /* only lower irqs */ /* we may as well handle all the pending IRQs here */ while (eintpnd) { irq = __ffs(eintpnd); eintpnd &= ~(1<<irq); irq += (IRQ_EINT4 - 4); generic_handle_irq(irq); } }
(4)函数s3c_irq_demux_extint4t7()根据寄存器S3C24XX_EINTPEND、S3C24XX_EINTMASK重新计算中断号,这个时候将计算出真正的中断号IRQ_EINT5,然后通过generic_handle_irq(irq)来调用irq_desc[EINT5].handle_irq(irq,desc)。此后的过程与EINT0发生中断后的执行过程类似。
1. 对异常概念的理解
异常就是可以打断CPU正常运行的事件,比如,外部中断、未定义的指令、软中断等。当这些异常发生时,就打断CPU的正常运行,跳到相应的异常处理程序去处理这些异常要求的一些操作。
2. Linux内核中断处理流程
基于Linux-2.6.29.4
1、Linux内核中异常向量表的拷贝
内核启动时将异常向量表从物理地址0x00000000拷贝到0xffff0000的虚拟地址中去;
其中异常向量在arch/arm/kernel/entry-armv.S中定义
中断向量表
.globl __vectors_start __vectors_start: swi SYS_ERROR0 b vector_und+ stubs_offset ldr pc,.LCvswi + stubs_offset b vector_pabt+ stubs_offset b vector_dabt+ stubs_offset b vector_addrexcptn+ stubs_offset b vector_irq+ stubs_offset b vector_fiq+ stubs_offset .globl __vectors_end __vectors_end:注:内核启动时调用start_kernel()函数,在start_kernel()中又调用了大量的函数。
asmlinkagevoid __init start_kernel(void) { ..................................... setup_arch(&command_line); mm_init_owner(&init_mm,&init_task); setup_command_line(command_line); ..................... sched_init(); ..................... init_IRQ(); rest_init(); } 函数setup_arch()在arch/arm/kernel/setup.c中 void__init setup_arch(char **cmdline_p) { ......................... memcpy(boot_command_line,from, COMMAND_LINE_SIZE); boot_command_line[COMMAND_LINE_SIZE-1]= '\0'; parse_cmdline(cmdline_p,from); paging_init(mdesc); request_standard_resources(&meminfo,mdesc); ............................. ............................... early_trap_init(); } void__init early_trap_init(void) { ........................................ 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); ............................... }
2、Linux内核中中断处理流程
首先,内核启动时完成中断初始化框架的流程
在start_kernel()中调用init_IRQ(),该函数在arch/arm/kernel/irq.c中被定义,被用来初始化中断的处理框架,设置各种中断的默认处理函数。
当发生中断时,中断总入口函数asm_do_IRQ()就可以调用这些函数作进一步处理。
void__init init_IRQ(void) { intirq; for(irq = 0; irq < NR_IRQS; irq++) irq_desc[irq].status|=IRQ_NOREQUEST | IRQ_NOPROBE; #ifdefCONFIG_SMP bad_irq_desc.affinity=CPU_MASK_ALL; bad_irq_desc.cpu=smp_processor_id(); #endif init_arch_irq(); } 其中init_arch_irq()被定义为 void(*init_arch_irq)(void) __initdata=NULL; 它的作用如下, void__init setup_arch(char **cmdline_p) { structmachine_desc *mdesc; 。。。。。。。。。。 setup_processor(); mdesc=setup_machine(machine_arch_type); machine_name=mdesc->name; memcpy(boot_command_line,from, COMMAND_LINE_SIZE); boot_command_line[COMMAND_LINE_SIZE-1]= '\0'; parse_cmdline(cmdline_p,from); paging_init(mdesc); request_standard_resources(&meminfo,mdesc); cpu_init(); init_arch_irq= mdesc->init_irq; system_timer= mdesc->timer; init_machine= mdesc->init_machine; early_trap_init(); } structmachine_desc { unsignedint nr; unsignedint phys_io; unsignedint io_pg_offst; constchar *name; unsignedlong boot_params; unsignedint video_start; unsignedint video_end; unsignedint reserve_lp0 :1; unsignedint reserve_lp1 :1; unsignedint reserve_lp2 :1; unsignedint soft_reboot :1; void (*fixup)(structmachine_desc *, struct tag *, char **, struct meminfo *); void (*map_io)(void); void (*init_irq)(void); structsys_timer *timer; void (*init_machine)(void); }; static structmachine_desc * __init setup_machine(unsignedint nr) { structmachine_desc *list; list= lookup_machine_type(nr); if(!list) { printk("Machineconfiguration botched (nr %d), unable " "to continue.\n", nr); while(1); } printk("Machine:%s\n", list->name); returnlist; } ENTRY(lookup_machine_type) stmfd sp!,{r4 - r6, lr} mov r1,r0 bl __lookup_machine_type mov r0,r5 ldmfd sp!,{r4 - r6, pc} ENDPROC(lookup_machine_type) __lookup_machine_type: adr r3,3b ldmia r3,{r4, r5, r6} sub r3,r3, r4 @ get offset between virt&phys add r5,r5, r3 @ convert virt addresses to add r6,r6, r3 @ physical address space 1: ldr r3,[r5, #MACHINFO_TYPE] @ get machine type teq r3,r1 @ matches loader number? beq 2f @found add r5,r5, #SIZEOF_MACHINE_DESC @ next machine_desc cmp r5,r6 blo 1b mov r5,#0 @ unknown machine 2: mov pc,lr ENDPROC(__lookup_machine_type) 接下来是内核完成启动后响应中断的流程 当CPU被中断时,强制跳转到异常中断向量表中的 b vector_fiq+ stubs_offset 如vector_irq这些是宏,定义在 vector_stub irq,IRQ_MODE, 4 vector_stub irq,IRQ_MODE, 4 这两句对应 .macro vector_stub,irq,IRQ_MODE,correction=4 .align 5 vector_irq: .if4 sub lr,lr, #4 计算返回地址 .endif @ @Save r0, lr_ (parent PC) and spsr_ @(parent CPSR) @保存被中断时CPU的现场 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) msr spsr_cxsf,r0 @ @the branch table must immediately follow this code @ and lr,lr, #0x0f mov r0,sp ldr lr,[pc, lr, lsl #2] movs pc,lr @ branch to handler in SVC mode继续执行跳转 ENDPROC(vector_irq) .endm 执行完上述语句后,跳转到 vector_stub irq,IRQ_MODE, 4 .long __irq_usr @ 0 (USR_26 / USR_32) 如果是用户态发生中断时,跳转到__irq_usr地址去执行 .long __irq_invalid @ 1 (FIQ_26 / FIQ_32) .long __irq_invalid @ 2 (IRQ_26 / IRQ_32) .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 当用户态发生中断是跳到__irq_usr __irq_usr: usr_entry kuser_cmpxchg_check #ifdefCONFIG_TRACE_IRQFLAGS bl trace_hardirqs_off #endif get_thread_infotsk #ifdefCONFIG_PREEMPT ldr r8,[tsk, #TI_PREEMPT] @ get preempt count add r7,r8, #1 @ increment it str r7,[tsk, #TI_PREEMPT] #endif .macro irq_handler get_irqnr_preambler5, lr 1: get_irqnr_and_baser0, r6, r5, lr movne r1,sp @ @routine called with r0 = irq number, r1 = struct pt_regs * @ adrne lr,1b bne asm_do_IRQ ***位于irq.c asmlinkagevoid __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs) { structpt_regs *old_regs = set_irq_regs(regs); irq_enter(); if(irq >= NR_IRQS) handle_bad_irq(irq,&bad_irq_desc); else generic_handle_irq(irq); irq_finish(irq); irq_exit(); set_irq_regs(old_regs); } irq_desc定义在include/linux/irq.h中 structirq_desc { unsignedint irq; #ifdefCONFIG_SPARSE_IRQ structtimer_rand_state *timer_rand_state; unsignedint *kstat_irqs; #ifdef CONFIG_INTR_REMAP structirq_2_iommu *irq_2_iommu; #endif #endif irq_flow_handler_t handle_irq; structirq_chip *chip; structmsi_desc *msi_desc; void *handler_data; void *chip_data; structirqaction *action; unsignedint status; unsignedint depth; unsignedint wake_depth; unsignedint irq_count; unsignedlong last_unhandled; unsignedint irqs_unhandled; spinlock_t lock; #ifdefCONFIG_SMP cpumask_t affinity; unsignedint cpu; #endif #ifdefCONFIG_GENERIC_PENDING_IRQ cpumask_t pending_mask; #endif #ifdefCONFIG_PROC_FS structproc_dir_entry *dir; #endif constchar *name; } 定义在include/linux/irq.h staticinline void generic_handle_irq(unsigned int irq) { generic_handle_irq_desc(irq,irq_to_desc(irq)); } staticinline void generic_handle_irq_desc(unsigned int irq, struct irq_desc*desc) { #ifdefCONFIG_GENERIC_HARDIRQS_NO__DO_IRQ desc->handle_irq(irq,desc); #else if(likely(desc->handle_irq)) desc->handle_irq(irq,desc); else __do_IRQ(irq); #endif }
最终处理的函数为handle_irq,为用户自定义。。。
版权声明:本文为博主编辑整理,未经博主允许不得转载。 http://blog.csdn.net/Leichelle/article/details/6889004
一、中断概述
中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,
转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。
1.1中断类型
同步中断由CPU本身产生,又称为内部中断。这里同步是指中断请求信号与代码指令之间的同步执行,在一条指令执行完毕后,CPU才能进行中断,不能在执行期间。所以也称为异常(exception)。
异步中断是由外部硬件设备产生,又称为外部中断,与同步中断相反,异步中断可在任何时间产生,包括指令执行期间,所以也被称为中断(interrupt)。
异常又可分为可屏蔽中断(Maskable interrupt)和非屏蔽中断(Nomaskable interrupt)。而中断可分为故障(fault)、陷阱(trap)、终止(abort)三类。
从广义上讲,中断又可分为四类:中断、故障、陷阱、终止。这些类别之间的异同点请参考 表 1。
1.2区分中断号与中断向
I/O设备把中断信号发送给中断控制器(8259A)时与之相关联的是一个中断号,当中断控制器把中断信号发送给CPU时与之关联的是一个中断向量。
换个角度分析就是中断号是从中断控制器层面划分,中断向量是从CPU层面划分,所以中断号与中断向量之间存在一对一映射关系。
在Intel X86中最大支持256种中断,从0到255开始编号,这个8位的编号就是中断向量。其中将0到31保留用于异常处理和不可屏蔽中断。
二、中断数据处理结构
Linux内核中处理中断主要有三个数据结构,irq_desc,irq_chip和irqaction。
在\include\linux\ irq.h中定义了
1)irq_desc用于描述IRQ线的属性与状态,被称为中断描述符。
/** * 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; struct msi_desc *msi_desc; void *handler_data; void *chip_data; 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; spinlock_t lock; #ifdef CONFIG_SMP cpumask_var_t affinity; 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; }
2)irq_chip用于描述不同类型的中断控制器。
/** * 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 (defaults to chip->mask if NULL) * @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; }
在\include\linux\ interrupt.h中定义了 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; };
三、Linux中断机制
Linux中断机制由三部分组成:
中断子系统初始化:内核自身初始化过程中对中断处理机制初始化,例如中断的数据结构以及中断请求等。
中断或异常处理:中断整体处理过程。
中断API:为设备驱动提供API,例如注册,释放和激活等。
3.1中断子系统初始化
3.1.1中断描述符表(IDT)初始化
中断描述符表初始化需要经过两个过程:
第一个过程在内核引导过程。由两个步骤组成,首先给分配IDT分配2KB空间(256中断向量,每个向量由8bit组成)并初始化;然后把IDT起始地址存储到IDTR寄存器中。
第二个过程内核在初始化自身的start_kernal函数中使用trap_init初始化系统保留中断向量,使用init_IRQ完成其余中断向量初始化。
3.1.2中断请求队列初始化
init_IRQ调用pre_intr_init_hook,进而最终调用init_ISA_irqs初始化中断控制器以及每个IRQ线的中断请求队列。
3.2中断或异常处理
中断处理过程:设备产生中断,并通过中断线将中断信号送往中断控制器,如果中断没有被屏蔽则会到达CPU的INTR引脚,CPU立即停止当前工作,根据获得中断向量号从IDT中找出门描述符,并执行相关中断程序。
异常处理过程:异常是由CPU内部发生所以不会通过中断控制器,CPU直接根据中断向量号从IDT中找出门描述符,并执行相关中断程序。
中断控制器处理主要有5个步骤:
1.中断请求
2.中断相应
3.优先级比较
4.提交中断向量
5.中断结束。
这里不再赘述5个步骤的具体流程。
CPU处理流程主要有6个步骤:
1.确定中断或异常的中断向量
2.通过IDTR寄存器找到IDT
3.特权检查
4.特权级发生变化,进行堆栈切换
5.如果是异常将异常代码压入堆栈,如果是中断则关闭可屏蔽中断
6.进入中断或异常服务程序执行。这里不再赘述6个步骤的具体流程。
3.3中断API
内核提供的API主要用于驱动的开发。
注册IRQ:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void
*dev);
释放IRQ:
void free_irq(unsigned int, void *);
注:IRQ线资源非常宝贵,我们在使用时必须先注册,不使用时必须释放IRQ资源。
激活当前CPU中断:
local_irq_enable();
禁止当前CPU中断:
local_irq_disable();
激活指定中断线:
void enable_irq(unsigned int irq);
禁止指定中断线:
void disable_irq(unsigned int irq);
禁止指定中断线:
void disable_irq_nosync(unsigned int irq);
注:此函数调用irq_chip中disable禁止指定中断线,所以不会保证中断线上执行的中断服务程序已经退出。
3.4中断机制划分
由于中断会打断内核中进程的正常调度运行,所以要求中断服务程序尽可能的短小精悍;
但是在实际系统中,当中断到来时,要完成工作往往进行大量的耗时处理。因此期望让中断处理程序运行得快,并想让它完成的工作量多,这两个目标相互制约,诞生——顶/底半部机制。
中断处理程序是顶半部——接受中断,它就立即开始执行,但只有做严格时限的工作。
能够被允许稍后完成的工作会推迟到底半部去,此后,在合适的时机,底半部会被开终端执行。
顶半部简单快速,执行时禁止一些或者全部中断。
底半部稍后执行,而且执行期间可以响应所有的中断。这种设计可以使系统处于中断屏蔽状态的时间尽可能的短,以此来提高系统的响应能力。
顶半部只有中断处理程序机制,而底半部的实现有软中断,tasklet和工作队列实现。
3.4.1顶/底半部划分原则:
1) 如果一个任务对时间非常敏感,将其放在顶半部中执行;
2) 如果一个任务和硬件有关,将其放在顶半部中执行;
3) 如果一个任务要保证不被其他中断打断,将其放在顶半部中执行;
4) 其他所有任务,考虑放置在底半部执行。
3.4.2底半部实现机制
软中断:
软中断作为下半部机制的代表,是随着SMP(share memory
processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。
软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。
它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,
软中断执行中断处理程序留给它去完成的剩余任务,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:
a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。
b)可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。
内核中定义了几种软中断的用途:
enum { HI_SOFTIRQ=0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ NR_SOFTIRQS };
Tasklet:
tasklet是通过软中断实现的,所以它本身也是软中断。
软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。
为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。
Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。Tasklet作为一种新机制,显然可以承担更多的优点。
正好这时候SMP越来越火了,因此又在tasklet中加入了SMP机制,保证同种中断只能在一个cpu上执行。
在软中断时代,显然没有这种考虑。
因此同一种软中断可以在两个cpu上同时执行,很可能造成冲突。
总结下tasklet的优点:
(1)无类型数量限制;
(2)效率高,无需循环查表;
(3)支持SMP机制;
它的特性如下:
1)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
2)多个不同类型的tasklet可以并行在多个CPU上。
3)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。
工作队列:
上面我们介绍的可延迟函数运行在中断上下文中,于是导致了一些问题,说明它们不可挂起,也就是说软中断不能睡眠、不能阻塞,原因是由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列,它能够在不同的进程间切换,以完成不同的工作。
工作队列能运行在进程上下文,它将工作给一个内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。我们用结构体workqueue_struct表示工作者线程,工作者线程是用内核线程实现的。而工作者线程是如何执行被推后的工作——有这样一个链表,它由结构体work_struct组成,而这个work_struct则描述了一个工作,一旦这个工作被执行完,相应的work_struct对象就从链表上移去,当链表上不再有对象时,工作者线程就会继续休眠。因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。
如何选择下半部机制:
软中断和tasklet运行在中断上下文,工作队列运行在进程上下文。如果需要休眠则选择工作队列,否则选择tasklet;如果对性能要求较高则选择软中断。
从易用性考虑,首选工作队列,然后tasklet,最后是软中断,因为软中断需要静态创建。
从代码安全考虑,如果对底半部代码保护不够安全,则选择tasklet,因为相对软中断,tasklet对锁要求低,上面也简述它们工作方式以及运用场景。
四、多处理器系统中断相关概念
4.1处理器间中断
在多处理器系统中,操作系统需要在多个处理器中协调操作,所以需要处理器中断(Inter-Processor-Interrupt,IPI)实现,IPI是一种特殊硬件中断,由CPU送出,其他CPU接收,处理CPU之间通信和同步操作。以下是x86中SMP定义的IPI,中断向量号用十六进制表示:
SPURIOUS_APIC_VECTOR 0xff ERROR_APIC_VECTOR 0xfe RESCHEDULE_VECTOR 0xfd CALL_FUNCTION_VECTOR 0xfc CALL_FUNCTION_SINGLE_VECTOR 0xfb THERMAL_APIC_VECTOR 0xfa THRESHOLD_APIC_VECTOR 0xf9 REBOOT_VECTOR 0xf8 INVALIDATE_TLB_VECTOR_END 0xf7 INVALIDATE_TLB_VECTOR_START 0xf0
4.2中断亲和力
将一个或多个中断服务程序绑定到特定的CPU上处理,这就是中断亲和力(SMP IRQ affinity)。我们可以使用中断亲和力来均衡各个CPU的负载,提高系统处理能力。
以上便是本人对Linux中断的理解,若有纰漏欢迎指正!
————————————————
版权声明:本文为CSDN博主「Bystander_J」的编辑整理,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42092278/article/details/81989449
1.中断概念
中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,
转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。Linux中通常分为外部中断(又叫硬件中断)和内部中断(又叫异常)。
在实地址模式中,CPU把内存中从0开始的1KB空间作为一个中断向量表。表中的每一项占4个字节。
但是在保护模式中,有这4个字节的表项构成的中断向量表不满足实际需求,于是根据反映模式切换的信息和偏移量的足够使得中断向量表的表项由8个字节组成,
而中断向量表也叫做了中断描述符表(IDT)。在CPU中增加了一个用来描述中断描述符表寄存器(IDTR),用来保存中断描述符表的起始地址。
2. Linux中断处理
2.1 系统中断号
由上述中断定义可知,系统中断向量表中共可保存256个中断向量入口,即IDT中包含的256个中断描述符(对应256个中断向量)。
而0-31号中断向量被intel公司保留用来处理异常事件,不能另作它用。对这
0-31号中断向量,操作系统只需提供异常的处理程序,当产生一个异常时,处理机就会自动把控制转移到相应的处理程序的入口,运行相应的处理程序;
而事实 上,对于这32个处理异常的中断向量,2.6版本的
Linux只提供了0-17号中断向量的处理程序,其对应处理程序参见下表、中断向量和异常事件对应表;也就是说,17-31号中断向量是空着未用的。
中断向量号 异常事件 Linux的处理程序
0 除法错误 Divide_error
1 调试异常 Debug
2 NMI中断 Nmi
3 单字节,int 3 Int3
4 溢出 Overflow
5 边界监测中断 Bounds
6 无效操作码 Invalid_op
7 设备不可用 Device_not_available
8 双重故障 Double_fault
9 协处理器段溢出 Coprocessor_segment_overrun
10 无效TSS Incalid_tss
11 缺段中断 Segment_not_present
12 堆栈异常 Stack_segment
13 一般保护异常 General_protection
14 页异常 Page_fault
15 (intel保留) Spurious_interrupt_bug
16 协处理器出错 Coprocessor_error
17 对齐检查中断 Alignment_check
0-31号中断向量已被保留,那么剩下32-255共224个中断向量可用。 这224个中断向量又是怎么分配的呢?2.6版本的Linux中,除了0x80
(SYSCALL_VECTOR)用作系统调用总入口之外,其他都用在外部硬件中断源上,其中包括可编程中断控制器8259A的15个irq;事实上,当
没有定义CONFIG_X86_IO_APIC时,其他223(除0x80外)个中断向量,只利用了从32号开始的15个,其它208个空着未用。
2.2 中断请求
2.2.1 中断请求概述
外部设备当需要操作系统做相关的事情的时候,会产生相应的中断。
设备通过相应的中断线向中断控制器发送高电平以产生中断信号,而操作系统则会从中断控制器的状态位取得那根中断线上产生的中断。
而且只有在设备在对某一条中断线拥有控制权,才可以向这条中断线上发送信号。也由于现在的外设越来越多,中断线又是很宝贵的资源不可能被一一对应。
因此在使用中断线前,就得对相应的中断线进行申请。
无论采用共享中断方式还是独占一个中断,申请过程都是先讲所有的中断线进行扫描,得出哪些没有别占用,从其中选择一个作为该设备的IRQ。
其次,通过中断申请函数申请相应的IRQ。最后,根据申请结果查看中断是否能够被执行。
2.2.2 中断相关结构
中断中核心处理数据结构为irq_desc,它完整的描述了一条中断线,Linux 2.6。22.6中源码如下。
irq_desc定义在include/linux/irq.h中
irq_desc
其相关联的几个结构体如下:
定义在include/linux/ interrupt.h中的中断行动结构体:struct irqaction
struct irqaction { irq_handler_t handler; unsigned long flags; cpumask_t mask; const char *name; void *dev_id; struct irqaction *next; int irq; struct proc_dir_entry *dir; };
定义在include/linux 中的:irq_chip 芯片相关的处理函数集合
/** * 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 (defaults to chip->mask if NULL) * @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 * * @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); void (*set_affinity)(unsigned int irq, cpumask_t 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); /* 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; };
2.2.3 中断请求实现
上下半部机制
我们期望让中断处理程序运行得快,并想让它完成的工作量多,这两个目标相互制约,如何解决——上下半部机制。
我们把中断处理切为两半。中断处理程序是上半部——接受中断,他就立即开始执行,但只有做严格时限的工作。
能够被允许稍后完成的工作会推迟到下半部去,此后,在合适的时机,下半部会被开终端执行。上半部简单快速,执行时禁止一些或者全部中断。
下半部稍后执行,而且执行期间可以响应所有的中断。这种设计可以使系统处于中断屏蔽状态的时间尽可能的短,以此来提高系统的响应能力。
上半部只有中断处理程序机制,而下半部的实现有软中断实现,tasklet实现和工作队列实现。
我们用网卡来解释一下这两半。当网卡接受到数据包时,通知内核,触发中断,所谓的上半部就是,及时读取数据包到内存,防止因为延迟导致丢失,这是很急迫的工作。
读到内存后,对这些数据的处理不再紧迫,此时内核可以去执行中断前运行的程序,而对网络数据包的处理则交给下半部处理。
上下半部划分原则
1) 如果一个任务对时间非常敏感,将其放在中断处理程序中执行;
2) 如果一个任务和硬件有关,将其放在中断处理程序中执行;
3) 如果一个任务要保证不被其他中断打断,将其放在中断处理程序中执行;
4) 其他所有任务,考虑放置在下半部执行。
下半部实现机制之软中断
软中断作为下半部机制的代表,是随着SMP(share memory
processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。
软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。
它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,软中断执行中断处理程序留给它去完成的剩余任务,
而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:
a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。
b)可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。
下半部实现机制之tasklet
tasklet是通过软中断实现的,所以它本身也是软中断。
软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。
为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。
Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。Tasklet作为一种新机制,显然可以承担更多的优点。
正好这时候SMP越来越火了,因此又在tasklet中加入了SMP机制,保证同种中断只能在一个cpu上执行。在软中断时代,显然没有这种考虑。
因此同一种软中断可以在两个cpu上同时执行,很可能造成冲突。
总结下tasklet的优点:
(1)无类型数量限制;
(2)效率高,无需循环查表;
(3)支持SMP机制;
它的特性如下:
1)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
2)多个不同类型的tasklet可以并行在多个CPU上。
3)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。
下半部实现机制之工作队列(work queue)
上面我们介绍的可延迟函数运行在中断上下文中(软中断的一个检查点就是do_IRQ退出的时候),于是导致了一些问题:软中断不能睡眠、不能阻塞。
由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死
。但可阻塞函数不能用在中断上下文中实现,必须要运行在进程上下文中,例如访问磁盘数据块的函数。因此,可阻塞函数不能用软中断来实现。
但是它们往往又具有可延迟的特性。
上面我们介绍的可延迟函数运行在中断上下文中,于是导致了一些问题,说明它们不可挂起,也就是说软中断不能睡眠、不能阻塞,
原因是由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。
因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。
为了完成这些不可能完成的任务,于是出现了工作队列,它能够在不同的进程间切换,以完成不同的工作。
如果推后执行的任务需要睡眠,那么就选择工作队列,如果不需要睡眠,那么就选择软中断或tasklet。
工作队列能运行在进程上下文,它将工作托付给一个内核线程。工作队列说白了就是一组内核线程,作为中断守护线程来使用。
多个中断可以放在一个线程中,也可以每个中断分配一个线程。我们用结构体workqueue_struct表示工作者线程,工作者线程是用内核线程实现的。
而工作者线程是如何执行被推后的工作——有这样一个链表,它由结构体work_struct组成,而这个work_struct则描述了一个工作,一旦这个工作被执行完,
相应的work_struct对象就从链表上移去,当链表上不再有对象时,工作者线程就会继续休眠。因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。
Linux软中断和工作队列的作用是什么
Linux中的软中断和工作队列是中断上下部机制中的下半部实现机制。
1.软中断一般是“可延迟函数”的总称,它不能睡眠,不能阻塞,它处于中断上下文,不能进城切换,
软中断不能被自己打断,只能被硬件中断打断(上半部),可以并发的运行在多个CPU上。所以软中断必须设计成可重入的函数,因此也需要自旋锁来保护其数据结构。
2.工作队列中的函数处在进程上下文中,它可以睡眠,也能被阻塞,能够在不同的进程间切换,以完成不同的工作。
可延迟函数和工作队列都不能访问用户的进程空间,可延时函数在执行时不可能有任何正在运行的进程,工作队列的函数有内核进程执行,他不能访问用户空间地址。