以下内容源于朱有鹏课程的学习,如有侵权,请告知删除。

参考资料:http://www.cnblogs.com/biaohc/p/6354068.html

一、S5PV210的中断体系介绍

1、什么是中断?

  • 中断用来解决宏观上的并行需求;
  • 微观上的并行,指的真正的并行,就是精确到每一秒甚至每一刻,多个事情都是在同时进行的。
  • 宏观上的并行,并不等于微观上的并行,有时候宏观上是并行的,微观上是串行的。
  • 单核CPU无法并行,但是通过中断机制,可以实现假并行(宏观上的并行,微观上实际还是串行的)。

2、中断的实现机制——异常向量表

(1)异常向量表是CPU中某些特定地址的特定定义。当中断发生的时候,中断要想办法通知CPU去处理中断,怎么做到?依靠异常向量表。

(2)在CPU设计时,事先定义了CPU中一些特定地址作为特定异常的入口地址

  • 譬如定义0x00000000地址为复位异常向量地址,则发生复位异常时CPU会自动跳转到0x00000000地址去执行指令。
  • 譬如外部中断对应的异常向量地址为0x30000008,则发生外部中断后,CPU会硬件自动跳转到0x30000008地址去执行指令。

(3)以上讲的是CPU硬件设计时对异常向量表的支持,下来就需要软件支持了。

  • 硬件决定发生什么异常时,CPU自动跳转PC到哪个地址去执行;
  • 软件把处理这个异常的代码的首地址填入这个异常向量地址。

3、S5PV210的异常向量表

  • 异常向量表中各个向量的相对位置是固定的,但是起始地址是不固定的,各种SoC可以不一样。
  • 复杂ARM中还可以让用户来软件设置异常向量表的基地址。
  • 所有架构(譬如51单片机、PIC单片机)的CPU的中断,都是通过异常向量表实现的,但是不同CPU异常向量表的构造和位置是不同的。

4、异常和中断的区别和联系

(1)对SoC来说,发生复位、软中断、中断、快速中断、取指令异常、数据异常等,我们都统一叫异常。因此,中断其实是异常的一种。

(2)异常的定义就是突发事件,打断了CPU的正常常规业务,CPU不得不跳转到异常向量表中去执行异常处理程序。

  • 中断是异常的一种,一般特指SoC内的内部外设产生的打断SoC常规业务,或者外部中断(SoC的GPIO引脚传回来的中断)。

二、异常向量表的编程处理

1、像内存一样去访问异常向量表

  • S5PV210的异常向量表可以改变(在CP15协处理器中),以适应操作系统的需求。
  • 但是系统刚启动时,DRAM尚未初始化,程序都在SRAM中运行。210在iRAM中设置了异常向量表,供暂时性使用。
  • 查阅iRAM的地址分配,可知,iRAM中的异常向量表起始地址为0xD0037400。

2、函数名的实质就是函数的首地址

(1)编译器把函数体对应的代码段和这个函数的函数名(实质是符号)对应起来

  • 当使用这个函数名符号时,编译器会将函数的函数体实际上做替换。因为函数体都不止4字节,而函数名这个符号只能对应1个地址,所以实际对应的是函数体那一个代码段的首地址。
  • 拿C语言中的语法来讲,函数名就是这个函数的函数指针。

(2)将异常处理程序的首地址和异常向量表绑定后,异常处理初步阶段就完成了。

到目前可以保证相应异常发生后,硬件自动跳转到对应异常向量表入口去执行时,可以执行到我们事先绑定的函数。

3、为什么中断处理要先在汇编中进行

(1)中断处理,需要保护现场和恢复现场

  • 比如中断从SVC模式来,则保存SVC模式下的必要寄存器的值;
  • 中断处理完成后,准备返回SVC模式前,要将相关寄存器的值恢复回去,否则到了SVC模式后寄存器的值乱了,SVC模式下原来正在进行的常规任务将会被破坏。

(2)保存现场

  • 第一,设置IRQ栈;
  • 第二,保存LR;
  • 第三,保存R0~R12;

(3)为什么要保存LR寄存器?

  • 要考虑中断返回的问题,即中断ISR执行完后如何返回SVC模式下去接着执行原来的代码。
  • 中断返回其实取决于我们进入中断时如何保存现场。
  • 中断返回时关键的2个寄存器就是PC和CPSR。
  • 在进入IRQ模式时,应该将SVC模式下的下一句指令的地址(中断返回地址)和CPSR保存起来,将来恢复时才可以将中断返回地址给PC,将保存的CPSR给CPSR。
  • 中断返回地址保存在LR中,而CPSR(自动)保存在(IRQ模式下的)SPSR中。
  • 见博客http://blog.csdn.net/oqqhutu12345678/article/details/71215628

(3)保护现场关键是保存:中断处理程序的返回地址,r0-r12(cpsr是自动保存的);

(4)恢复现场主要是恢复:r0-r12,pc,cpsr

三、S5PV210的向量中断控制器

1、异常处理的2个阶段

  • 第一个阶段是异常向量表跳转;
  • 第二个阶段是进入真正的异常处理程序。

2、中断处理的第一阶段(异常向量表阶段)处理。

  • 第一个阶段主要依赖CPU设计时提供的异常向量表机制,主要任务是从异常发生到响应异常并且保存/恢复现场、跳转到真正的异常处理程序处。
  • 第二个阶段的目的是识别多个中断源中究竟哪一个发生了中断,然后调用相应的中断处理程序来处理这个中断。

3、S3C2440的第二阶段处理过程

(1)怎么找到具体是哪个中断?

  • S3C2440的中断控制器中有一个寄存器(32位的),寄存器的每一个位对应一个中断源(为了解决支持更多中断源,2440又设计了一个子中断机制。在一级中断寄存器中有一些中断是共用的一个bit位,譬如AC97和WDT。对于共用中断,用子中断来区分究竟是哪一个发生了中断)

(2)怎么找到对应的isr?

  • 首先给每个中断做了个编号,进入isr_handler之后先通过查阅中断源寄存器和子中断寄存器(中哪一位为1)确定中断的编号,然后用这个编号去isr数组(isr数组是中断初始化时事先设定好的,就是把各个中断的isr的函数名组成一个数组,用中断对应的编号作为索引来查询这个数组)中查阅得到isr地址。

评价:第一个过程中使用子中断搞成2级的很麻烦;第二个过程中计算中断编号是个麻烦事,很耗费时间。而中断处理的时间是很宝贵的(系统有一个性能指标,叫实时性。实时性就是中断发生到响应的时间,这个时间越短越好。)

4、S5PV210的第二阶段处理过程

(1)怎么找到具体是哪个中断?

  • S5PV210中因为支持的中断源很多,所以直接设计了4个中断寄存器,每个32位,每位对应一个中断源。
  • 理论上210最多支持128个中断,实际支持不足128个,有些位是空的;见博客http://blog.csdn.net/oqqhutu12345678/article/details/71747613。
  • 210没有子中断寄存器,每个中断源都是并列的。
  • 当中断发生时,在irq_handler中依次去查询4个中断源寄存器,看哪一个中断源寄存器的哪一位被置1,则这个位对应的寄存器就发生了中断,即找到了中断编号。

(2)怎么找到对应的isr?

  • 210中支持的中断源多了很多,如果还使用2440的那一套来寻找isr地址就太慢了,太影响实时性了。于是210开拓了一种全新的寻找isr的机制。210提供了很多寄存器来解决每个中断源对应isr的寻找问题,具体寻找过程和建立过程见下节,实现的效果是当发生相应中断时,硬件会自动的将相应isr推入一定的寄存器中,我们软件只要去这个寄存器中执行函数就行了。

5、总结:第一阶段都相同,第二阶段各不同

  • 第一阶段(异常向量表阶段)2440和210几乎是完全相同的。实际上几乎所有的CPU在第一阶段都是相同的。
  • 第二阶段彼此不同。各个SoC根据自己对实时性的要求,和支持的中断源的多少,发明了各自处理中断,找到中断编号,进一步找到对应isr地址的方式。

四、S5PV210中断处理的主要寄存器

1、VICnINTENABLE、VICnINTENCLEAR

(1)VICnINTENABLE寄存器负责相应的中断的使能,VICnINTENCLEAR寄存器负责相应的中断的禁止。

  • 启用(即当硬件产生中断时CPU能接收的到)某个中断时,需要在此中断编号对应的VICnINTENABLE的相应bit位写1;
  • 禁止某个中断源时,需要向VICnINTENCLEAR中相应的位写1;
  • 有些CPU是中断使能和禁止是一个寄存器位,写1就使能写0就禁止(或者反过来写1就进制写0就使能);
  • 有些CPU是使能和禁止分开为2个寄存器,要使能就写使能寄存器,要禁止就写禁止寄存器。

(2)这里的n=0,1,2,3共四个寄存器,每个寄存器都是32bit,每个bit对应一个中断源是否使能 。

  • VIC0INTENABLE  VIC0INTCLEAR;
  • VIC1INTENABLE  VIC1INTCLEAR
  • VIC2INTENABLE  VIC2INTCLEAR
  • VIC3INTENABLE  VIC3INTCLEAR

(3)例如下面的代码

// 使能中断
// 通过传参的intnum来使能某个具体的中断源,中断号在int.h中定义,是物理中断号
void intc_enable(unsigned long intnum)
{unsigned long temp;// 确定intnum在哪个寄存器的哪一位// <32就是0~31,必然在VIC0if(intnum<32){temp = VIC0INTENABLE;temp |= (1<<intnum);        // 如果是第一种设计则必须位操作,第二种设计可以// 直接写。VIC0INTENABLE = temp;}else if(intnum<64){temp = VIC1INTENABLE;temp |= (1<<(intnum-32));VIC1INTENABLE = temp;}else if(intnum<96){temp = VIC2INTENABLE;temp |= (1<<(intnum-64));VIC2INTENABLE = temp;}else if(intnum<NUM_ALL){temp = VIC3INTENABLE;temp |= (1<<(intnum-96));VIC3INTENABLE = temp;}// NUM_ALL : enable all interruptelse{VIC0INTENABLE = 0xFFFFFFFF;VIC1INTENABLE = 0xFFFFFFFF;VIC2INTENABLE = 0xFFFFFFFF;VIC3INTENABLE = 0xFFFFFFFF;}}
// 禁止中断
// 通过传参的intnum来禁止某个具体的中断源,中断号在int.h中定义,是物理中断号
void intc_disable(unsigned long intnum)
{unsigned long temp;if(intnum<32){temp = VIC0INTENCLEAR;temp |= (1<<intnum);VIC0INTENCLEAR = temp;}else if(intnum<64){temp = VIC1INTENCLEAR;temp |= (1<<(intnum-32));VIC1INTENCLEAR = temp;}else if(intnum<96){temp = VIC2INTENCLEAR;temp |= (1<<(intnum-64));VIC2INTENCLEAR = temp;}else if(intnum<NUM_ALL){temp = VIC3INTENCLEAR;temp |= (1<<(intnum-96));VIC3INTENCLEAR = temp;}// NUM_ALL : disable all interruptelse{VIC0INTENCLEAR = 0xFFFFFFFF;VIC1INTENCLEAR = 0xFFFFFFFF;VIC2INTENCLEAR = 0xFFFFFFFF;VIC3INTENCLEAR = 0xFFFFFFFF;}return;
}

2、VICnINTSELECT寄存器

(1)设置各个中断的模式为irq还是fiq。一般都设置成irq。

(2)IRQ和FIQ究竟有何区别?

  • 210中支持2种中断,irq和fiq。irq是普通中断,fiq是快速中断。
  • 快速中断提供一种更快响应处理的中断通道,用于对实时性要求很高的中断源。
  • fiq在CPU设计时预先提供了一些机制保证fiq可以被快速处理,从而保证实时性。
  • fiq的限制就是只能有一个中断源被设置为fiq,其他都是irq。

(3)CPU如何保证fiq比irq快?

  • 第一,fiq模式有专用的r8~r12,因此在fiq的isr中可以直接使用r8-r12而不用保存,这就能节省时间;
  • 第二,异常向量表中fiq是最后一个异常向量入口。因此fiq的isr不需要跳转,可以直接写在原地,这样就比其他异常少跳转一次,省了些时间。

(4)这里的n=0,1,2,3共四个寄存器,可以设置各个中断的模式。

  • VIC0INTSELECT,VIC1INTSELECT,VIC2INTSELECT,VIC3INTSELECT。

(5)代码

// 清除需要处理的中断的中断处理函数的地址
void intc_clearvectaddr(void)
{// VICxADDR:当前正在处理的中断的中断处理函数的地址(即isr的入口地址,见3的代码示例)VIC0ADDR = 0;VIC1ADDR = 0;VIC2ADDR = 0;VIC3ADDR = 0;
}// 初始化中断控制器
void intc_init(void)
{// 禁止所有中断// 为什么在中断初始化之初要禁止所有中断?// 因为中断一旦打开,因为外部或者硬件自己的原因产生中断后一定就会寻找isr// 而我们可能认为自己用不到这个中断就没有提供isr,这时它自动拿到的就是乱码// 则程序很可能跑飞,所以不用的中断一定要关掉。// 一般的做法是先全部关掉,然后再逐一打开自己感兴趣的中断。一旦打开就必须// 给这个中断提供相应的isr并绑定好。VIC0INTENCLEAR = 0xffffffff;VIC1INTENCLEAR = 0xffffffff;VIC2INTENCLEAR = 0xffffffff;VIC3INTENCLEAR = 0xffffffff;// 这里把所有的中断源的中断类型设置为IRQ;其实可以具体到设置每一个中断的中断模式 VIC0INTSELECT = 0x0;VIC1INTSELECT = 0x0;VIC2INTSELECT = 0x0;VIC3INTSELECT = 0x0;// 清VICxADDRintc_clearvectaddr();
}

3、VICnIRQSTATUS、VICnFIQSTATUS

(1)中断状态寄存器
  • 该寄存器是只读的。
  • 当发生了中断时,硬件会自动将该寄存器的对应位置为1,表示中断发生了。
  • 软件在处理中断第二阶段的第一阶段,就是靠查询这个寄存器来得到中断编号的。
  • 有四个:VIC0IRQSTATUS,VIC1IRQSTATUS,VIC2IRQSTATUS,VIC3IRQSTATUS。

(2)代码示例

// 通过读取VICnIRQSTATUS寄存器,判断其中哪个有一位为1,来得知哪个VIC发生中断了
unsigned long intc_getvicirqstatus(unsigned long ucontroller)
{if(ucontroller == 0)return   VIC0IRQSTATUS;else if(ucontroller == 1)return     VIC1IRQSTATUS;else if(ucontroller == 2)return     VIC2IRQSTATUS;else if(ucontroller == 3)return     VIC3IRQSTATUS;else{}return 0;
}// 真正的中断处理程序。意思就是说这里只考虑中断处理,不考虑保护/恢复现场
void irq_handler(void)
{//printf("irq_handler.\n");// SoC支持很多个(在低端CPU例如2440中有30多个,在210中有100多个)中断// 这么多中断irq在第一个阶段走的是一条路,都会进入到irq_handler来// 我们在irq_handler中要去区分究竟是哪个中断发生了,然后再去调用该中断// 对应的isr。// 虽然硬件已经自动帮我们把isr放入了VICnADDRESS中,但是因为有4个,所以我们必须// 先去软件的检查出来到底哪个VIC中断了,也就是说isr到底在哪个VICnADDRESS寄存器中unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};int i=0;void (*isr)(void) = NULL;for(i=0; i<4; i++){// 发生一个中断时,4个VIC中有3个是全0,1个的其中一位不是0if(intc_getvicirqstatus(i) != 0){isr = (void (*)(void)) vicaddr[i];break;}}(*isr)();      // 通过函数指针来调用函数
}

4、VICnVECTPRIORITY0~VICnVECTPRIORITY31寄存器

  • 中断优先级设置寄存器,设置多个中断同时发生时先处理谁后处理谁的问题。
  • 一般来说高优先级的中断可以打断低优先级的中断,从而嵌套处理中断。

5、VICnVECTADDR0~VICnVECTADDR31寄存器

(1)这些寄存器和210中断处理第二阶段的第二阶段有关。

(2)VICnVECTADDR0~31这32个寄存器。

  • 分别用来存放真正的各个中断对应的isr的函数地址。即中断处理函数的存放地址。
  • 相当于每一个中断源都有一个VECTADDR寄存器,程序员在设置中断的时候,把这个中断的isr地址直接放入这个中断对应的VECTADDR寄存器即可。
  • n=0,1,2,3;
(3)代码
// 绑定我们写的isr到VICnVECTADDR寄存器
// 绑定过之后我们就把isr地址交给硬件了,剩下的我们不用管了,硬件自己会处理
// 等发生相应中断的时候,我们直接到相应的VICnADDR中去取isr地址即可。
// 参数:intnum是int.h定义的物理中断号,handler是函数指针,就是我们写的isr// VIC0VECTADDR定义为VIC0VECTADDR0寄存器的地址,就相当于是VIC0VECTADDR0~31这个
// 数组(这个数组就是一个函数指针数组)的首地址,然后具体计算每一个中断的时候
// 只需要首地址+偏移量即可。
void intc_setvectaddr(unsigned long intnum, void (*handler)(void))
{//VIC0if(intnum<32){*( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) = (unsigned)handler;}//VIC1else if(intnum<64){*( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler;}//VIC2else if(intnum<96){*( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler;}//VIC3else{*( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler;}return;
}

6、VICnADDRESS寄存器

  • 内容由硬件自动设置。当发生中断时,硬件自动识别中断编号,并且自动找到这个中断的VECTADDR寄存器,把寄存器的内容复制到VICADDRESS中,以供使用。
  • 这样的设计避免了软件查找中断源和isr,节省了时间,提高了210的中断响应速度。
// 真正的中断处理程序。意思就是说这里只考虑中断处理,不考虑保护/恢复现场
void irq_handler(void)
{//printf("irq_handler.\n");// SoC支持很多个(在低端CPU例如2440中有30多个,在210中有100多个)中断// 这么多中断irq在第一个阶段走的是一条路,都会进入到irq_handler来// 我们在irq_handler中要去区分究竟是哪个中断发生了,然后再去调用该中断// 对应的isr。(函数指针)// 虽然硬件已经自动帮我们把isr放入了VICnADDR中,但是因为有4个,所以我们必须// 先去软件的检查出来到底哪个VIC中断了,也就是说isr到底在哪个VICADDR寄存器中unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};int i=0;void (*isr)(void) = NULL;for(i=0; i<4; i++){// 发生一个中断时,4个VIC中有3个是全0,1个的其中一位不是0if(intc_getvicirqstatus(i) != 0){isr = (void (*)(void)) vicaddr[i];break;}}(*isr)();       // 通过函数指针来调用函数
}

五、总结

整个中断过程可以分为两部分
第一部分是我们为中断响应而做的预备工作:
  • 初始化中断控制器
  • 绑定写好的isr到中断控制器
  • 相应中断的所有条件使能
第二部分是当硬件产生中断后如何自动执行isr:
  • 第一步,经过异常向量表跳转入IRQ/FIQ的入口(IRQ_handl:)
  • 第二步,做中断现场保护(在start.S中),然后跳入isr_handler
  • 第三步,在isr_handler中先去搞清楚是哪个VIC中断了,然后直接去这个VIC的ADDR寄存器中取isr来执行即可
  • 第四步,isr执行完,中断现场恢复,直接返回继续做常规任务。

s5pv210——中断系统相关介绍相关推荐

  1. STC89C52单片机定时器及中断系统的介绍以及代码示例

    目录 一,定时器介绍 二,STC89C52定时器资源 三,定时器框图 四,定时器工作模式 五,中断系统及其流程 六,STC89C52中断资源 七,定时器与中断系统 八,相关寄存器的认识与配置 首先是定 ...

  2. Linux 系统相关介绍

    Linux 系统相关说明 Linux 内核版本 没有图形化页面 稳定版 : 面向普通用户 开发版 : 面向开发人员 Linux 发行版 在内核版本的基础上, 增加比如桌面/音乐播放器等应用软件的系统 ...

  3. 【DSP开发】C6000非多核非KeyStone系列DSP中断系统

    C6000系列DSP的中断系统 上一篇介绍了C6455的GPIO系统,最后把GPIO4配置成了中断/事件模式,本文将介绍C6455的中断系统,介绍完基本概念后,给出把GPIO4映射到INT4的代码. ...

  4. S5PV210中断的介绍与配置

    一.介绍 1.三星S5PV210中断体系介绍 异常向量表(矢量中断控制器) 异常向量表是CPU中某些特定地址的特定定义,当中断发生的时候,中断要通知CPU处理中断,在CPU设计时,定义了CPU中一些特 ...

  5. Linux 系统相关目录介绍

    Linux 总目录篇 Linux 系统相关目录介绍 /bin 用来贮存用户命令./usr/bin 也被用来贮存用户命令. /sbin 许多系统命令(例如 shutdown)的贮存位置./usr/sbi ...

  6. Linux学习笔记之——Linux系统内部相关介绍

    Linux学习笔记之--Linux系统内部相关介绍 摘要:主要记录一些比较有用的能够帮助理解和使用Linux的知识.比如一些相关概念.没兴趣的看看就好.知道有这么个东西.注意事项.和一些常用目录的作用 ...

  7. 工业视觉系统相关知识和选型介绍(一):相机篇

    工业视觉系统相关知识和选型介绍(一):相机篇 一.工业视觉系统 二.工业相机 三.关键名词解释 四.工业相机选型 五.工业相机品牌 六.相机选型实例 一.工业视觉系统 1.机器视觉就是用机器代替人眼来 ...

  8. 工业视觉系统相关知识和选型介绍(二):镜头篇

    工业视觉系统相关知识和选型介绍(二):镜头篇 一.工业镜头介绍 二. 关键名词解释 二.图解镜头基本参数 三.镜头选型 四.常用工业镜头品牌 一.工业镜头介绍 镜头的基本功能就是实现光束变换(调制), ...

  9. 中断系统的相关知识(二)(可位寻址、不可位寻址)

    中断系统中会涉及许多寄存器,这个时候我们会遇到一些有点陌生的名词,比如接下来说的"可位寻址"和"不可位寻址",如下图:  位寻址是指对一些内部数据存储器RAM和 ...

最新文章

  1. 时间序列预测实例(prophet的血泪史)
  2. C++中引用与指针的区别(详细介绍)
  3. 【EOJ Monthly 2018.10 - A】oxx 的小姐姐们(模拟,水题,填充矩阵,输出格式有坑)
  4. OpenCV中的「透视变换 / 投影变换 / 单应性」—cv.warpPerspective、cv.findHomography
  5. GitLab 发布安全修复版本:11.9.4, 11.8.6 和 11.7.10
  6. 学python电脑硬件_2020 python学习第一天————编程语言及计算机介绍
  7. 小白如何购买阿里云服务器(2019最详细教程)
  8. python批量删除文件名_用python批量删掉文件名中共同存在的字符
  9. kx驱动中的DSP设置
  10. 有趣好玩恶搞的C语言程序,有趣搞笑的整人VBS小脚本(整菜鸟专用)
  11. 激励视频广告 Android,激励视频广告
  12. fabs linux头文件,C/C++ algorithm头文件的max()、min()、abs()和math头文件的fabs(
  13. html文件如何创建文件,怎么创建html文件
  14. 电脑怎么搜索重复文件?如何快速找到重复文件?
  15. 我的世界服务器物品栏太小咋办,《我的世界》MC背包容量太小?这四个装东西的道具了解一下!...
  16. element-ui dialog(多弹框、嵌套弹框)被蒙版遮住
  17. linux文件权限前面的r是什么意思,文件权限:普通(r、w、x)和特殊(s、t)
  18. Dom型xss的理解和反射型xss实战操作
  19. 第四课 实战go语言改造php仿优酷-Redis改造优化接口
  20. mysql+excel:数据分析----销售情况分析仪表盘

热门文章

  1. 一文告诉你 Event Loop 是什么?
  2. 数据特征分析:2.对比分析
  3. Flask WTForm表单的使用
  4. [译]git revert
  5. 神马是线程?PHP对其具体的应用?应用在哪里?
  6. 大家狂欢吧,我的Google帐号悲剧了
  7. 进击的二维码 | ArcBlock 课堂预告
  8. vue学习:7、路由跳转
  9. windows 下安装rabbitmq
  10. CSDN博客投票活动开始了