鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断

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

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

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

  • 中断切换篇 用自下而上的方式,从中断源头纯汇编代码往上跟踪代码细节.说清楚保存和恢复中断现场TaskIrqContext过程.

编译开关

//....
#define LOSCFG_ARCH_ARM_VER "armv7-a"
#define LOSCFG_ARCH_CPU "cortex-a7"
#define LOSCFG_PLATFORM "hi3516dv300"
#define LOSCFG_PLATFORM_BSP_GIC_V2 1
#define LOSCFG_PLATFORM_ROOTFS 1
#define LOSCFG_KERNEL_CPPSUPPORT 1
#define LOSCFG_HW_RANDOM_ENABLE 1
#define LOSCFG_ARCH_CORTEX_A7 1
#define LOSCFG_DRIVERS_HDF_PLATFORM_RTC 1
#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 文档看.可前往地址下载查看.

//硬件中断初始化
VOID HalIrqInit(VOID)
{
    UINT32 i;

    /* set externel interrupts to be level triggered, active low. */	//将外部中断设置为电平触发,低电平激活
    for (i = 32; i < OS_HWI_MAX_NUM; i += 16) {
        GIC_REG_32(GICD_ICFGR(i / 16)) = 0;
    }

    /* set externel interrupts to CPU 0 */	//将外部中断设置为CPU 0
    for (i = 32; i < OS_HWI_MAX_NUM; i += 4) {
        GIC_REG_32(GICD_ITARGETSR(i / 4)) = 0x01010101;
    }

    /* set priority on all interrupts */	//设置所有中断的优先级
    for (i = 0; i < OS_HWI_MAX_NUM; i += 4) {
        GIC_REG_32(GICD_IPRIORITYR(i / 4)) = GICD_INT_DEF_PRI_X4;
    }

    /* disable all interrupts. */			//禁用所有中断。
    for (i = 0; i < OS_HWI_MAX_NUM; i += 32) {
        GIC_REG_32(GICD_ICENABLER(i / 32)) = ~0;
    }

    HalIrqInitPercpu();//初始化当前CPU中断信息

    /* enable gic distributor control */
    GIC_REG_32(GICD_CTLR) = 1; //使能分发中断寄存器,该寄存器作用是允许给CPU发送中断信号

#if (LOSCFG_KERNEL_SMP == YES)
    /* register inter-processor interrupt *///注册核间中断,啥意思?就是CPU各核直接可以发送中断信号
    //处理器间中断允许一个CPU向系统其他的CPU发送中断信号,处理器间中断(IPI)不是通过IRQ线传输的,而是作为信号直接放在连接所有CPU本地APIC的总线上。
    LOS_HwiCreate(LOS_MP_IPI_WAKEUP, 0xa0, 0, OsMpWakeHandler, 0);//注册唤醒CPU的中断处理函数
    LOS_HwiCreate(LOS_MP_IPI_SCHEDULE, 0xa0, 0, OsMpScheduleHandler, 0);//注册调度CPU的中断处理函数
    LOS_HwiCreate(LOS_MP_IPI_HALT, 0xa0, 0, OsMpScheduleHandler, 0);//注册停止CPU的中断处理函数
#endif
}
//给每个CPU core初始化硬件中断
VOID HalIrqInitPercpu(VOID)
{
    /* unmask interrupts */	//取消中断屏蔽
    GIC_REG_32(GICC_PMR) = 0xFF;

    /* enable gic cpu interface */	//启用gic cpu接口
    GIC_REG_32(GICC_CTLR) = 1;
}

解读

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

  • 以下是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位配置字段。此字段标识相应的中断是边缘触发的还是电平触发的

    0xC00 - 0xCFC GICD_ICFGRn RW IMPLEMENTATION DEFINED Interrupt Configuration Registers
    #define GICD_ICFGR(n)                   (GICD_OFFSET + 0xc00 + (n) * 4) /* Interrupt Configuration Registers */		//中断配置寄存器

如此一个32位寄存器可以记录16个中断的信息,这也是代码中出现 GIC_REG_32(GICD_ICFGR(i / 16))的原因.

  • 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上,函数的最后就注册了三个核间中断的函数.
        typedef enum {//核间中断
            LOS_MP_IPI_WAKEUP,	//唤醒CPU
            LOS_MP_IPI_SCHEDULE,//调度CPU
            LOS_MP_IPI_HALT,	//停止CPU
        } MP_IPI_TYPE;

中断相关的结构体

size_t g_intCount[LOSCFG_KERNEL_CORE_NUM] = {0};//记录每个CPUcore的中断数量 
HwiHandleForm g_hwiForm[OS_HWI_MAX_NUM];//中断注册表 @note_why 用 form 来表示?有种写 HTML的感觉 :P
STATIC CHAR *g_hwiFormName[OS_HWI_MAX_NUM] = {0};//记录每个硬中断的名称 
STATIC UINT32 g_hwiFormCnt[OS_HWI_MAX_NUM] = {0};//记录每个硬中断的总数量
STATIC UINT32 g_curIrqNum = 0; //记录当前中断号
typedef VOID (*HWI_PROC_FUNC)(VOID); //中断函数指针
typedef struct tagHwiHandleForm {	
    HWI_PROC_FUNC pfnHook;	//中断处理函数
    HWI_ARG_T uwParam;		//中断处理函数参数
    struct tagHwiHandleForm *pstNext;	//节点,指向下一个中断,用于共享中断的情况
} HwiHandleForm;

typedef struct tagIrqParam {	//中断参数
    int swIrq;		//	软件中断
    VOID *pDevId;	//	设备ID
    const CHAR *pName;	//名称
} HwiIrqParam;

注册硬中断

/******************************************************************************
 创建一个硬中断
 中断创建,注册中断号、中断触发模式、中断优先级、中断处理程序。中断被触发时,
 handleIrq会调用该中断处理程序
******************************************************************************/
LITE_OS_SEC_TEXT_INIT UINT32 LOS_HwiCreate(HWI_HANDLE_T hwiNum,	//硬中断句柄编号 默认范围[0-127]
                                           HWI_PRIOR_T hwiPrio,		//硬中断优先级	
                                           HWI_MODE_T hwiMode,		//硬中断模式 共享和非共享
                                           HWI_PROC_FUNC hwiHandler,//硬中断处理函数
                                           HwiIrqParam *irqParam)	//硬中断处理函数参数
{
    UINT32 ret;

    (VOID)hwiPrio;
    if (hwiHandler == NULL) {//中断处理函数不能为NULL
        return OS_ERRNO_HWI_PROC_FUNC_NULL;
    }
    if ((hwiNum > OS_USER_HWI_MAX) || ((INT32)hwiNum < OS_USER_HWI_MIN)) {//中断数区间限制 [32,96]
        return OS_ERRNO_HWI_NUM_INVALID;
    }

#ifdef LOSCFG_NO_SHARED_IRQ	//不支持共享中断
    ret = OsHwiCreateNoShared(hwiNum, hwiMode, hwiHandler, irqParam);
#else
    ret = OsHwiCreateShared(hwiNum, hwiMode, hwiHandler, irqParam);
#endif
    return ret;
}
//创建一个共享硬件中断,共享中断就是一个中断能触发多个响应函数
STATIC UINT32 OsHwiCreateShared(HWI_HANDLE_T hwiNum, HWI_MODE_T hwiMode,
                                HWI_PROC_FUNC hwiHandler, const HwiIrqParam *irqParam)
{
    UINT32 intSave;
    HwiHandleForm *hwiFormNode = NULL;
    HwiHandleForm *hwiForm = NULL;
    HwiIrqParam *hwiParam = NULL;
    HWI_MODE_T modeResult = hwiMode & IRQF_SHARED;

    if (modeResult && ((irqParam == NULL) || (irqParam->pDevId == NULL))) {
        return OS_ERRNO_HWI_SHARED_ERROR;
    }

    HWI_LOCK(intSave);//中断自旋锁

    hwiForm = &g_hwiForm[hwiNum];//获取中断处理项
    if ((hwiForm->pstNext != NULL) && ((modeResult == 0) || (!(hwiForm->uwParam & IRQF_SHARED)))) {
        HWI_UNLOCK(intSave);
        return OS_ERRNO_HWI_SHARED_ERROR;
    }

    while (hwiForm->pstNext != NULL) {//pstNext指向 共享中断的各处理函数节点,此处一直撸到最后一个
        hwiForm = hwiForm->pstNext;//找下一个中断
        hwiParam = (HwiIrqParam *)(hwiForm->uwParam);//获取中断参数,用于检测该设备ID是否已经有中断处理函数
        if (hwiParam->pDevId == irqParam->pDevId) {//设备ID一致时,说明设备对应的中断处理函数已经存在了.
            HWI_UNLOCK(intSave);
            return OS_ERRNO_HWI_ALREADY_CREATED;
        }
    }

    hwiFormNode = (HwiHandleForm *)LOS_MemAlloc(m_aucSysMem0, sizeof(HwiHandleForm));//创建一个中断处理节点
    if (hwiFormNode == NULL) {
        HWI_UNLOCK(intSave);
        return OS_ERRNO_HWI_NO_MEMORY;
    }

    hwiFormNode->uwParam = OsHwiCpIrqParam(irqParam);//获取中断处理函数的参数
    if (hwiFormNode->uwParam == LOS_NOK) {
        HWI_UNLOCK(intSave);
        (VOID)LOS_MemFree(m_aucSysMem0, hwiFormNode);
        return OS_ERRNO_HWI_NO_MEMORY;
    }

    hwiFormNode->pfnHook = hwiHandler;//绑定中断处理函数
    hwiFormNode->pstNext = (struct tagHwiHandleForm *)NULL;//指定下一个中断为NULL,用于后续遍历找到最后一个中断项(见于以上 while (hwiForm->pstNext != NULL)处)
    hwiForm->pstNext = hwiFormNode;//共享中断

    if ((irqParam != NULL) && (irqParam->pName != NULL)) {
        g_hwiFormName[hwiNum] = (CHAR *)irqParam->pName;
    }

    g_hwiForm[hwiNum].uwParam = modeResult;

    HWI_UNLOCK(intSave);
    return LOS_OK;
}

解读

  • 内核将硬中断进行编号,如:
      #define NUM_HAL_INTERRUPT_TIMER0        33
      #define NUM_HAL_INTERRUPT_TIMER1        33
      #define NUM_HAL_INTERRUPT_TIMER2        34
      #define NUM_HAL_INTERRUPT_TIMER3        34
      #define NUM_HAL_INTERRUPT_TIMER4        35
      #define NUM_HAL_INTERRUPT_TIMER5        35
      #define NUM_HAL_INTERRUPT_TIMER6        36
      #define NUM_HAL_INTERRUPT_TIMER7        36
      #define NUM_HAL_INTERRUPT_DMAC          60
      #define NUM_HAL_INTERRUPT_UART0         38
      #define NUM_HAL_INTERRUPT_UART1         39
      #define NUM_HAL_INTERRUPT_UART2         40
      #define NUM_HAL_INTERRUPT_UART3         41
      #define NUM_HAL_INTERRUPT_UART4         42
      #define NUM_HAL_INTERRUPT_TIMER         NUM_HAL_INTERRUPT_TIMER4

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

    //硬时钟初始化
    VOID HalClockInit(VOID)
    {
      // ...
      (void)LOS_HwiCreate(NUM_HAL_INTERRUPT_TIMER, 0xa0, 0, OsTickHandler, 0);//注册OsTickHandler到中断向量表 
    }
    //节拍中断处理函数 ,鸿蒙默认10ms触发一次
      LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
      {
          UINT32 intSave;
          TICK_LOCK(intSave);//tick自旋锁
          g_tickCount[ArchCurrCpuid()]++;// 累加当前CPU核tick数
          TICK_UNLOCK(intSave);
          OsTimesliceCheck();//时间片检查
          OsTaskScan(); /* task timeout scan *///扫描超时任务 例如:delay(300)
          #if (LOSCFG_BASE_CORE_SWTMR == YES)
              OsSwtmrScan();//扫描定时器,查看是否有超时定时器,加入队列
          #endif
      } 
  • 鸿蒙是支持中断共享的,在OsHwiCreateShared中,将函数注册到g_hwiForm中.中断向量完成注册后,就是如何触发和回调的问题.触发在中断切换篇中已经讲清楚,触发是从底层汇编向上调用,调用的C函数就是HalIrqHandler

中断怎么触发的?

分两种情况:

  • 通过硬件触发,比如按键,USB的插拔这些中断源向中断控制器发送电信号(高低电平触发或是上升/下降沿触发),中断控制器经过过滤后将信号发给对应的CPU处理,通过硬件改变PC和CPSR寄存值,直接跳转到中断向量(固定地址)执行.
      b   reset_vector            @开机代码
      b   _osExceptUndefInstrHdl 	@异常处理之CPU碰到不认识的指令
      b   _osExceptSwiHdl			@异常处理之:软中断
      b   _osExceptPrefetchAbortHdl	@异常处理之:取指异常
      b   _osExceptDataAbortHdl		@异常处理之:数据异常
      b   _osExceptAddrAbortHdl		@异常处理之:地址异常
      b   OsIrqHandler				@异常处理之:硬中断
      b   _osExceptFiqHdl				@异常处理之:快中断
  • 通过软件触发,常见于核间中断的情况, 核间中断指的是几个CPU之间相互通讯的过程.以下为某一个CPU向其他CPU(可以是多个)发起让这些CPU重新调度LOS_MpSchedule的中断请求信号.最终是写了中断控制器的GICD_SGIR寄存器,这是一个由软件触发中断的寄存器.中断控制器会将请求分发给对应的CPU处理中断,即触发了OsIrqHandler.
    //给参数CPU发送调度信号
      VOID LOS_MpSchedule(UINT32 target)//target每位对应CPU core 
      {
          UINT32 cpuid = ArchCurrCpuid();
          target &= ~(1U << cpuid);//获取除了自身之外的其他CPU
          HalIrqSendIpi(target, LOS_MP_IPI_SCHEDULE);//向目标CPU发送调度信号,核间中断(Inter-Processor Interrupts),IPI
      }
      //SGI软件触发中断(Software Generated Interrupt)通常用于多核间通讯
      STATIC VOID GicWriteSgi(UINT32 vector, UINT32 cpuMask, UINT32 filter)
      {
          UINT32 val = ((filter & 0x3) << 24) | ((cpuMask & 0xFF) << 16) |
                      (vector & 0xF);

          GIC_REG_32(GICD_SGIR) = val;//写SGI寄存器
      }
      //向指定核发送核间中断
      VOID HalIrqSendIpi(UINT32 target, UINT32 ipi)
      {
          GicWriteSgi(ipi, target, 0);
      }

中断统一处理入口函数 HalIrqHandler

//硬中断统一处理函数,这里由硬件触发,调用见于 ..\arch\arm\arm\src\los_dispatch.S
VOID HalIrqHandler(VOID)
{
    UINT32 iar = GIC_REG_32(GICC_IAR);//从中断确认寄存器获取中断ID号
    UINT32 vector = iar & 0x3FFU;//计算中断向量号
    /*
     * invalid irq number, mainly the spurious interrupts 0x3ff,
     * gicv2 valid irq ranges from 0~1019, we use OS_HWI_MAX_NUM
     * to do the checking.
     */
    if (vector >= OS_HWI_MAX_NUM) {
        return;
    }
    g_curIrqNum = vector;//记录当前中断ID号
    OsInterrupt(vector);//调用上层中断处理函数
    /* use orignal iar to do the EOI */
    GIC_REG_32(GICC_EOIR) = iar;//更新中断结束寄存器
}
VOID OsInterrupt(UINT32 intNum)//中断实际处理函数
{
    HwiHandleForm *hwiForm = NULL;
    UINT32 *intCnt = NULL;

    intCnt = &g_intCount[ArchCurrCpuid()];//当前CPU的中断总数量 ++
    *intCnt = *intCnt + 1;//@note_why 这里没看明白为什么要 +1

#ifdef LOSCFG_CPUP_INCLUDE_IRQ //开启查询系统CPU的占用率的中断
    OsCpupIrqStart();//记录本次中断处理开始时间
#endif

#ifdef LOSCFG_KERNEL_TICKLESS
    OsTicklessUpdate(intNum);
#endif
    hwiForm = (&g_hwiForm[intNum]);//获取对应中断的实体
#ifndef LOSCFG_NO_SHARED_IRQ	//如果没有定义不共享中断 ,意思就是如果是共享中断
    while (hwiForm->pstNext != NULL) { //一直撸到最后
        hwiForm = hwiForm->pstNext;//下一个继续撸
#endif
        if (hwiForm->uwParam) {//有参数的情况
            HWI_PROC_FUNC2 func = (HWI_PROC_FUNC2)hwiForm->pfnHook;//获取回调函数
            if (func != NULL) {
                UINTPTR *param = (UINTPTR *)(hwiForm->uwParam);
                func((INT32)(*param), (VOID *)(*(param + 1)));//运行带参数的回调函数
            }
        } else {//木有参数的情况
            HWI_PROC_FUNC0 func = (HWI_PROC_FUNC0)hwiForm->pfnHook;//获取回调函数
            if (func != NULL) {
                func();//运行回调函数
            }
        }
#ifndef LOSCFG_NO_SHARED_IRQ
    }
#endif
    ++g_hwiFormCnt[intNum];//中断号计数器总数累加

    *intCnt = *intCnt - 1;	//@note_why 这里没看明白为什么要 -1 
#ifdef LOSCFG_CPUP_INCLUDE_IRQ	//开启查询系统CPU的占用率的中断
    OsCpupIrqEnd(intNum);//记录中断处理时间完成时间
#endif
}

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

  • 第一步:取号,这号是由中断控制器的 GICC_IAR寄存器提供的,这是一个专门保存当前中断号的寄存器.
  • 第二步:从注册表g_hwiForm中查询注册函数,同时取出参数.
  • 第三步:执行函数,也就是回调注册函数,分有参和无参两种情况 func(...),在中断共享的情况,注册函数会指向 next 注册函数pstNext,依次执行回调函数,这是中断共享的实现细节.
    typedef struct tagHwiHandleForm {	
          HWI_PROC_FUNC pfnHook;	//中断处理函数
          HWI_ARG_T uwParam;		//中断处理函数参数
          struct tagHwiHandleForm *pstNext;	//节点,指向next中断,用于共享中断的情况
    } HwiHandleForm;
  • 第四步:销号,本次中断完成了就需要消除记录,中断控制器也有专门的销号寄存器GICC_EOIR
  • 另外的是一些统一数据,每次中断号处理内核都会记录次数,和耗时,以便定位/跟踪/诊断问题.

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大厂APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:https://gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/600408.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

端口被其他进程占用:OSError: [Errno 98] Address already in use

一、问题描述 错误提示端口号正在被使用 二、解决办法 1.使用 lsof 命令&#xff0c;列出所有正在监听&#xff08;即被绑定&#xff09;的网络连接&#xff0c;包括它们所使用的端口号 sudo lsof -i -P -n | grep LISTEN 2.解绑被绑定的端口号 根据 netstat 或 lsof 命令…

基于OpenPCDet框架进行Pointpillars算法环境搭建并基于TensorRT和ROS部署

文章目录 参考链接1.创建虚拟环境2.安装OpenDet3.安装用于模型转换的库4.数据集转换5.模型训练6.部署安装tensorrt模型转换 编译ROS工程结果报错梳理【报错1】【报错2】【报错3】【报错4】【报错5】 参考链接 基于OpenDet进行训练&#xff0c;基于tensorrt-8.5进行部署并移植到…

常见错误以及如何纠正它们

团队和关键结果目标 (OKR) 之间的关系是深刻且至关重要的。总而言之&#xff0c;一切都应该是相互关联的。正如《团队的智慧》一书中所强调的&#xff1a; 在团队中&#xff0c;没有什么比每个成员对共同目标和一组相关绩效目标的承诺更重要的了&#xff0c;而团队对此负有共同…

经常发文章的你是否想过定时发布是咋实现的?

前言 可乐他们团队最近在做一个文章社区平台,由于人手不够,前后端都是由前端同学来写。后端使用 nest 来实现。 某一天周五下午,可乐正在快乐摸鱼,想到周末即将来临,十分开心。然而,产品突然找到了他,说道:可乐,我们要做一个文章定时发布功能。 现在我先为你解释一…

值得收藏!修复Windows 10/11中找不到输出或输入设备的五种方法

序言 这篇文章主要关注处理声音输出/输入设备未发现的问题。它提供了许多可行的方法,帮助了许多Windows用户。阅读以下内容以找到你的解决方案。 最近,我将Windows 10更新到21H2,发现我的音频无法工作。当我把鼠标放在任务栏上的声音图标(上面有一个十字图标)上时,它会…

市面上好用的AI工具有哪些?

市面上的AI工具数不胜数&#xff0c;选择合适自己的AI工具则需要考虑自己的需求&#xff0c;看是否能满足的使用需求。那么市面上又有哪些好用的AI工具呢&#xff1f; 泰迪智能科技拥有简单易用的大数据挖掘建模平台&#xff0c;能够让数据创造更大的价值。 功能板块&…

基于Springboot的校园新闻管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的校园新闻管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

Android 巧用putBinder方法传递大文件

使用Intent传递数据大家都知道&#xff0c;但是如果你使用Intent传递大于1Mb的数据时&#xff0c;就一定会报如下的错误&#xff1a; Caused by: android.os.TransactionTooLargeException: data parcel size 1049112 bytes 就是说你的传输数据太大了&#xff0c;当前的大小达…

Rust 解决循环引用

导航 循环引用一、现象二、解决 循环引用 循环引用出现的一个场景就是你指向我&#xff0c;我指向你&#xff0c;导致程序崩溃 解决方式可以通过弱指针&#xff0c;而Rust中的弱指针就是Weak 在Rc中&#xff0c;可以实现&#xff0c;对一个变量&#xff0c;持有多个不可变引…

FSC森林认证是什么?

FSC森林认证&#xff0c;又称木材认证&#xff0c;是一种运用市场机制来促进森林可持续经营&#xff0c;实现生态、社会和经济目标的工具。FSC森林认证包括森林经营认证&#xff08;Forest Management, FM&#xff09;和产销监管链认证&#xff08;Chain of Custody, COC&#…

人大金仓V8R6迁移mysql8.0

人大金仓数据库迁移mysql mysql版本&#xff1a;mysql 8.0.22 人大金仓版本;KingbaseES V008R006C008B0014 on x64 打开数据迁移工具 等待执行完成后使用命令窗口中提示的地址在浏览器中打开&#xff1a; 登录。此处登录不用修改任何信息&#xff0c;点击登录即可 新建源数…

初识Node.js-认识node(安装Node.js环境配置)

目录 一、node介绍 1.概念和特点 2.核心功能 3.应用场景 二、Node初使用 1.安装node配置 windows上安Node.js 1.windows安装包&#xff08;.msi&#xff09; 2、Windows 二进制文件 (.exe)安装 Linux 上安装 Node.js 直接使用已编译好的包 Ubuntu 源码安装 Node.js …

⚡REST 和 SOAP 协议有什么区别?

原文链接&#xff1a;https://document360.com/blog/rest-vs-soap/ API 是应用程序编程接口&#xff08;Application Programming Interface&#xff09;的缩写。API 规定了不同的软件组件应如何以编程方式进行交互和通信。 最常见的 API 类型就是 Web API。网络应用&#xff…

自学错误合集--项目打包报错,运行报错持续更新中

java后端自学错误总结 一.项目打包报错2.项目打包之后运行报错 二.项目运行报错 一.项目打包报错 javac: &#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ļ&#xfffd;: E:\xx\xx\xx\docer-xx\src\main\java\xx\xx\xx\xx\xx\xx.java &#xfffd;&#xff…

OceanBase 如何实现多层面的资源隔离

OceanBase的资源隔离涵盖了多个方面&#xff0c;如物理机器间的隔离、不同租户之间的隔离、同一租户内的隔离&#xff0c;以及针对大型查询请求的隔离等。在实际应用OceanBase的过程中&#xff0c;我们经常会遇到这些操作场景或产生相关需求。这篇文章针对这些内容进行了简要的…

如果你作 为Java程序员曾经遭遇过NullPointerException,请举起手

如果你作 为Java程序员曾经遭遇过NullPointerException&#xff0c;请举起手 1.让Optional发光发热&#xff1a;去除代码中对null的检查&#xff0c; 采用防御式检查减少NullPointerException java8实战 书籍 第225页 免费下载链接&#xff1a; https://pan.quark.cn/s/5cf68…

书生·浦语大模型实战营之手把手带你评测 Llama 3 能力(OpenCompass 版)

书生浦语大模型实战营之手把手带你评测 Llama 3 能力&#xff08;OpenCompass 版&#xff09; 环境配置 conda create -n llama3 python3.10 pytorch torchvision pytorch-cuda -c nvidia -c pytorch -y conda activate llama3conda install git git-lfs install✨下载 Llama3…

Figma 高效技巧:设计系统中的图标嵌套

Figma 高效技巧&#xff1a;设计系统中的图标嵌套 在设计中&#xff0c;图标起着不可或缺的作用。一套便捷易用的图标嵌套方法可以有效提高设计效率。 分享一下我在图标嵌套上走过的弯路和经验教训。我的图标嵌套可以分三个阶段&#xff1a; 第一阶段&#xff1a;建立图标库 一…

使用Java编写的简单彩票中奖概率计算器

前言 在当今社会&#xff0c;彩票已经成为许多人追逐梦想和改变生活的一种方式。然而&#xff0c;中奖的概率却是一个让人犹豫和兴奋的话题。在这篇文章中&#xff0c;我们将探讨如何使用Java编程语言实现一个简单的彩票中奖概率计算器。通过这个计算器&#xff0c;我们可以根…

C++细节,可能存在的隐患,面试题03

文章目录 11. C编译过程12. const vs #define12.1. 全局const vs 局部const 13. C内存分区14. C变量作用域14.1. 常量 vs 全局变量 vs 静态变量 15. C类型转换16. 函数指针17. 悬空指针 vs 野指针18. 为什么使用空指针&#xff0c;建议使用nullptr而不是NULL&#xff1f; 11. C…