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】

  __vectors_start:---------------〉在中断向量表被拷贝后,该地址就是0xffff0000.

  ARM( swi SYS_ERROR0 )

  THUMB( svc #0 )

  THUMB( nop )

  W(b) vector_und + stubs_offset

  W(ldr) pc, .LCvswi + stubs_offset

  W(b) vector_pabt + stubs_offset

  W(b) vector_dabt + stubs_offset

  W(b) vector_addrexcptn + stubs_offset

  W(b)vector_irq + stubs_offset----------〉当外部中断产生时,pc直接指向这个地址。

  W(b) vector_fiq + stubs_offset

  .globl __vectors_end

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

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

 

 /*

  * 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_\name:

  .if \correction

  sub lr, lr, #\correction //因为硬件处理器是将当前指令的下两条指令的地址存储在lr寄存器中,所以这里需要减4,让他指向被中断指令的下一条,这样当中断被恢复时,可以继续被中断的指令继续执行。

  .endif //需要注意的是,这个时候的lr寄存器,已经是irq模式下的私有寄存器了,在中断产生时,硬件处理器已经自动为他赋了值。

  @

  @ Save r0, lr_ (parent PC) and spsr_

  @ (parent CPSR)

  @

  stmia sp, {r0, lr} @ save r0, lr//保存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 //让r0寄存器指向中断模式下堆栈的基地址

  ARM( ldr lr, [pc, lr, lsl #2] )

  movs pc, lr @ branch to handler in SVC mode,同时将中断模式下的spsr_irq(irq私有的)赋值给cpsr(该寄存器所有模式共享)

  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

 

 __stubs_start:

  /*

  * Interrupt dispatcher

  */

  vector_stub irq, IRQ_MODE, 4 //减去4,确保返回发生中断之后的那条指令

  .long __irq_usr@ 0 (USR_26 / USR_32) //从用户态进入中断的处理函数 base address + 0

  .long __irq_invalid@ 1 (FIQ_26 / FIQ_32)

  .long __irq_invalid@ 2 (IRQ_26 / IRQ_32)

  .long __irq_svc@ 3 (SVC_26 / SVC_32) //从SVC进入中断的处理函数 base address + 12

  .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_svc两个分支。

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

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

  __irq_usr:

  usr_entry //进行中断前的硬件上下文的保存

  kuser_cmpxchg_check

  irq_handler

  get_thread_info tsk//获取被中断的用户进程或内核线程所对应的内核栈所对应的thread info结构。

  mov why, #0

  b ret_to_user_from_irq//恢复被中断时的上下文,然后继续被中断的进程或线程的执行

  UNWIND(.fnend )

  ENDPROC(__irq_usr)

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

  

usr_entry展开如下:

  .macro usr_entry

  UNWIND(.fnstart )

  UNWIND(.cantunwind ) @ don't unwind the user space

  sub sp, sp, #S_FRAME_SIZE // #S_FRAME_SIZE的值为72

  ARM( stmib sp, {r1 - r12} ) //尽管当前是处于管理模式,但由于svc和usr的r0-r12是公共的,所以相当于保存用户模式的r1-r12寄存器

  THUMB( stmia sp, {r0 - r12} )

  ldmia r0, {r3 - r5} //将之前保存在中断模式堆栈中的r0_usr,lr,spsr分别存储到r3-r5中

  add r0, sp, #S_PC @ here for interlock avoidance #S_PC=60

  mov r6, #-1 @ "" "" "" ""

  str r3, [sp] @ save the "real" r0 copied

  @ from the exception stack

  @

  @ We are now ready to fill in the remaining blanks on the stack:

  @

  @ r4 - lr_, already fixed up for correct return/restart

  @ r5 - spsr_

  @ r6 - orig_r0 (see pt_regs definition in ptrace.h)

  @

  @ Also, separately save sp_usr and lr_usr

  @

  stmia r0, {r4 - r6}

  ARM( stmdb r0, {sp, lr}^ )//保存用户模式下的sp_usr,lr_usr

  THUMB( store_user_sp_lr r0, r1, S_SP - S_PC )

  @

  @ Enable the alignment trap while in kernel mode

  @

  alignment_trap r0

  @

  @ Clear FP to mark the first stack frame

  @

  zero_fp

  ifdef CONFIG_IRQSOFF_TRACER

  bl trace_hardirqs_off

  endif

  .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

  .macro irq_handler

  get_irqnr_preamble r5, lr

  @在include/asm/arch-s3c2410/entry-macro.s中定义了宏get_irqnr_preamble为空操作,什么都不做

  1: get_irqnr_and_base r0, r6, r5, lr @判断中断号,通过R0返回,3.5节有实现过程

  movne r1, sp

  @

  @ routine called with r0 = irq number, r1 = struct pt_regs *

  @

  adrne lr, 1b

  bne asm_do_IRQ @进入中断处理。

  ……

  .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

  * asm_do_IRQ is the interface to be used from assembly code.

  */

  asmlinkage void __exception_irq_entry

  asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

  {

  handle_IRQ(irq, regs);

  }

  其中关键一步handle_IRQ(irq, regs)

  2、handle_IRQ(irq, regs)

 

 /*

  * handle_IRQ handles all hardware IRQ's. Decoded IRQs should

  * not come via this function. Instead, they should provide their

  * own 'handler'. Used by platform code implementing C-based 1st

  * level decoding.

  */

  void handle_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\n", irq);

  ack_bad_irq(irq);

  } else {

  generic_handle_irq(irq);

  }

  irq_exit();

  set_irq_regs(old_regs);

  }

  主要调用generic_handle_irq(irq)

  3、generic_handle_irq(irq)

 

 include/linux/irq.h

  static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)

  {

  #ifdef CONFIG_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

  }

  static inline void generic_handle_irq(unsigned int irq)

  {

  generic_handle_irq_desc(irq, irq_to_desc(irq));

  }

  generic_handle_irq调用前面定义的generic_handle_irq_desc

  4、generic_handle_irq_des

 

 static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)

  {

  desc->handle_irq(irq, desc);

  }

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

  5、irq_desc结构体

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

  include/linux/irq.h

  {

  .........

  irq_flow_handler_t handle_irq; //当前的中断处理函数入口

  struct irq_chip *chip; //底层的硬件访问

  ..........

  struct irqaction *action; //用户提供的中断处理函数链表

  unsigned int status; //IRQ状态

  ...........

  const char *name; //中断名称

  } ____cacheline_internodealigned_in_smp;

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

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

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

 

 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_handler的值是handler_edge_irq ,这里是处理边沿触发的中断函数,当然还有电平触发方式的中断(handler_level_irq),继续看代码

  

kernel/irq/chip.c

  void

  handle_edge_irq(unsigned int irq, struct irq_desc *desc)

  {

  spin_lock(&desc->lock); 上锁

  desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);

  /*

  * If we're currently running this IRQ, or its disabled,

  * we shouldn't process the IRQ. Mark it pending, handle

  * the necessary masking and go out

  */

  if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) || 判断

  !desc->action)) {

  desc->status |= (IRQ_PENDING | IRQ_MASKED);

  mask_ack_irq(desc, irq); 屏蔽并清除中断

  goto out_unlock;

  }

  kstat_incr_irqs_this_cpu(irq, desc); 中断统计计数

  /* Start handling the irq */

  if (desc->chip->ack) 应答中断

  desc->chip->ack(irq);

  /* Mark the IRQ currently in progress.*/

  desc->status |= IRQ_INPROGRESS; 标记中断状态

  do {

  struct irqaction *action = desc->action;

  irqreturn_t action_ret;

  if (unlikely(!action)) {

  desc->chip->mask(irq);

  goto out_unlock;

  }

  /*

  * When another irq arrived while we were handling

  * one, we could have masked the irq.

  * Renable it, if it was not disabled in meantime.

  */

  if (unlikely((desc->status &

  (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==

  (IRQ_PENDING | IRQ_MASKED))) {

  desc->chip->unmask(irq);

  desc->status &= ~IRQ_MASKED;

  }

  desc->status &= ~IRQ_PENDING;

  spin_unlock(&desc->lock);

  action_ret = handle_IRQ_event(irq, action); 处理中断,最重要的函数,注意参数,action这个参数将联系到我们的用户中断处理函数

  if (!noirqdebug)

  note_interrupt(irq, desc, action_ret);

  spin_lock(&desc->lock);

  } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

  desc->status &= ~IRQ_INPROGRESS;

  out_unlock:

  spin_unlock(&desc->lock);

  }

  进行追踪handle_IRQ_event()

  kernel/irq/handle.c

  trace_irq_handler_entry(irq, action);

  ret = action->handler(irq, action->dev_id);

  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中断

  http://www.makeru.com.cn/live/3512.html?s=45051

  Exynos4412裸机LED驱动开发

  http://www.makeru.com.cn/live/3556.html?s=45051

  uboot添加自定义命令

  http://www.makeru.com.cn/live/1392_815.html?s=45051

  Uboot启劢流程分析

  http://www.makeru.com.cn/live/1392_1107.html?s=45051

  -嵌入式系统移植(主要讲UBOOT移植)

  http://www.makeru.com.cn/live/3483_1629.html?s=45051

  深入讲解uboot

  http://www.makeru.com.cn/live/3483_1534.html?s=45051

  RISC-V嵌入式系统开发

  http://www.makeru.com.cn/course/details/5666?s=45051

  循环链表及线性表的应用

  http://www.makeru.com.cn/course/details/1902?s=45051

Exynos4412 中断处理流程详解的更多相关文章

  1. C++的性能C#的产能?! - .Net Native 系列《二》:.NET Native开发流程详解

    之前一文<c++的性能, c#的产能?!鱼和熊掌可以兼得,.NET NATIVE初窥> 获得很多朋友支持和鼓励,也更让我坚定做这项技术的推广者,希望能让更多的朋友了解这项技术,于是先从官方 ...

  2. [nRF51822] 5、 霸屏了——详解nRF51 SDK中的GPIOTE(从GPIO电平变化到产生中断事件的流程详解)

    :由于在大多数情况下GPIO的状态变化都会触发应用程序执行一些动作.为了方便nRF51官方把该流程封装成了GPIOTE,全称:The GPIO Tasks and Events (GPIOTE) . ...

  3. 迅为4412开发板Linux驱动教程——总线_设备_驱动注册流程详解

    本文转自:http://www.topeetboard.com 视频下载地址: 驱动注册:http://pan.baidu.com/s/1i34HcDB 设备注册:http://pan.baidu.c ...

  4. Linux启动流程详解【转载】

    在BIOS阶段,计算机的行为基本上被写死了,可以做的事情并不多:一般就是通电.BIOS.主引导记录.操作系统这四步.所以我们一般认为加载内核是linux启动流程的第一步. 第一步.加载内核 操作系统接 ...

  5. iOS 组件化流程详解(git创建流程)

    [链接]组件化流程详解(一)https://www.jianshu.com/p/2deca619ff7e

  6. git概念及工作流程详解

    git概念及工作流程详解 既然我们已经把gitlab安装完毕[当然这是非必要条件],我们就可以使用git来管理自己的项目了,前文也多多少少提及到git的基本命令,本文就先简单对比下SVN与git的区别 ...

  7. Lucene系列六:Lucene搜索详解(Lucene搜索流程详解、搜索核心API详解、基本查询详解、QueryParser详解)

    一.搜索流程详解 1. 先看一下Lucene的架构图 由图可知搜索的过程如下: 用户输入搜索的关键字.对关键字进行分词.根据分词结果去索引库里面找到对应的文章id.根据文章id找到对应的文章 2. L ...

  8. JPEG图像压缩算法流程详解

    JPEG图像压缩算法流程详解 JPEG代表Joint Photographic Experts Group(联合图像专家小组).此团队创立于1986年,1992年发布了JPEG的标准而在1994年获得 ...

  9. unity3d-配置Android环境,打包发布Apk流程详解

    31:unity3d-配置Android环境,打包发布Apk流程详解 作者 阿西纳尼 关注 2016.08.28 22:52 字数 498 阅读 1806评论 0喜欢 5 Unity配置Android ...

随机推荐

  1. 动态规划精讲(一)A单串

    单串 单串 dp[i] 线性动态规划最简单的一类问题,输入是一个串,状态一般定义为 dp[i] := 考虑[0..i]上,原问题的解,其中 i 位置的处理,根据不同的问题,主要有两种方式: 第一种是 ...

  2. Django学习day05随堂笔记

    每日测验 """ 今日考题 1.反向解析的本质是什么,无名和有名反向解析如何操作? 2..路由分发能够实现的前提是什么,需要注意什么,名称空间什么时候使用 3..什么是虚 ...

  3. Android View post 方法

    解析View.post方法.分析一下这个方法的流程. 说起post方法,我们很容易联想到Handler的post方法,都是接收一个Runnable对象.那么这两个方法有啥不同呢? Handler的po ...

  4. 解决下载的css样式文件在同一排的问题

    一.将样式文件里的所有内容复制到word里 Ctrl+F查找替换,将所有分号;替换成;^p 小提示:在word里^p表示回车 二.将央视文件里的所有反括号}进行替换替换成}^p然后将代码整个粘贴回样式 ...

  5. Java基础系列(15)- 用户交互Scanner

    Scanner对象 之前我们学的基本语法中我们并没有实现程序和人的交互,但是Java给我们提供了这样一个工具类,我们可以获取用户的输入.java.util.Scanner是Java5的新特征.我们可以 ...

  6. chrome 的手机调试工具 toggle device toolbar

    chrome 的手机调试工具 toggle device toolbar 是否可以模拟到不同系统,如苹果系统和安卓系统.

  7. git 报错 gitThere is no tracking information for the current branch. Please specify which branch you w

    新建本地分支后将本地分支推送到远程库, 使用git pull 或者 git push 的时候报错gitThere is no tracking information for the current ...

  8. sublime text 3 在Windows下配置sublimelinter-php的路径问题

    首先用package control安装sublimelinter和sublimelinter-php,然后依次点击菜单preference-package settings-sublimelinte ...

  9. 【转载】在Windows终端中显示UTF-8字符

    一直苦恼于如何在Windows终端中显示UTF-8字符的问题.比如,在MySQL命令行下,如果数据库的编码是UTF-8,那么,在查询数据库的时候,里面的中文都会变成乱码.今天半无意的搜索了一下,结果发 ...

  10. 鸿蒙内核源码分析(寄存器篇) | 小强乃宇宙最忙存储器 | 百篇博客分析OpenHarmony源码 | v38.02

    百篇博客系列篇.本篇为: v38.xx 鸿蒙内核源码分析(寄存器篇) | 小强乃宇宙最忙存储器 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...