Linux 中,当外设触发中断后,大体处理流程如下:

a -- 具体CPU architecture相关的模块会进行现场保护,然后调用machine driver对应的中断处理handler;

b -- machine driver对应的中断处理handler中会根据硬件的信息获取HW interrupt ID,并且通过irq domain模块翻译成IRQ number;

c --  调用该IRQ number 对应的high level irq event handler,在这个high level的handler中,会通过和interupt controller交互,进行中断处理的flow control(处理中断的嵌套、抢占等),当然最终会遍历该中断描述符的IRQ action list,调用外设的specific handler来处理该中断;

d -- 具体CPU architecture相关的模块会进行现场恢复;

总结下来,整个过程可以分为三部分:1、硬件处理部分;2、汇编处理部分;3、C 处理部分;

下面我们来追踪一下代码,了解当中断发生时,Linux 是如何处理的,前面的一些中断初始化部分就不再这里详述了,下面开始具体分析:

一、硬件处理部分

当一切准备好之后,一旦打开处理器的全局中断就可以处理来自外设的各种中断事件了。

当外设(SOC内部或者外部都可以)检测到了中断事件,就会通过interrupt requestion line上的电平或者边沿(上升沿或者下降沿或者both)通知到该外设连接到的那个中断控制器,而中断控制器就会在多个处理器中选择一个,并把该中断通过IRQ(或者FIQ,本文不讨论FIQ的情况)分发给该processor。

ARM处理器感知到了中断事件后,会进行下面一系列的动作(硬件处理部分):

1、切换处理器模式

修改 CPSR 寄存器中的 M[4:0],切换处理器模式位 IRQ Mode(这里M[4:0] 所添值为 10010);

2、保护现场

 保存发生中断时,CPSR值与PC值(为恢复现场做准备);这里要注意,此时中断可能发生在 usr mode (用户空间),也可能发生在 SVC mode(内核空间);

3、mask IRQ exception

关闭IRQ中断,也就是设定CPSR.I = 1;

4、设定PC值为IRQ exception vector

实现向异常向量表的跳转,ARM处理器会跳转到IRQ的exception vector地址,到这硬件所做的工作就结束了,下面就是软件行为了。

 软件处理部分流程如下:

可以看到 Vetor_irq 是汇编部分入口点,而Asm_do_irq 是C 部分入口点,下面分析Vetor_irq 向 Asm_do_irq 跳转过程


二、汇编部分

前面硬件部分结束后,跳转到相应的异常中断处理程序处执行,对于ARMv7向量表普遍是0xFFFF0018 ,而对于低向量PC=0x00000018

假设在用户空间时,产生了外部硬件中断,这个时候的会跳转到异常向量表,向量表(vector table)的代码如下

【arch/arm/kernel/entry-armv.S】

[cpp] view plaincopy
  1. __vectors_start:---------------〉在中断向量表被拷贝后,该地址就是0xffff0000.
  2. ARM( swi SYS_ERROR0 )
  3. THUMB( svc #0 )
  4. THUMB( nop )
  5. W(b) vector_und + stubs_offset
  6. W(ldr) pc, .LCvswi + stubs_offset
  7. W(b) vector_pabt + stubs_offset
  8. W(b) vector_dabt + stubs_offset
  9. W(b) vector_addrexcptn + stubs_offset
  10. W(b)vector_irq + stubs_offset----------〉当外部中断产生时,pc直接指向这个地址。
  11. W(b) vector_fiq + stubs_offset
  12. .globl __vectors_end

1、IRQ mode中的处理 (vector table --- > vector_irq )

IRQ mode的处理都在vector_irq中,vector_stub是一个宏,定义如下:

[cpp] view plaincopy
  1. /*
  2. * Vector stubs.
  3. *
  4. * This code is copied to 0xffff0200 so we can use branches in the
  5. * vectors, rather than ldr's.  Note that this code must not
  6. * exceed 0x300 bytes.
  7. *
  8. * Common stub entry macro:
  9. *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
  10. *
  11. * SP points to a minimal amount of processor-private memory, the address
  12. * of which is copied into r0 for the mode specific abort handler.
  13. */
  14. .macro  vector_stub, name, mode, correction=0
  15. .align  5
  16. vector_\name:
  17. .if \correction
  18. sub lr, lr, #\correction  //因为硬件处理器是将当前指令的下两条指令的地址存储在lr寄存器中,所以这里需要减4,让他指向被中断指令的下一条,这样当中断被恢复时,可以继续被中断的指令继续执行。
  19. .endif    //需要注意的是,这个时候的lr寄存器,已经是irq模式下的私有寄存器了,在中断产生时,硬件处理器已经自动为他赋了值。
  20. @
  21. @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
  22. @ (parent CPSR)
  23. @
  24. stmia   sp, {r0, lr}        @ save r0, lr//保存r0和lr寄存器,即被中断的下一条指令
  25. mrs lr, spsr
  26. str lr, [sp, #8]        @ save spsr
  27. @
  28. @ Prepare for SVC32 mode.  IRQs remain disabled.//准备从中断模式切换到管理模式,不同的模式,对应各自不同的堆栈。
  29. @
  30. mrs r0, cpsr
  31. eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
  32. msr spsr_cxsf, r0
  33. @
  34. @ the branch table must immediately follow this code
  35. @
  36. and lr, lr, #0x0f           //获取被中断前,处理器所处的模式
  37. THUMB( adr r0, 1f          )
  38. THUMB( ldr lr, [r0, lr, lsl #2]    )
  39. mov r0, sp  //让r0寄存器指向中断模式下堆栈的基地址
  40. ARM(   ldr lr, [pc, lr, lsl #2]    )
  41. movs    pc, lr          @ branch to handler in SVC mode,同时将中断模式下的spsr_irq(irq私有的)赋值给cpsr(该寄存器所有模式共享)
  42. ENDPROC(vector_\name)

从这可以看出 vector_stub 的使用方法:

 vector_stub, name, mode, correction=0

上面这段究竟做了些什么呢?

(1)我们期望在栈上保存发生中断时候的硬件现场(HW context),这里就包括ARM的core register。上一章我们已经了解到,当发生IRQ中断的时候,lr中保存了发生中断的PC+4,如果减去4的话,得到的就是发生中断那一点的PC值。

(2)当前是IRQ mode,SP_irq在初始化的时候已经设定(12个字节)。在irq mode的stack上,依次保存了发生中断那一点的r0值、PC值以及CPSR值(具体操作是通过spsr进行的,其实硬件已经帮我们保存了CPSR到SPSR中了)。为何要保存r0值?因为随后的代码要使用r0寄存器,因此我们要把r0放到栈上,只有这样才能完完全全恢复硬件现场。

(3)可怜的IRQ mode稍纵即逝,这段代码就是准备将ARM推送到SVC mode。如何准备?其实就是修改SPSR的值,SPSR不是CPSR,不会引起processor mode的切换(毕竟这一步只是准备而已)。

(4)很多异常处理的代码返回的时候都是使用了stack相关的操作,这里没有。“movs pc, lr ”指令除了字面上意思(把lr的值付给pc),还有一个
隐含的操作(movs中‘s’的含义):把SPSR copy到CPSR,从而实现了模式的切换

这里有个问题:中断为什么必须进入svc模式

一个最重要原因是:如果一个中断模式(例如从usr进入irq模式,在irq模式中)中重新允许了中断,并且在这个中断例程中使用了BL指令调用子程序,BL指令会自动将子程序返回地址保存到当前模式的sp(即r14_irq)中,这个地址随后会被在当前模式下产生的中断所破坏,因为产生中断时CPU会将当前模式的PC保存到r14_irq,这样就把刚刚保存的子程序返回地址冲掉。为了避免这种情况,中断例程应该切换到SVC或者系统模式,这样的话,BL指令可以使用r14_svc来保存子程序的返回地址。


2、vector table --- > vector_irq  ---> vector _stub

对于IRQ Mode 则 vector_stub, irq, IRQ_MODE, 4   

[cpp] view plaincopy
  1. __stubs_start:
  2. /*
  3. * Interrupt dispatcher
  4. */
  5. vector_stub irq, IRQ_MODE, 4  //减去4,确保返回发生中断之后的那条指令
  6. .long __irq_usr@  0  (USR_26 / USR_32)  //从用户态进入中断的处理函数 base address + 0
  7. .long __irq_invalid@  1  (FIQ_26 / FIQ_32)
  8. .long __irq_invalid@  2  (IRQ_26 / IRQ_32)
  9. .long __irq_svc@  3  (SVC_26 / SVC_32)  //从SVC进入中断的处理函数 base address + 12
  10. .long __irq_invalid@  4
  11. .long __irq_invalid@  5
  12. .long __irq_invalid@  6
  13. .long __irq_invalid@  7
  14. .long __irq_invalid@  8
  15. .long __irq_invalid@  9
  16. .long __irq_invalid@  a
  17. .long __irq_invalid@  b
  18. .long __irq_invalid@  c
  19. .long __irq_invalid@  d
  20. .long __irq_invalid@  e
  21. .long __irq_invalid@  f

这里根据被中断时,处理器模式的不同,分别跳转到__irq_usr和__irq_svc两个分支。

3、vector table --- > vector_irq  ---> vector _stub ---> __irq_usr

在这里我们以__irq_usr为例来说明:

[cpp] view plaincopy
  1. __irq_usr:
  2. usr_entry       //进行中断前的硬件上下文的保存
  3. kuser_cmpxchg_check
  4. irq_handler
  5. get_thread_info tsk//获取被中断的用户进程或内核线程所对应的内核栈所对应的thread info结构。
  6. mov why, #0
  7. b   ret_to_user_from_irq//恢复被中断时的上下文,然后继续被中断的进程或线程的执行
  8. UNWIND(.fnend      )
  9. ENDPROC(__irq_usr)

4、vector table --- > vector_irq  ---> vector _stub ---> __irq_usr ---> usr_entry

usr_entry展开如下:

[cpp] view plaincopy
  1. .macro  usr_entry
  2. UNWIND(.fnstart )
  3. UNWIND(.cantunwind  )   @ don't unwind the user space
  4. sub sp, sp, #S_FRAME_SIZE   // #S_FRAME_SIZE的值为72
  5. ARM(    stmib   sp, {r1 - r12}  )      //尽管当前是处于管理模式,但由于svc和usr的r0-r12是公共的,所以相当于保存用户模式的r1-r12寄存器
  6. THUMB(  stmia   sp, {r0 - r12}  )
  7. ldmia   r0, {r3 - r5}          //将之前保存在中断模式堆栈中的r0_usr,lr,spsr分别存储到r3-r5中
  8. add r0, sp, #S_PC       @ here for interlock avoidance #S_PC=60
  9. mov r6, #-1         @  ""  ""     ""        ""
  10. str r3, [sp]        @ save the "real" r0 copied
  11. @ from the exception stack
  12. @
  13. @ We are now ready to fill in the remaining blanks on the stack:
  14. @
  15. @  r4 - lr_<exception>, already fixed up for correct return/restart
  16. @  r5 - spsr_<exception>
  17. @  r6 - orig_r0 (see pt_regs definition in ptrace.h)
  18. @
  19. @ Also, separately save sp_usr and lr_usr
  20. @
  21. stmia   r0, {r4 - r6}
  22. ARM(    stmdb   r0, {sp, lr}^           )//保存用户模式下的sp_usr,lr_usr
  23. THUMB(  store_user_sp_lr r0, r1, S_SP - S_PC    )
  24. @
  25. @ Enable the alignment trap while in kernel mode
  26. @
  27. alignment_trap r0
  28. @
  29. @ Clear FP to mark the first stack frame
  30. @
  31. zero_fp
  32. ifdef CONFIG_IRQSOFF_TRACER
  33. bl  trace_hardirqs_off
  34. endif
  35. .endm

代码执行到这里的时候,ARM处理已经切换到了【SVC mode】。一旦进入SVC mode,ARM处理器看到的寄存器已经发生变化,这里的sp已经变成了sp_svc了。因此,后续的压栈操作都是压入了发生中断那一刻的进程的(或者内核线程)内核栈(svc mode栈)。

此时的管理模式的内核栈分布如下:

需要说明的是:上图中的lr_irq即为用户模式下被中断指令的下一条指令,spsr_irq即为用户模式下被中断时的cpsr寄存器。

5、vector table --- > vector_irq  ---> vector _stub ---> __irq_usr ---> (usr_entry  ---> irq_handler )

       usr_entry 结束后,会执行 irq_handler

irq_handler的实现过程 arch\arm\kernel\entry-armv.S

[cpp] view plaincopy
  1. .macro irq_handler
  2. get_irqnr_preamble r5, lr
  3. @在include/asm/arch-s3c2410/entry-macro.s中定义了宏get_irqnr_preamble为空操作,什么都不做
  4. 1: get_irqnr_and_base r0, r6, r5, lr @判断中断号,通过R0返回,3.5节有实现过程
  5. movne r1, sp
  6. @
  7. @ routine called with r0 = irq number, r1 = struct pt_regs *
  8. @
  9. adrne lr, 1b
  10. bne asm_do_IRQ @进入中断处理。
  11. ……
  12. .endm

可以看到 bne asm_do_IRQ @进入中断处理  泪奔~~o(>_<)o ~~ 终于到了asm_do_IRQ

可以看到整个跳转过程:

vector table --- > vector_irq  --->vector_stub, irq, IRQ_MODE, 4 ---> __irq_usr ---> usr_entry  ---> irq_handler --->asm_do_IRQ


三、C语言处理部分

1、 asm_do_IRQ

asm_do_IRQ实现过程,arch/arm/kernel/irq.c

[cpp] view plaincopy
  1. * asm_do_IRQ is the interface to be used from assembly code.
  2. */
  3. asmlinkage void __exception_irq_entry
  4. asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
  5. {
  6. handle_IRQ(irq, regs);
  7. }
[cpp] view plaincopy
  1. <span style="font-family: Arial, Helvetica, sans-serif;">其中关键一步handle_IRQ(irq, regs)</span>

2、handle_IRQ(irq, regs)

[cpp] view plaincopy
  1. /*
  2. * handle_IRQ handles all hardware IRQ's.  Decoded IRQs should
  3. * not come via this function.  Instead, they should provide their
  4. * own 'handler'.  Used by platform code implementing C-based 1st
  5. * level decoding.
  6. */
  7. void handle_IRQ(unsigned int irq, struct pt_regs *regs)
  8. {
  9. struct pt_regs *old_regs = set_irq_regs(regs);
  10. irq_enter();
  11. /*
  12. * Some hardware gives randomly wrong interrupts.  Rather
  13. * than crashing, do something sensible.
  14. */
  15. if (unlikely(irq >= nr_irqs)) {
  16. if (printk_ratelimit())
  17. printk(KERN_WARNING "Bad IRQ%u\n", irq);
  18. ack_bad_irq(irq);
  19. } else {
  20. generic_handle_irq(irq);
  21. }
  22. irq_exit();
  23. set_irq_regs(old_regs);
  24. }

主要调用generic_handle_irq(irq)

3、generic_handle_irq(irq)

[cpp] view plaincopy
  1. include/linux/irq.h
  2. static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
  3. {
  4. #ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
  5. desc->handle_irq(irq, desc);
  6. #else
  7. if (likely(desc->handle_irq))
  8. desc->handle_irq(irq, desc);
  9. else
  10. __do_IRQ(irq);
  11. #endif
  12. }
  13. static inline void generic_handle_irq(unsigned int irq)
  14. {
  15. generic_handle_irq_desc(irq, irq_to_desc(irq));
  16. }

generic_handle_irq调用前面定义的generic_handle_irq_desc

4、generic_handle_irq_des

[cpp] view plaincopy
  1. static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
  2. desc->handle_irq(irq, desc);

而 generic_handle_irq_desc也没做什么,调用desc——>handle_irq</strong>,这个函数就是irq_desc中的成员

5、irq_desc结构体   

Linux内核将所有中断统一编号,使用irq_desc结构来描述中断:每个数组项对应一个中断(也可能是一组中断,它们使用共同的中断号),里面记录了中断的名称,中断状态,中断标记,并提供硬件访问函数(清除,屏蔽,使能中断),提供了这个中断的处理函数的入口,通过它可以调用用户注册的中断处理函数

[cpp] view plaincopy
  1. include/linux/irq.h
  2. {
  3. .........
  4. irq_flow_handler_t handle_irq;   //当前的中断处理函数入口
  5. struct irq_chip *chip;       //底层的硬件访问
  6. ..........
  7. struct irqaction *action; //用户提供的中断处理函数链表
  8. unsigned int status; //IRQ状态
  9. ...........
  10. const char *name;     //中断名称
  11. } ____cacheline_internodealigned_in_smp;

Handle_irq是这个或者这组中断的处理函数入口

这里调用desc->handle_irq分为俩种情况,一是单独的中断号的,一是共享中断号的,俩者的区别在于后者需要先判断是共享中断的中的哪一个然后再真正的去调用handle_irq,所以我这里分析一下单独中断号的处理流程,共享中断也是一样可以分析。

我们分析一个具体的,以外部中断为例

[cpp] view plaincopy
  1. for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
  2. irqdbf("registering irq %d (ext int)\n", irqno);
  3. set_irq_chip(irqno, &s3c_irq_eint0t4);
  4. set_irq_handler(irqno, handle_edge_irq);
  5. set_irq_flags(irqno, IRQF_VALID);
  6. }

上面代码我们看到,set_irq_handler的值是handler_edge_irq ,这里是处理边沿触发的中断函数,当然还有电平触发方式的中断(handler_level_irq),继续看代码

[cpp] view plaincopy
  1. kernel/irq/chip.c
  2. void
  3. handle_edge_irq(unsigned int irq, struct irq_desc *desc)
  4. {
  5. spin_lock(&desc->lock);         上锁
  6. desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
  7. /*
  8. * If we're currently running this IRQ, or its disabled,
  9. * we shouldn't process the IRQ. Mark it pending, handle
  10. * the necessary masking and go out
  11. */
  12. if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||   判断
  13. !desc->action)) {
  14. desc->status |= (IRQ_PENDING | IRQ_MASKED);
  15. mask_ack_irq(desc, irq);   屏蔽并清除中断
  16. goto out_unlock;
  17. }
  18. kstat_incr_irqs_this_cpu(irq, desc); 中断统计计数
  19. /* Start handling the irq */
  20. if (desc->chip->ack)      应答中断
  21. desc->chip->ack(irq);
  22. /* Mark the IRQ currently in progress.*/
  23. desc->status |= IRQ_INPROGRESS;   标记中断状态
  24. do {
  25. struct irqaction *action = desc->action;
  26. irqreturn_t action_ret;
  27. if (unlikely(!action)) {
  28. desc->chip->mask(irq);
  29. goto out_unlock;
  30. }
  31. /*
  32. * When another irq arrived while we were handling
  33. * one, we could have masked the irq.
  34. * Renable it, if it was not disabled in meantime.
  35. */
  36. if (unlikely((desc->status &
  37. (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
  38. (IRQ_PENDING | IRQ_MASKED))) {
  39. desc->chip->unmask(irq);
  40. desc->status &= ~IRQ_MASKED;
  41. }
  42. desc->status &= ~IRQ_PENDING;
  43. spin_unlock(&desc->lock);
  44. action_ret = handle_IRQ_event(irq, action);  处理中断,最重要的函数,注意参数,action这个参数将联系到我们的用户中断处理函数
  45. if (!noirqdebug)
  46. note_interrupt(irq, desc, action_ret);
  47. spin_lock(&desc->lock);
  48. } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
  49. desc->status &= ~IRQ_INPROGRESS;
  50. out_unlock:
  51. spin_unlock(&desc->lock);
  52. }

进行追踪handle_IRQ_event()

[cpp] view plaincopy
  1. kernel/irq/handle.c
  2. trace_irq_handler_entry(irq, action);
  3. ret = action->handler(irq, action->dev_id);
  4. trace_irq_handler_exit(irq, action, ret);

调用action中的handler,我们注册中断的时候注意任务就是构造一个irqaction结构并添加到irq_desc中的irqaction链表中的指针action下面。现在处理中断中我们就看到了调用了我们自己的中断处理函数来处理中断了。至此中断处理流程就结束了

总结软件部分:

asm_do_IRQ --> handle_IRQ(irq, regs)--> generic_handle_irq(irq) --> generic_handle_irq_desc --> (desc—>handle_irq ) -->handle_level_irq --> handle_irq_event---> action->handler(irq, action->dev_id)

Exynos4412 中断处理流程详解相关推荐

  1. 跨境电商三单对碰三单申报流程详解

    跨境电商三单对碰三单申报流程详解 概要:三单申报是指"电子订单.电子运单.支付凭证". 1.电子订单: 适合申报企业类型"电商企业.电商交易平台.电商境内代理企业&quo ...

  2. Android事件流程详解

    Android事件流程详解 网络上有不少博客讲述了android的事件分发机制和处理流程机制,但是看过千遍,总还是觉得有些迷迷糊糊,因此特地抽出一天事件来亲测下,向像我一样的广大入门程序员详细讲述an ...

  3. 基于spark mllib_Spark高级分析指南 | 机器学习和分析流程详解(下)

    - 点击上方"中国统计网"订阅我吧!- 我们在Spark高级分析指南 | 机器学习和分析流程详解(上)快速介绍了一下不同的高级分析应用和用力,从推荐到回归.但这只是实际高级分析过程 ...

  4. View的绘制-draw流程详解

    目录 作用 根据 measure 测量出的宽高,layout 布局的位置,渲染整个 View 树,将界面呈现出来. 具体分析 以下源码基于版本27 DecorView 的draw 流程 在<Vi ...

  5. 杂志订阅管理系统c++_电池管理系统BMS功能安全开发流程详解

    点击上面 "电动知家"可以订阅哦! BMS功能安全开发流程详解 BMS和ISO26262 - BMS & ISO26262简介 BMS即Battery Management ...

  6. View的绘制-layout流程详解

    目录 作用 根据 measure 测量出来的宽高,确定所有 View 的位置. 具体分析 View 本身的位置是通过它的四个点来控制的: 以下涉及到源码的部分都是版本27的,为方便理解观看,代码有所删 ...

  7. U-Boot启动流程详解

    参考:U-Boot顶层目录链接脚本文件(u-boot.lds)介绍 作者:一只青木呀 发布时间: 2020-10-23 13:52:23 网址:https://blog.csdn.net/weixin ...

  8. java isight zmf_isight集成catia和abaqus,nastran流程详解

    isight集成catia和abaqus,nastran流程详解 CAD软件中参数化建模,导入有限元软件中计算各个工况,isight根据计算结果调整模型参数,反复迭代计算的过程是尺寸优化的典型问题~ ...

  9. java处理请求的流程_Java Spring mvc请求处理流程详解

    Spring mvc请求处理流程详解 前言 spring mvc框架相信很多人都很熟悉了,关于这方面的资料也是一搜一大把.但是感觉讲的都不是很细致,让很多初学者都云里雾里的.本人也是这样,之前研究过, ...

最新文章

  1. 游戏设计行业标准测试:秘密收集
  2. 第十四课.Transformer
  3. dijkstra算法matlab程序_编程习题课 | 用最短路算法为你的小地图导航
  4. Mysql 内置函数
  5. 奇异值分解讨论及其实现的计算步骤
  6. mysql存储引擎中INNODB和MyISAM的区别
  7. Spring MVC 源码-初始化阶段
  8. Cisco SSL ×××
  9. 前端工程师面试题汇总
  10. 第七季2:MP4v2库的移植与播放实战
  11. matlab 仿真钢琴,用Matlab模拟钢琴的声音
  12. Spring项目在启动时报Error running 'ProviderC': Cannot start process, the working directory 'E:\ ' does not
  13. java 文件工具类_java文件工具类,文件的一些基本操作
  14. QT编程ARM下摄像头无法使用怎么办
  15. 架构 - 5种常见的软件架构
  16. C语言中的除法运算符( /)
  17. 下行法求最小割集案例_故障树中最小割集和最小径集的改进算法
  18. 手写jQuery轮播图插件,即拿即用,更多接口,更少代码实现你想要的轮播图~~
  19. JAVA中Action层, Service层 ,modle层 和 Dao层的功能区分
  20. HDFS DataNode高密度存储机型的探索尝试

热门文章

  1. Silverlight 5 新特性
  2. Oracle:使用ASM自动存储管理, 严重推荐
  3. tb计算机存储单位_如何节省数TB的云存储
  4. leetcode 377. 组合总和 Ⅳ(dp)
  5. 安软件一劳永逸_如何克服一劳永逸地公开演讲的恐惧
  6. 谷歌cloud_通过使用Google Cloud ML大规模提供机器学习模型,我们学到了什么
  7. vue.js 全局应用js_如何在不到7分钟的时间内测试您的Vue.js应用
  8. JS之this与语句分号问题v(**V**)v
  9. windows7使用Sphinx+PHP+MySQL详细介绍
  10. Struts2的Action配置的各项默认值