鸿蒙内核源码分析(中断管理篇) | 硬中断的实现<>观察者模式

开发
文章由鸿蒙社区产出,想要了解更多内容请前往:51CTO和华为官方战略合作共建的鸿蒙技术社区https://harmonyos.51cto.com

[[390523]]

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 关于中断部分系列篇将用三篇详细说明整个过程.

● 中断概念篇 中断概念很多,比如中断控制器,中断源,中断向量,中断共享,中断处理程序等等.本篇做一次整理.先了解透概念才好理解中断过程.用海公公打比方说明白中断各个概念.可前往鸿蒙内核源码分析查看.

● 中断管理篇(本篇) 从中断初始化HalIrqInit开始,到注册中断的LOS_HwiCreate函数,到消费中断函数的 HalIrqHandler,剖析鸿蒙内核实现中断的过程,很像设计模式中的观察者模式.

● 中断切换篇 用自下而上的方式,从中断源头纯汇编代码往上跟踪代码细节.说清楚保存和恢复中断现场TaskIrqContext过程.可前往鸿蒙内核源码分析查看.

编译开关

系列篇编译平台为 hi3516dv300,整个工程可前往查看. 预编译处理过程会自动生成编译开关 menuconfig.h ,供编译阶段选择编译,可前往查看.

  1. //.... 
  2. #define LOSCFG_ARCH_ARM_VER "armv7-a" 
  3. #define LOSCFG_ARCH_CPU "cortex-a7" 
  4. #define LOSCFG_PLATFORM "hi3516dv300" 
  5. #define LOSCFG_PLATFORM_BSP_GIC_V2 1 
  6. #define LOSCFG_PLATFORM_ROOTFS 1 
  7. #define LOSCFG_KERNEL_CPPSUPPORT 1 
  8. #define LOSCFG_HW_RANDOM_ENABLE 1 
  9. #define LOSCFG_ARCH_CORTEX_A7 1 
  10. #define LOSCFG_DRIVERS_HDF_PLATFORM_RTC 1 
  11. #define LOSCFG_DRIVERS_HDF_PLATFORM_UART 1 

中断初始化

hi3516dv300 中断控制器选择了 LOSCFG_PLATFORM_BSP_GIC_V2 ,对应代码为 gic_v2.c GIC(Generic Interrupt Controller)是ARM公司提供的一个通用的中断控制器. 看这种代码因为涉及硬件部分,需要对照ARM中断控制器 gic_v2.pdf文档看.可前往地址查看.

  1. //硬件中断初始化 
  2. VOID HalIrqInit(VOID) 
  3.     UINT32 i; 
  4.  
  5.     /* set externel interrupts to be level triggered, active low. */    //将外部中断设置为电平触发,低电平激活 
  6.     for (i = 32; i < OS_HWI_MAX_NUM; i += 16) { 
  7.         GIC_REG_32(GICD_ICFGR(i / 16)) = 0; 
  8.     } 
  9.  
  10.     /* set externel interrupts to CPU 0 */  //将外部中断设置为CPU 0 
  11.     for (i = 32; i < OS_HWI_MAX_NUM; i += 4) { 
  12.         GIC_REG_32(GICD_ITARGETSR(i / 4)) = 0x01010101; 
  13.     } 
  14.  
  15.     /* set priority on all interrupts */    //设置所有中断的优先级 
  16.     for (i = 0; i < OS_HWI_MAX_NUM; i += 4) { 
  17.         GIC_REG_32(GICD_IPRIORITYR(i / 4)) = GICD_INT_DEF_PRI_X4; 
  18.     } 
  19.  
  20.     /* disable all interrupts. */           //禁用所有中断。 
  21.     for (i = 0; i < OS_HWI_MAX_NUM; i += 32) { 
  22.         GIC_REG_32(GICD_ICENABLER(i / 32)) = ~0; 
  23.     } 
  24.  
  25.     HalIrqInitPercpu();//初始化当前CPU中断信息 
  26.  
  27.     /* enable gic distributor control */ 
  28.     GIC_REG_32(GICD_CTLR) = 1; //使能分发中断寄存器,该寄存器作用是允许给CPU发送中断信号 
  29.  
  30. #if (LOSCFG_KERNEL_SMP == YES) 
  31.     /* register inter-processor interrupt *///注册核间中断,啥意思?就是CPU各核直接可以发送中断信号 
  32.     //处理器间中断允许一个CPU向系统其他的CPU发送中断信号,处理器间中断(IPI)不是通过IRQ线传输的,而是作为信号直接放在连接所有CPU本地APIC的总线上。 
  33.     LOS_HwiCreate(LOS_MP_IPI_WAKEUP, 0xa0, 0, OsMpWakeHandler, 0);//注册唤醒CPU的中断处理函数 
  34.     LOS_HwiCreate(LOS_MP_IPI_SCHEDULE, 0xa0, 0, OsMpScheduleHandler, 0);//注册调度CPU的中断处理函数 
  35.     LOS_HwiCreate(LOS_MP_IPI_HALT, 0xa0, 0, OsMpScheduleHandler, 0);//注册停止CPU的中断处理函数 
  36. #endif 
  37. //给每个CPU core初始化硬件中断 
  38. VOID HalIrqInitPercpu(VOID) 
  39.     /* unmask interrupts */ //取消中断屏蔽 
  40.     GIC_REG_32(GICC_PMR) = 0xFF; 
  41.  
  42.     /* enable gic cpu interface */  //启用gic cpu接口 
  43.     GIC_REG_32(GICC_CTLR) = 1; 

解读

● 上来四个循环,是对中断控制器寄存器组的初始化,也就是驱动程序,驱动程序是配置硬件寄存器的过程.寄存器分通用和专用寄存器.图为 gic_v2 的寄存器功能 ,这里对照代码和datasheet重点说下中断配置寄存器

● 以下是GICD_ICFGRn的介绍

  • The GICD_ICFGRs provide a 2-bit Int_config field for each interrupt supported by the GIC. This field identifies whether the corresponding interrupt is edge-triggered or level-sensitive
  • GICD_ICFGRs为GIC支持的每个中断提供一个2位配置字段。此字段标识相应的中断是边缘触发的还是电平触发的

● GIC-v2支持三种类型的中断

◊ PPI:私有外设中断(Private Peripheral Interrupt),是每个CPU私有的中断。最多支持16个PPI中断,硬件中断号从ID16~ID31。PPI通常会送达到指定的CPU上,应用场景有CPU本地时钟。

◊ SPI:公用外设中断(Shared Peripheral Interrupt),最多可以支持988个外设中断,硬件中断号从ID32~ID1019。

◊ SGI:软件触发中断(Software Generated Interrupt)通常用于多核间通讯,最多支持16个SGI中断,硬件中断号从ID0~ID15。SGI通常在内核中被用作 IPI 中断(inter-processor interrupts),并会送达到系统指定的CPU上,函数的最后就注册了三个核间中断的函数.

  1. typedef enum {//核间中断 
  2.     LOS_MP_IPI_WAKEUP,  //唤醒CPU 
  3.     LOS_MP_IPI_SCHEDULE,//调度CPU 
  4.     LOS_MP_IPI_HALT,    //停止CPU 
  5. } MP_IPI_TYPE; 

中断相关的结构体

  1. size_t g_intCount[LOSCFG_KERNEL_CORE_NUM] = {0};//记录每个CPUcore的中断数量  
  2. HwiHandleForm g_hwiForm[OS_HWI_MAX_NUM];//中断注册表 @note_why 用 form 来表示?有种写 HTML的感觉 :P 
  3. STATIC CHAR *g_hwiFormName[OS_HWI_MAX_NUM] = {0};//记录每个硬中断的名称  
  4. STATIC UINT32 g_hwiFormCnt[OS_HWI_MAX_NUM] = {0};//记录每个硬中断的总数量 
  5. STATIC UINT32 g_curIrqNum = 0; //记录当前中断号 
  6. typedef VOID (*HWI_PROC_FUNC)(VOID); //中断函数指针 
  7. typedef struct tagHwiHandleForm {    
  8.     HWI_PROC_FUNC pfnHook;  //中断处理函数 
  9.     HWI_ARG_T uwParam;      //中断处理函数参数 
  10.     struct tagHwiHandleForm *pstNext;   //节点,指向下一个中断,用于共享中断的情况 
  11. } HwiHandleForm; 
  12.  
  13. typedef struct tagIrqParam {    //中断参数 
  14.     int swIrq;      //  软件中断 
  15.     VOID *pDevId;   //  设备ID 
  16.     const CHAR *pName;  //名称 
  17. } HwiIrqParam; 

注册硬中断

  1. /****************************************************************************** 
  2.  创建一个硬中断 
  3.  中断创建,注册中断号、中断触发模式、中断优先级、中断处理程序。中断被触发时, 
  4.  handleIrq会调用该中断处理程序 
  5. ******************************************************************************/ 
  6. LITE_OS_SEC_TEXT_INIT UINT32 LOS_HwiCreate(HWI_HANDLE_T hwiNum, //硬中断句柄编号 默认范围[0-127] 
  7.                                            HWI_PRIOR_T hwiPrio,     //硬中断优先级     
  8.                                            HWI_MODE_T hwiMode,      //硬中断模式 共享和非共享 
  9.                                            HWI_PROC_FUNC hwiHandler,//硬中断处理函数 
  10.                                            HwiIrqParam *irqParam)   //硬中断处理函数参数 
  11.     UINT32 ret; 
  12.  
  13.     (VOID)hwiPrio; 
  14.     if (hwiHandler == NULL) {//中断处理函数不能为NULL 
  15.         return OS_ERRNO_HWI_PROC_FUNC_NULL; 
  16.     } 
  17.     if ((hwiNum > OS_USER_HWI_MAX) || ((INT32)hwiNum < OS_USER_HWI_MIN)) {//中断数区间限制 [32,96] 
  18.         return OS_ERRNO_HWI_NUM_INVALID; 
  19.     } 
  20.  
  21. #ifdef LOSCFG_NO_SHARED_IRQ //不支持共享中断 
  22.     ret = OsHwiCreateNoShared(hwiNum, hwiMode, hwiHandler, irqParam); 
  23. #else 
  24.     ret = OsHwiCreateShared(hwiNum, hwiMode, hwiHandler, irqParam); 
  25. #endif 
  26.     return ret; 
  27. //创建一个共享硬件中断,共享中断就是一个中断能触发多个响应函数 
  28. STATIC UINT32 OsHwiCreateShared(HWI_HANDLE_T hwiNum, HWI_MODE_T hwiMode, 
  29.                                 HWI_PROC_FUNC hwiHandler, const HwiIrqParam *irqParam) 
  30.     UINT32 intSave; 
  31.     HwiHandleForm *hwiFormNode = NULL
  32.     HwiHandleForm *hwiForm = NULL
  33.     HwiIrqParam *hwiParam = NULL
  34.     HWI_MODE_T modeResult = hwiMode & IRQF_SHARED; 
  35.  
  36.     if (modeResult && ((irqParam == NULL) || (irqParam->pDevId == NULL))) { 
  37.         return OS_ERRNO_HWI_SHARED_ERROR; 
  38.     } 
  39.  
  40.     HWI_LOCK(intSave);//中断自旋锁 
  41.  
  42.     hwiForm = &g_hwiForm[hwiNum];//获取中断处理项 
  43.     if ((hwiForm->pstNext != NULL) && ((modeResult == 0) || (!(hwiForm->uwParam & IRQF_SHARED)))) { 
  44.         HWI_UNLOCK(intSave); 
  45.         return OS_ERRNO_HWI_SHARED_ERROR; 
  46.     } 
  47.  
  48.     while (hwiForm->pstNext != NULL) {//pstNext指向 共享中断的各处理函数节点,此处一直撸到最后一个 
  49.         hwiForm = hwiForm->pstNext;//找下一个中断 
  50.         hwiParam = (HwiIrqParam *)(hwiForm->uwParam);//获取中断参数,用于检测该设备ID是否已经有中断处理函数 
  51.         if (hwiParam->pDevId == irqParam->pDevId) {//设备ID一致时,说明设备对应的中断处理函数已经存在了. 
  52.             HWI_UNLOCK(intSave); 
  53.             return OS_ERRNO_HWI_ALREADY_CREATED; 
  54.         } 
  55.     } 
  56.  
  57.     hwiFormNode = (HwiHandleForm *)LOS_MemAlloc(m_aucSysMem0, sizeof(HwiHandleForm));//创建一个中断处理节点 
  58.     if (hwiFormNode == NULL) { 
  59.         HWI_UNLOCK(intSave); 
  60.         return OS_ERRNO_HWI_NO_MEMORY; 
  61.     } 
  62.  
  63.     hwiFormNode->uwParam = OsHwiCpIrqParam(irqParam);//获取中断处理函数的参数 
  64.     if (hwiFormNode->uwParam == LOS_NOK) { 
  65.         HWI_UNLOCK(intSave); 
  66.         (VOID)LOS_MemFree(m_aucSysMem0, hwiFormNode); 
  67.         return OS_ERRNO_HWI_NO_MEMORY; 
  68.     } 
  69.  
  70.     hwiFormNode->pfnHook = hwiHandler;//绑定中断处理函数 
  71.     hwiFormNode->pstNext = (struct tagHwiHandleForm *)NULL;//指定下一个中断为NULL,用于后续遍历找到最后一个中断项(见于以上 while (hwiForm->pstNext != NULL)处) 
  72.     hwiForm->pstNext = hwiFormNode;//共享中断 
  73.  
  74.     if ((irqParam != NULL) && (irqParam->pName != NULL)) { 
  75.         g_hwiFormName[hwiNum] = (CHAR *)irqParam->pName; 
  76.     } 
  77.  
  78.     g_hwiForm[hwiNum].uwParam = modeResult; 
  79.  
  80.     HWI_UNLOCK(intSave); 
  81.     return LOS_OK; 

解读

● 内核将硬中断进行编号,如:

  1. #define NUM_HAL_INTERRUPT_TIMER0        33 
  2. #define NUM_HAL_INTERRUPT_TIMER1        33 
  3. #define NUM_HAL_INTERRUPT_TIMER2        34 
  4. #define NUM_HAL_INTERRUPT_TIMER3        34 
  5. #define NUM_HAL_INTERRUPT_TIMER4        35 
  6. #define NUM_HAL_INTERRUPT_TIMER5        35 
  7. #define NUM_HAL_INTERRUPT_TIMER6        36 
  8. #define NUM_HAL_INTERRUPT_TIMER7        36 
  9. #define NUM_HAL_INTERRUPT_DMAC          60 
  10. #define NUM_HAL_INTERRUPT_UART0         38 
  11. #define NUM_HAL_INTERRUPT_UART1         39 
  12. #define NUM_HAL_INTERRUPT_UART2         40 
  13. #define NUM_HAL_INTERRUPT_UART3         41 
  14. #define NUM_HAL_INTERRUPT_UART4         42 
  15. #define NUM_HAL_INTERRUPT_TIMER         NUM_HAL_INTERRUPT_TIMER4 

例如:时钟节拍处理函数 OsTickHandler 就是在 HalClockInit中注册的

  1. //硬时钟初始化 
  2. VOID HalClockInit(VOID) 
  3.   // ... 
  4.   (void)LOS_HwiCreate(NUM_HAL_INTERRUPT_TIMER, 0xa0, 0, OsTickHandler, 0);//注册OsTickHandler到中断向量表  
  5. //节拍中断处理函数 ,鸿蒙默认10ms触发一次 
  6.   LITE_OS_SEC_TEXT VOID OsTickHandler(VOID) 
  7.   { 
  8.       UINT32 intSave; 
  9.       TICK_LOCK(intSave);//tick自旋锁 
  10.       g_tickCount[ArchCurrCpuid()]++;// 累加当前CPU核tick数 
  11.       TICK_UNLOCK(intSave); 
  12.       OsTimesliceCheck();//时间片检查 
  13.       OsTaskScan(); /* task timeout scan *///扫描超时任务 例如:delay(300) 
  14.       #if (LOSCFG_BASE_CORE_SWTMR == YES) 
  15.           OsSwtmrScan();//扫描定时器,查看是否有超时定时器,加入队列 
  16.       #endif 
  17.   }  

● 鸿蒙是支持中断共享的,在OsHwiCreateShared中,将函数注册到g_hwiForm中.中断向量完成注册后,就是如何触发和回调的错误.触发在 鸿蒙内核源码分析中断切换篇中已经讲清楚,触发是从底层汇编向上调用,调用的C函数就是HalIrqHandler

中断怎么触发的?

分两种情况:

● 通过硬件触发,比如按键,USB的插拔这些中断源向中断控制器发送电信号(高低电平触发或是上升/下降沿触发),中断控制器经过过滤后将信号发给对应的CPU处理,通过硬件改变PC和CPSR寄存值,直接跳转到中断向量(固定地址)执行.

  1. b   reset_vector            @开机代码 
  2.  b   _osExceptUndefInstrHdl     @异常处理之CPU碰到不认识的指令 
  3.  b   _osExceptSwiHdl            @异常处理之:软中断 
  4.  b   _osExceptPrefetchAbortHdl  @异常处理之:取指异常 
  5.  b   _osExceptDataAbortHdl      @异常处理之:数据异常 
  6.  b   _osExceptAddrAbortHdl      @异常处理之:地址异常 
  7.  b   OsIrqHandler               @异常处理之:硬中断 
  8.  b   _osExceptFiqHdl                @异常处理之:快中断 

● 通过软件触发,常见于核间中断的情况, 核间中断指的是几个CPU之间相互通讯的过程.以下为某一个CPU向其他CPU(可以是多个)发起让这些CPU重新调度LOS_MpSchedule的中断请求信号.最终是写了中断控制器的GICD_SGIR寄存器,这是一个由软件触发中断的寄存器.中断控制器会将请求分发给对应的CPU处理中断,即触发了OsIrqHandler.

  1. //给参数CPU发送调度信号 
  2.   VOID LOS_MpSchedule(UINT32 target)//target每位对应CPU core  
  3.   { 
  4.       UINT32 cpuid = ArchCurrCpuid(); 
  5.       target &= ~(1U << cpuid);//获取除了自身之外的其他CPU 
  6.       HalIrqSendIpi(target, LOS_MP_IPI_SCHEDULE);//向CPU发送调度信号,核间中断(Inter-Processor Interrupts),IPI 
  7.   } 
  8.   //SGI软件触发中断(Software Generated Interrupt)通常用于多核间通讯 
  9.   STATIC VOID GicWriteSgi(UINT32 vector, UINT32 cpuMask, UINT32 filter) 
  10.   { 
  11.       UINT32 val = ((filter & 0x3) << 24) | ((cpuMask & 0xFF) << 16) | 
  12.                   (vector & 0xF); 
  13.  
  14.       GIC_REG_32(GICD_SGIR) = val;//写SGI寄存器 
  15.   } 
  16.   //向指定核发送核间中断 
  17.   VOID HalIrqSendIpi(UINT32 target, UINT32 ipi) 
  18.   { 
  19.       GicWriteSgi(ipi, target, 0); 
  20.   } 

中断统一处理入口函数 HalIrqHandler

  1. //硬中断统一处理函数,这里由硬件触发,调用见于 ..\arch\arm\arm\src\los_dispatch.S 
  2. VOID HalIrqHandler(VOID) 
  3.     UINT32 iar = GIC_REG_32(GICC_IAR);//从中断确认寄存器获取中断ID号 
  4.     UINT32 vector = iar & 0x3FFU;//计算中断向量号 
  5.     /* 
  6.      * invalid irq number, mainly the spurious interrupts 0x3ff, 
  7.      * gicv2 valid irq ranges from 0~1019, we use OS_HWI_MAX_NUM 
  8.      * to do the checking. 
  9.      */ 
  10.     if (vector >= OS_HWI_MAX_NUM) { 
  11.         return
  12.     } 
  13.     g_curIrqNum = vector;//记录当前中断ID号 
  14.     OsInterrupt(vector);//调用上层中断处理函数 
  15.     /* use orignal iar to do the EOI */ 
  16.     GIC_REG_32(GICC_EOIR) = iar;//更新中断结束寄存器 
  17. VOID OsInterrupt(UINT32 intNum)//中断实际处理函数 
  18.     HwiHandleForm *hwiForm = NULL
  19.     UINT32 *intCnt = NULL
  20.  
  21.     intCnt = &g_intCount[ArchCurrCpuid()];//当前CPU的中断总数量 ++ 
  22.     *intCnt = *intCnt + 1;//@note_why 这里没看明白为什么要 +1 
  23.  
  24. #ifdef LOSCFG_CPUP_INCLUDE_IRQ //开启查询系统CPU的占用率的中断 
  25.     OsCpupIrqStart();//记录本次中断处理开始时间 
  26. #endif 
  27.  
  28. #ifdef LOSCFG_KERNEL_TICKLESS 
  29.     OsTicklessUpdate(intNum); 
  30. #endif 
  31.     hwiForm = (&g_hwiForm[intNum]);//获取对应中断的实体 
  32. #ifndef LOSCFG_NO_SHARED_IRQ    //如果没有定义不共享中断 ,意思就是如果是共享中断 
  33.     while (hwiForm->pstNext != NULL) { //一直撸到最后 
  34.         hwiForm = hwiForm->pstNext;//下一个继续撸 
  35. #endif 
  36.         if (hwiForm->uwParam) {//有参数的情况 
  37.             HWI_PROC_FUNC2 func = (HWI_PROC_FUNC2)hwiForm->pfnHook;//获取回调函数 
  38.             if (func != NULL) { 
  39.                 UINTPTR *param = (UINTPTR *)(hwiForm->uwParam); 
  40.                 func((INT32)(*param), (VOID *)(*(param + 1)));//运行带参数的回调函数 
  41.             } 
  42.         } else {//木有参数的情况 
  43.             HWI_PROC_FUNC0 func = (HWI_PROC_FUNC0)hwiForm->pfnHook;//获取回调函数 
  44.             if (func != NULL) { 
  45.                 func();//运行回调函数 
  46.             } 
  47.         } 
  48. #ifndef LOSCFG_NO_SHARED_IRQ 
  49.     } 
  50. #endif 
  51.     ++g_hwiFormCnt[intNum];//中断号计数器总数累加 
  52.  
  53.     *intCnt = *intCnt - 1;  //@note_why 这里没看明白为什么要 -1  
  54. #ifdef LOSCFG_CPUP_INCLUDE_IRQ  //开启查询系统CPU的占用率的中断 
  55.     OsCpupIrqEnd(intNum);//记录中断处理时间完成时间 
  56. #endif 

解读 统一中断处理函数是一个通过一个中断号去找到注册函数的过程,分四步走:

● 第一步:取号,这号是由中断控制器的 GICC_IAR寄存器提供的,这是一个专门保存当前中断号的寄存器.

● 第二步:从注册表g_hwiForm中查询注册函数,同时取出参数.

● 第三步:执行函数,也就是回调注册函数,分有参和无参两种情况 func(...),在中断共享的情况,注册函数会指向之后的注册函数pstNext,依次执行回调函数,这是中断共享的实现细节.

  1. typedef struct tagHwiHandleForm {    
  2.       HWI_PROC_FUNC pfnHook;    //中断处理函数 
  3.       HWI_ARG_T uwParam;        //中断处理函数参数 
  4.       struct tagHwiHandleForm *pstNext; //节点,指向下一个中断,用于共享中断的情况 
  5. } HwiHandleForm; 

● 第四步:销号,本次中断完成了就需要消除记录,中断控制器也有专门的销号寄存器GICC_EOIR

● 另外的是一些统一数据,每次中断号处理内核都会记录次数,和耗时,以便定位/跟踪/诊断错误.

参与贡献

访问注解仓库地址

Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request

● 新建 Issue

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2021-03-24 17:18:41

鸿蒙HarmonyOS应用开发

2020-10-26 08:45:39

观察者模式

2021-09-06 10:04:47

观察者模式应用

2021-04-01 09:38:02

鸿蒙HarmonyOS应用

2022-01-29 22:12:35

前端模式观察者

2013-11-26 17:09:57

Android设计模式

2021-07-08 11:28:43

观察者模式设计

2024-06-04 13:11:52

Python行为设计模式开发

2011-04-29 09:22:22

2024-12-03 09:34:35

观察者模 式编程Javav

2021-03-29 07:14:28

Spring观察者模式

2012-08-27 10:52:20

.NET架构观察者模式

2021-05-11 09:54:55

鸿蒙HarmonyOS应用

2015-11-25 11:10:45

Javascript设计观察

2024-02-18 12:36:09

2009-03-30 09:39:04

观察者思想换位设计模式

2022-07-13 08:36:57

MQ架构设计模式

2021-01-25 05:38:04

设计原理VueSubject

2021-03-11 11:14:39

鸿蒙HarmonyOS应用

2021-09-29 19:45:24

观察者模式Observable
点赞
收藏

51CTO技术栈公众号