当前位置: 首页 > news >正文

μC/OS-Ⅱ源码学习(7)---软件定时器

快速回顾

μC/OS-Ⅱ中的多任务

μC/OS-Ⅱ源码学习(1)---多任务系统的实现

μC/OS-Ⅱ源码学习(2)---多任务系统的实现(下)

μC/OS-Ⅱ源码学习(3)---事件模型

μC/OS-Ⅱ源码学习(4)---信号量

μC/OS-Ⅱ源码学习(5)---消息队列

μC/OS-Ⅱ源码学习(6)---事件标志组

        本文进一步解析μC/OS-Ⅱ中,软件定时器的函数源码。

μC/OSⅡ中的定时器模型

        软件定时器并不属于事件系统,是一类特殊的模型,有自己独特的执行逻辑。更具体来说,除了手动创建、启动和销毁,软件定时器的其它生命周期过程无需用户操作,更不与其它任务相关联,它和定时器任务共同构成了软件定时器系统。在μC/OSⅡ中,软件定时器的运行方式是“车轮”式的,将时间线周期性地均匀缠绕在“车轮”上,好比钟表周期性走时一样,每一根车辐都对应一个时间刻度,进而可以将不同的定时器通过到期时间归属到对应的时刻上,来减少对大量定时器的遍历,提高效率。

相关结构和类型

        和软件定时器相关的变量如下:

//ucos_ii.h
OS_EXT  INT16U        OSTmrFree;                /* 剩余可用的空白定时器 */
OS_EXT  INT16U        OSTmrUsed;                /* 已使用的定时器数量 */
OS_EXT  INT32U        OSTmrTime;                /* 当前时间 */OS_EXT  OS_EVENT     *OSTmrSem;                 /* 操作定时器的权限 */
OS_EXT  OS_EVENT     *OSTmrSemSignal;           /* 当定时器更新(到期)时,用于提醒定时器任务运行的信号量 */OS_EXT  OS_TMR        OSTmrTbl[OS_TMR_CFG_MAX];   /* 软件定时器数组 */
OS_EXT  OS_TMR       *OSTmrFreeList;              /* 空白软件定时器链表 */
OS_EXT  OS_STK        OSTmrTaskStk[OS_TASK_TMR_STK_SIZE];    //软件定时器堆栈大小OS_EXT  OS_TMR_WHEEL  OSTmrWheelTbl[OS_TMR_CFG_WHEEL_SIZE];   //时间车轮数组,每一个成员代表一个时间刻度

        OS_TMR_CFG_MAX表示定时器的最大数量,OS_TMR_CFG_WHEEL_SIZE表示时间车轮的车辐数,也即时间刻度数,下文中的“车辐”和“刻度”表示一个意思。

        两个信号量,OSTmrSem是获取定时器操作权限的信号量,OSTmrSemSignal是滴答中断用来提醒定时器任务执行的信号量。

        这里面比较重要的类型就是定时器OS_TMR时间车轮OS_TMR_WHEEL

OS_TMR

        与任务、事件类似,定时器也是以链表的形式进行遍历检索的(除了存储定时器的原始数组),且是双向链表的形式,方便在链表中进行插入和删除元素。

//ucos_ii.h
typedef  struct  os_tmr {INT8U            OSTmrType;             /* 初始化后被设置位OS_TMR_TYPE(其它类型无效) */OS_TMR_CALLBACK  OSTmrCallback;         /* 定时器回调函数 */void            *OSTmrCallbackArg;      /* 需要传给回调函数的参数 */void            *OSTmrNext;             /* 和OSTmrPrev共同构成双向链表指针 */void            *OSTmrPrev;INT32U           OSTmrMatch;            /* 到期时间,即当OSTmrTime=OSTmrMatch时定时器到期 */INT32U           OSTmrDly;              /* 开启定时器前的延时 */INT32U           OSTmrPeriod;           /* 定时器周期 */
#if OS_TMR_CFG_NAME_EN > 0uINT8U           *OSTmrName;             /* 定时器名称 */
#endifINT8U            OSTmrOpt;              /* 选项(如OS_TMR_OPT_xxx) */INT8U            OSTmrState;            /* 定时器状态,有四种 */
} OS_TMR;

        里面有两个成员可以进一步解析,一个是选项OSTmrOpt

//ucos_ii.h
#define  OS_TMR_OPT_NONE              0u  /* 无选项 */#define  OS_TMR_OPT_ONE_SHOT          1u  /* 单次定时器,不会自动重启 */
#define  OS_TMR_OPT_PERIODIC          2u  /* 周期性定时器,到期后自动重启 */#define  OS_TMR_OPT_CALLBACK          3u  /* OSTmrStop() option to call 'callback' w/ timer arg.     */
#define  OS_TMR_OPT_CALLBACK_ARG      4u  /* OSTmrStop() option to call 'callback' w/ new   arg.     */

        另一个是定时器状态OSTmrState

//ucos_ii.h
#define  OS_TMR_STATE_UNUSED       0u    //未使用的(未初始化)
#define  OS_TMR_STATE_STOPPED      1u    //已初始化但未启动的定时器
#define  OS_TMR_STATE_COMPLETED    2u    //已经结束的定时器
#define  OS_TMR_STATE_RUNNING      3u    //正在运行的定时器

OS_TMR_WHEEL

//ucos_ii.h
typedef struct os_tmr_wheel {OS_TMR      *OSTmrFirst;      /* 指向定时器的指针 */INT16U       OSTmrEntries;     //该车轮片剩余的定时器
} OS_TMR_WHEEL;

        OSTmrWheelTbl[OS_TMR_CFG_WHEEL_SIZE]包含了所有的刻度(详见上一节的图解模型),里面的每个对象都对应一个刻度(OS_TMR_WHEEL类型),刻度上会挂载用户所需的定时器(OSTmrFirst链表)。系统会根据定时器到期时间自动挂载到对应的刻度上。

软件定时器初始化

        在OSInit()中,对系统的软件定时器进行了结构初始化OSTmr_Init():将原始的定时器数组按顺序相连,形成一条空白链表,同时进行白板初始化。

//os_tmr.c
void  OSTmr_Init (void)
{
#if OS_EVENT_NAME_EN > 0uINT8U    err;
#endifINT16U   ix;INT16U   ix_next;OS_TMR  *ptmr1;OS_TMR  *ptmr2;OS_MemClr((INT8U *)&OSTmrTbl[0],      sizeof(OSTmrTbl));     /* 清除定时器数组 */OS_MemClr((INT8U *)&OSTmrWheelTbl[0], sizeof(OSTmrWheelTbl));       /* Clear the timer wheel                      */for (ix = 0u; ix < (OS_TMR_CFG_MAX - 1u); ix++) {     /* 遍历定时器数组对每个定时器进行初始化,并用指针连成链表 */ix_next = ix + 1u;ptmr1 = &OSTmrTbl[ix];ptmr2 = &OSTmrTbl[ix_next];ptmr1->OSTmrType    = OS_TMR_TYPE;ptmr1->OSTmrState   = OS_TMR_STATE_UNUSED;ptmr1->OSTmrNext    = (void *)ptmr2;     /* OSTmrNext指针指向下一个定时器 */
#if OS_TMR_CFG_NAME_EN > 0uptmr1->OSTmrName    = (INT8U *)(void *)"?";
#endif}ptmr1               = &OSTmrTbl[ix];ptmr1->OSTmrType    = OS_TMR_TYPE;ptmr1->OSTmrState   = OS_TMR_STATE_UNUSED;ptmr1->OSTmrNext    = (void *)0;     /* 最后一个定时器的下一个为空 */
#if OS_TMR_CFG_NAME_EN > 0uptmr1->OSTmrName    = (INT8U *)(void *)"?";
#endifOSTmrTime           = 0u;OSTmrUsed           = 0u;OSTmrFree           = OS_TMR_CFG_MAX;OSTmrFreeList       = &OSTmrTbl[0];      //链表头指向数组首,正式形成OSTmrFreeList链表OSTmrSem            = OSSemCreate(1u);    //创建定时器操作权限信号量,初始值为1(第一次操作必定要成功)OSTmrSemSignal      = OSSemCreate(0u);    //创建定时器提醒信号量,初始值为0(等待Tick中段释放)#if OS_EVENT_NAME_EN > 0uOSEventNameSet(OSTmrSem,       (INT8U *)(void *)"uC/OS-II TmrLock",   &err);OSEventNameSet(OSTmrSemSignal, (INT8U *)(void *)"uC/OS-II TmrSignal", &err);
#endifOSTmr_InitTask();     //初始化定时器任务
}

软件定时器任务

        在μC/OSⅡ中,软件定时器的运行离不开定时器任务的管理,这是一个特殊的任务,需要使用时,还要明确指定定时器任务的优先级,不能与其它任务优先级相冲突:

//用户自定义定时器任务优先级
#define  OS_TASK_TMR_PRIO     10

        在上一节定时器初始化函数的最后,还对定时器任务进行了初始化OSTmr_InitTask()

//os_tmr.c
static  void  OSTmr_InitTask (void)
{
#if OS_TASK_NAME_EN > 0uINT8U  err;
#endif#if OS_TASK_CREATE_EXT_EN > 0u     //使用OSTaskCreateExt创建任务#if OS_STK_GROWTH == 1u    //堆栈增长方向,由高到地(void)OSTaskCreateExt(OSTmr_Task,(void *)0,                                       /* No arguments passed to OSTmrTask()      */&OSTmrTaskStk[OS_TASK_TMR_STK_SIZE - 1u],        /* Set Top-Of-Stack                        */OS_TASK_TMR_PRIO,OS_TASK_TMR_ID,&OSTmrTaskStk[0],                                /* Set Bottom-Of-Stack                     */OS_TASK_TMR_STK_SIZE,(void *)0,                                       /* No TCB extension                        */OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);      /* Enable stack checking + clear stack     */#else(void)OSTaskCreateExt(OSTmr_Task,(void *)0,                                       /* No arguments passed to OSTmrTask()      */&OSTmrTaskStk[0],                                /* Set Top-Of-Stack                        */OS_TASK_TMR_PRIO,OS_TASK_TMR_ID,&OSTmrTaskStk[OS_TASK_TMR_STK_SIZE - 1u],        /* Set Bottom-Of-Stack                     */OS_TASK_TMR_STK_SIZE,(void *)0,                                       /* No TCB extension                        */OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);      /* Enable stack checking + clear stack     */#endif
#else#if OS_STK_GROWTH == 1u(void)OSTaskCreate(OSTmr_Task,(void *)0,&OSTmrTaskStk[OS_TASK_TMR_STK_SIZE - 1u],OS_TASK_TMR_PRIO);#else(void)OSTaskCreate(OSTmr_Task,(void *)0,&OSTmrTaskStk[0],OS_TASK_TMR_PRIO);#endif
#endif#if OS_TASK_NAME_EN > 0uOSTaskNameSet(OS_TASK_TMR_PRIO, (INT8U *)(void *)"uC/OS-II Tmr", &err);
#endif
}

        直接看定时器任务OSTmr_Task():该任务会周期性获得OSTmrSemSignal信号量并执行,主要功能是检索当前所在时间车轮的刻度,并对该刻度上挂载的软件定时器对象进行遍历,检查是否到期,如果到期就执行对应的回调函数,并根据定时器配置决定是否需要再次挂载

//os_tmr.c
static  void  OSTmr_Task (void *p_arg)
{INT8U            err;OS_TMR          *ptmr;OS_TMR          *ptmr_next;OS_TMR_CALLBACK  pfnct;OS_TMR_WHEEL    *pspoke;INT16U           spoke;p_arg = p_arg;      /* 引用一下传参防止编译告警 */for (;;) {OSSemPend(OSTmrSemSignal, 0u, &err);        /* 等待定时器信号量提醒时间到期 */OSSchedLock();     //给调度器上锁OSTmrTime++;      /* 当前时间+1 */spoke  = (INT16U)(OSTmrTime % OS_TMR_CFG_WHEEL_SIZE);    /* 定位当前时间所处的车轮片 */pspoke = &OSTmrWheelTbl[spoke];    //取出该片ptmr   = pspoke->OSTmrFirst;while (ptmr != (OS_TMR *)0) {ptmr_next = (OS_TMR *)ptmr->OSTmrNext;     /* 要提前获取下一个,因为当前定时器可能会被后续操作移除出链表 */if (OSTmrTime == ptmr->OSTmrMatch) {       /* 当前时间匹配定时器内设定的时间 */OSTmr_Unlink(ptmr);       /* 时间匹配,说明该定时器已到期,则从车轮中取出 */if (ptmr->OSTmrOpt == OS_TMR_OPT_PERIODIC) {OSTmr_Link(ptmr, OS_TMR_LINK_PERIODIC);      /* 重新加入到时间车轮 */} else {ptmr->OSTmrState = OS_TMR_STATE_COMPLETED;   /* 标记定时器已完成 */}pfnct = ptmr->OSTmrCallback;     /* 获取当前定时器绑定的回调函数 */if (pfnct != (OS_TMR_CALLBACK)0) {(*pfnct)((void *)ptmr, ptmr->OSTmrCallbackArg);     //执行回调函数}}ptmr = ptmr_next;}OSSchedUnlock();}
}

        其中涉及到两个关键函数:OSTmr_Unlink()OSTmr_Link(),先看前者:

//os_tmr.c
static  void  OSTmr_Unlink (OS_TMR *ptmr)
{OS_TMR        *ptmr1;OS_TMR        *ptmr2;OS_TMR_WHEEL  *pspoke;INT16U         spoke;spoke  = (INT16U)(ptmr->OSTmrMatch % OS_TMR_CFG_WHEEL_SIZE);    //查看到期时间属于哪个片段pspoke = &OSTmrWheelTbl[spoke];    //取出该片if (pspoke->OSTmrFirst == ptmr) {      /* 目标定时器处于片内首位 */ptmr1              = (OS_TMR *)ptmr->OSTmrNext;pspoke->OSTmrFirst = (OS_TMR *)ptmr1;if (ptmr1 != (OS_TMR *)0) {ptmr1->OSTmrPrev = (void *)0;}} else {ptmr1            = (OS_TMR *)ptmr->OSTmrPrev;     /* 目标定时器位于中间某个位置 */ptmr2            = (OS_TMR *)ptmr->OSTmrNext;ptmr1->OSTmrNext = ptmr2;if (ptmr2 != (OS_TMR *)0) {ptmr2->OSTmrPrev = (void *)ptmr1;}}ptmr->OSTmrState = OS_TMR_STATE_STOPPED;     //设置为已停止状态ptmr->OSTmrNext  = (void *)0;ptmr->OSTmrPrev  = (void *)0;pspoke->OSTmrEntries--;
}

        再看重新链接函数OSTmr_Link()

//os_tmr.c
static  void  OSTmr_Link (OS_TMR  *ptmr,INT8U    type)
{OS_TMR       *ptmr1;OS_TMR_WHEEL *pspoke;INT16U        spoke;ptmr->OSTmrState = OS_TMR_STATE_RUNNING;     //设置为运行态if (type == OS_TMR_LINK_PERIODIC) {     /* 使用周期时间还是单次延时值作为重启的计算时间 */ptmr->OSTmrMatch = ptmr->OSTmrPeriod + OSTmrTime;} else {if (ptmr->OSTmrDly == 0u) {ptmr->OSTmrMatch = ptmr->OSTmrPeriod + OSTmrTime;} else {ptmr->OSTmrMatch = ptmr->OSTmrDly    + OSTmrTime;}}spoke  = (INT16U)(ptmr->OSTmrMatch % OS_TMR_CFG_WHEEL_SIZE);    //获取该定时器应处于的车轮片pspoke = &OSTmrWheelTbl[spoke];//插入到车轮片首位if (pspoke->OSTmrFirst == (OS_TMR *)0) {pspoke->OSTmrFirst   = ptmr;ptmr->OSTmrNext      = (OS_TMR *)0;pspoke->OSTmrEntries = 1u;} else {ptmr1                = pspoke->OSTmrFirst;pspoke->OSTmrFirst   = ptmr;ptmr->OSTmrNext      = (void *)ptmr1;ptmr1->OSTmrPrev     = (void *)ptmr;pspoke->OSTmrEntries++;}ptmr->OSTmrPrev = (void *)0;
}

软件定时器的创建

        创建定时器的函数为:

OS_TMR *OSTmrCreate (INT32U dlyINT32U  period, INT8U opt, OS_TMR_CALLBACK callback, void *callback_argINT8U *pnameINT8U *perr);

        配置信息很丰富,dly表示定时器首次调用的延时,period表示周期性定时器的执行周期,可选项opt包括:

//ucos_ii.h
#define  OS_TMR_OPT_ONE_SHOT   1u   /* 单次定时器,不会自动重启 */
#define  OS_TMR_OPT_PERIODIC   2u   /* 周期定时器,到期后会自动重启 */

        接着来看源码:

//os_tmr.c
OS_TMR  *OSTmrCreate (INT32U           dly,INT32U           period,INT8U            opt,OS_TMR_CALLBACK  callback,void            *callback_arg,INT8U           *pname,INT8U           *perr)
{OS_TMR   *ptmr;#ifdef OS_SAFETY_CRITICALif (perr == (INT8U *)0) {OS_SAFETY_CRITICAL_EXCEPTION();}
#endif#ifdef OS_SAFETY_CRITICAL_IEC61508if (OSSafetyCriticalStartFlag == OS_TRUE) {OS_SAFETY_CRITICAL_EXCEPTION();}
#endif#if OS_ARG_CHK_EN > 0uswitch (opt) {      /* 校验选项参数 */case OS_TMR_OPT_PERIODIC:if (period == 0u) {     //周期性定时器的周期不能为0*perr = OS_ERR_TMR_INVALID_PERIOD;return ((OS_TMR *)0);}break;case OS_TMR_OPT_ONE_SHOT:if (dly == 0u) {     //单次定时器的初次调用延时不能为0(否则就是立即执行,没有意义,不如直接调用函数)*perr = OS_ERR_TMR_INVALID_DLY;return ((OS_TMR *)0);}break;default:    //非法选项,返回空*perr = OS_ERR_TMR_INVALID_OPT;return ((OS_TMR *)0);}
#endifif (OSIntNesting > 0u) {      /* 不能在中断内调用创建函数 */*perr  = OS_ERR_TMR_ISR;return ((OS_TMR *)0);}OSSchedLock();    //给调度器上锁ptmr = OSTmr_Alloc();      /* 从空白定时器链表中取一个定时器 */if (ptmr == (OS_TMR *)0) {     //如无可用定时器,则标记错误并返回空OSSchedUnlock();*perr = OS_ERR_TMR_NON_AVAIL;return ((OS_TMR *)0);}/* 填装定时器 */ptmr->OSTmrState       = OS_TMR_STATE_STOPPED;     /* 未运行状态 */ptmr->OSTmrDly         = dly;ptmr->OSTmrPeriod      = period;ptmr->OSTmrOpt         = opt;ptmr->OSTmrCallback    = callback;ptmr->OSTmrCallbackArg = callback_arg;
#if OS_TMR_CFG_NAME_EN > 0uptmr->OSTmrName        = pname;
#endifOSSchedUnlock();    //解锁调度器*perr = OS_ERR_NONE;return (ptmr);    //返回创建好的定时器对象指针
}

        发现了一个盲点,只有定时器任务相关的函数会使用OSSchedLock()OSSchedUnLock()函数进行调度器上锁解锁操作,其它的源码中是不存在这样的操作的,取而代之直接使用临界区OS_ENTER_CRITICAL()OS_EXIT_CRITICAL()囊括一整段代码,那么是为什么呢?

        我们知道进入临界区本质上就是关闭中断(除了NMI和硬件FAULT):

//os_cpu_a.asm
OS_CPU_SR_SaveMRS     R0, PRIMASK  	;读取PRIMASK到R0,R0为返回值 CPSID   I				;PRIMASK=1,关中断(NMI和硬件FAULT可以响应)BX      LR			    ;返回OS_CPU_SR_RestoreMSR     PRIMASK, R0	   	;读取R0到PRIMASK中,R0为参数BX      LR				;返回

        当关闭中断后,相当于执行连续、不可打断的代码。而上锁调度器仅仅是无法切换任务,其使用条件是不如临界区严格的。

//os_core.c
void  OSSchedLock (void)
{
#if OS_CRITICAL_METHOD == 3u     /* 初始化临界区变量 */OS_CPU_SR  cpu_sr = 0u;
#endifif (OSRunning == OS_TRUE) {      /* 系统正在运行 */OS_ENTER_CRITICAL();if (OSIntNesting == 0u) {      /* 不能在中断中调用 */if (OSLockNesting < 255u) {      /* 防止调度器锁上溢 */OSLockNesting++;      /* 调度器锁+1 */}}OS_EXIT_CRITICAL();}
}

        笔者的理解是,有些对事件和任务的操作是可以在中断中进行的(典型的如Post操作),因此在不希望发生这些事情的场合(如Pend操作),就进入临界区,防止中断内相关操作的干扰。

        而定时器的生态链是比较封闭的,不与其它任务和事件相关联,无需担心意外的操作导致出错。此外,定时器有实时性要求(比如0延时立即执行),不希望被切换到其它大型任务,这样就无法保证设定的延时,所以在进行操作前都会对调度器上锁。

软件定时器的运行

        使用OSTmrStart()运行对应的定时器,直接分析源码:

//os_tmr.c
BOOLEAN  OSTmrStart (OS_TMR   *ptmr,INT8U    *perr)
{
#ifdef OS_SAFETY_CRITICALif (perr == (INT8U *)0) {OS_SAFETY_CRITICAL_EXCEPTION();}
#endif#if OS_ARG_CHK_EN > 0uif (ptmr == (OS_TMR *)0) {*perr = OS_ERR_TMR_INVALID;return (OS_FALSE);}
#endifif (ptmr->OSTmrType != OS_TMR_TYPE) {       /* 验证定时器类型是否正确 */*perr = OS_ERR_TMR_INVALID_TYPE;return (OS_FALSE);}if (OSIntNesting > 0u) {         /* 不能在中断中调用 */*perr  = OS_ERR_TMR_ISR;return (OS_FALSE);}OSSchedLock();switch (ptmr->OSTmrState) {case OS_TMR_STATE_RUNNING:       /* 本来就在运行状态,先脱离再重新链接重启 */OSTmr_Unlink(ptmr);       /* 脱离时间车轮 */OSTmr_Link(ptmr, OS_TMR_LINK_DLY);       /* 重新链接时间车轮 */OSSchedUnlock();*perr = OS_ERR_NONE;return (OS_TRUE);case OS_TMR_STATE_STOPPED:case OS_TMR_STATE_COMPLETED:     /* 原来处于停止或完成状态,直接链接上并重启 */OSTmr_Link(ptmr, OS_TMR_LINK_DLY);      /* 链接到时间车轮上 */OSSchedUnlock();*perr = OS_ERR_NONE;return (OS_TRUE);case OS_TMR_STATE_UNUSED:        /* 未初始化的定时器,标记错误并返回 */OSSchedUnlock();*perr = OS_ERR_TMR_INACTIVE;return (OS_FALSE);default:     //非法选项,标记错误并返回OSSchedUnlock();*perr = OS_ERR_TMR_INVALID_STATE;return (OS_FALSE);}
}

软件定时器的到期

        在开启后,如何保证时间到期后能触发呢?这就涉及到滴答中断了。在中断中会OSTimeTickHook()钩子函数,该函数会释放信号量OSTmrSemSignal以便定时器任务OSTmr_Task()执行(回到前面的定时器任务一节)。

//os_cpu_c.c
void  OSTimeTickHook (void)
{
#if OS_APP_HOOKS_EN > 0App_TimeTickHook();    //用户定义的回调函数
#endif#if OS_TMR_EN > 0OSTmrCtr++;    //系统滴答和定时器检测频率是不同的,用一个计数器来进行同步if (OSTmrCtr >= (OS_TICKS_PER_SEC / OS_TMR_CFG_TICKS_PER_SEC)) {OSTmrCtr = 0;OSTmrSignal();     //释放OSTmrSemSignal信号量,可以继续执行定时器任务}
#endif
}

        其中OS_TICKS_PER_SEC表示滴答中断的频率,比如200表示一秒内中断200次。而OS_TMR_CFG_TICKS_PER_SEC表示定时器任务检测的频率,比如40表示一秒内检测40次。二者做个除法,表示滴答中断5次,才进行一次定时器任务,这也符合宏定义本身的定义。

相关文章:

μC/OS-Ⅱ源码学习(7)---软件定时器

快速回顾 μC/OS-Ⅱ中的多任务 μC/OS-Ⅱ源码学习(1)---多任务系统的实现 μC/OS-Ⅱ源码学习(2)---多任务系统的实现(下) μC/OS-Ⅱ源码学习(3)---事件模型 μC/OS-Ⅱ源码学习(4)---信号量 μC/OS-Ⅱ源码学习(5)---消息队列 μC/OS-Ⅱ源码学习(6)---事件标志组 本文进一…...

3D和AR技术在电商行业的应用有哪些?

3D展示和AR技术在电商行业的应用为消费者带来了更为直观、沉浸式的购物体验&#xff0c;显著提升了商品展示效果和销售转化率。以下是3D和AR技术在电商行业的具体应用&#xff1a; 1、商品3D展示&#xff1a; 通过3D技术&#xff0c;商品可以在电商平台上以三维形式呈现&…...

数据增强的几大方式

1. 随机擦除(Random Erasing) 说明 随机在图像中选取一个矩形区域&#xff0c;将其像素值随机化或设为零&#xff0c;以增加模型对部分缺失信息的鲁棒性。 import numpy as np import cv2def random_erasing(image, sl0.02, sh0.2, r10.3):h, w, _ image.shapearea h * wta…...

GraphReader: 将长文本结构化为图,并让 agent 自主探索,结合的大模型长文本处理增强方法

GraphReader: 将长文本结构化为图&#xff0c;并让 agent 自主探索&#xff0c;结合的大模型长文本处理增强方法 论文大纲理解为什么大模型和知识图谱不够&#xff1f;还要多智能体 设计思路数据分析解法拆解全流程核心模式提问为什么传统的长文本处理方法会随着文本长度增加而…...

VTK 模型封闭 closeSurface 补洞, 网格封闭性检测

网格封闭性检测 见&#xff1a; vtk Edges 特征边 提取 网格封闭性检测_vtkfeatureedges-CSDN博客 由于以前做过3D打印模型&#xff0c;要求模型必须是封闭的&#xff0c;原来对模型封闭有研究过&#xff0c;不过没有记录&#xff1b;现在又遇到&#xff0c;整理一下&#xff…...

【译】仅有 Text2SQL 是不够的: 用 TAG 统一人工智能和数据库

原文地址&#xff1a;Text2SQL is Not Enough: Unifying AI and Databases with TAG 摘要 通过数据库为自然语言问题提供服务的人工智能系统有望释放出巨大的价值。此类系统可让用户利用语言模型&#xff08;LM&#xff09;的强大推理和知识能力&#xff0c;以及数据管理系统…...

Java:链接redis报错:NoSuchElementException: Unable to validate object

目录 前言报错信息排查1、确认redis密码设置是否有效2、确认程序配置文件&#xff0c;是否配置了正确的redis登录密码3、检测是否是redis持久化的问题4、确认程序读取到的redis密码没有乱码 原因解决 前言 一个已经上线的项目&#xff0c;生产环境的redis居然没有设置密码&…...

每日一题 334. 递增的三元子序列

334. 递增的三元子序列 使用贪心来找到三个数字 class Solution { public:bool increasingTriplet(vector<int>& nums) {int first INT_MAX;int second INT_MAX;for(int num : nums){if(num < first){first num;}else if(num < second){second num;}els…...

金仓 Kingbase 日常运维 SQL 汇总

金仓 Kingbase 日常运维 SQL 汇总 1 单机启停 sys_ctl start|stop|restart 或指定data路径和端口等 sys_ctl start|stop|restart -D /data/kingbase/data -p 543222 集群启停 sys_monitor start|stop|restart3 修改配置后重新加载 sys_ctl reload4 初始化实例 initdb -E ut…...

JAVA开发ERP时在 PurchaseOrderServiceImpl.java 中添加日志记录进行调试

在 PurchaseOrderServiceImpl.java 中添加日志记录&#xff0c;以便在保存订单时输出参数进行调试。可以使用 Spring 的日志框架&#xff08;SLF4J 和 Logback&#xff09;来实现这一点。 添加日志记录 引入 SLF4J 依赖: 确保项目中已经包含了 SLF4J 和 Logback 的依赖。通常在…...

36.3 grafana-dashboard看图分析

kube-prometheus中的grafana总结 db使用 sqlit&#xff0c;volume类型为emptydir 无法持久化&#xff0c;pod扩缩就重新创建通过configMap设置的prometheus DataSource 通过 prometheus-k8s svc对应的 域名访问下面对应两个prometheus容器&#xff0c;有HA 各个dashboard通过 …...

面试题整理5----进程、线程、协程区别及僵尸进程处理

面试题整理5----进程、线程、协程区别及僵尸进程处理 1. 进程、线程与协程的区别1.1 进程&#xff08;Process&#xff09;1.2 线程&#xff08;Thread&#xff09;1.3 协程&#xff08;Coroutine&#xff09;2. 总结对比 3. 僵尸进程3.1 什么是僵尸进程&#xff1f;3.2 僵尸进…...

【C语言程序设计——基础】顺序结构程序设计(头歌实践教学平台习题)【合集】

目录&#x1f60b; <第1关&#xff1a;顺序结构的应用> 任务描述 相关知识 编程要求 测试说明 我的通关代码: 测试结果&#xff1a; <第2关&#xff1a;交换变量值> 任务描述 相关知识 编程要求 测试说明 我的通关代码: 测试结果&#xff1a; <第…...

LLM大语言模型私有化部署-OpenEuler22.03SP3上容器化部署Dify与Qwen2.5

背景 Dify 是一款开源的大语言模型(LLM) 应用开发平台。其直观的界面结合了 AI 工作流、 RAG 管道、 Agent 、模型管理、可观测性功能等&#xff0c;让您可以快速从原型到生产。相比 LangChain 这类有着锤子、钉子的工具箱开发库&#xff0c; Dify 提供了更接近生产需要的完整…...

C语言中的转义字符

C语言中的转义字符 常见字符ASCII码表...

ilqr算法原理推导及代码实践

目录 一. ilqr原理推导1.1 ilqr问题描述1.2 ilqr算法原理1.3 ilqr算法迭代过程 二. ilqr实践代码 一. ilqr原理推导 1.1 ilqr问题描述 本文参考知乎博主: LQR与iLQR&#xff1a;从理论到实践【详细】 基础LQR只能处理线性系统 (指可以使用 x ( k 1 ) A x ( k ) B u ( k )…...

系列1:基于Centos-8.6部署Kubernetes (1.24-1.30)

每日禅语 “木末芙蓉花&#xff0c;山中发红萼&#xff0c;涧户寂无人&#xff0c;纷纷开自落。​”这是王维的一首诗&#xff0c;名叫《辛夷坞》​。这首诗写的是在辛夷坞这个幽深的山谷里&#xff0c;辛夷花自开自落&#xff0c;平淡得很&#xff0c;既没有生的喜悦&#xff…...

finereport新的数据工厂插件使用场景一 通过accessToken获取数据

1 有两个接口,一个接口获取一个accessToken,一个接口根据accessToken来获取数据。代码示例为: @RequestMapping(value = {"df_test/getAccessToken"},method = {RequestMethod.GET})@ResponseBodypublic String getAccessToken(HttpServletRequest req, HttpServ…...

matlab绘图时设置左、右坐标轴为不同颜色

目录 一、需求描述 二、实现方法 一、需求描述 当图中存在两条曲线&#xff0c;需要对两条曲线进行分别描述时&#xff0c;应设置左、右坐标轴为不同颜色&#xff0c;并设置刻度线&#xff0c;且坐标轴颜色需要和曲线颜色相同。 二、实现方法 1.1、可以实现&#xff1a; 1…...

魏裕雄的JAVA学习总结

JAVA学习总结 Java面向对象程序设计知识总结第1章 初识Java与面向对象程序设计JAVA概述面向对象程序设计思想JAVA开发环境搭建第一个JAVA程序JAVA常用开发工具 第2章 Java编程基础变量与常量运算符与表达式选择结构循环结构方法数组JVM中的堆内存与栈内存 第3章 面向对象程序设…...

深度学习从入门到精通——图像分割实战DeeplabV3

DeeplabV3算法 参数配置关于数据集的配置训练集参数 数据预处理模块DataSet构建模块测试一下数据集去正则化模型加载模块DeepLABV3 参数配置 关于数据集的配置 parser argparse.ArgumentParser()# Datset Optionsparser.add_argument("--data_root", typestr, defa…...

SAP抓取外部https报错SSL handshake处理方法

一、问题描述 SAP执行报表抓取https第三方数据,数据获取失败。 报错消息: SSL handshake with XXX.COM:449 failed: SSSLERR_SSL_READ (-58)#SAPCRYPTO:SSL_read() failed##SapSSLSessionStartNB()==SSSLERR_SSL_READ# SSL:SSL_read() failed (536875120/0x20001070)# …...

Electron-Vue 开发下 dev/prod/webpack server各种路径设置汇总

背景 在实际开发中&#xff0c;我发现团队对于这几个路径的设置上是纯靠猜的&#xff0c;通过一点点地尝试来找到可行的路径&#xff0c;这是不应该的&#xff0c;我们应该很清晰地了解这几个概念&#xff0c;以下通过截图和代码进行细节讲解。 npm run dev 下的路径如何处理&…...

穷举vs暴搜vs深搜vs回溯vs剪枝专题一>全排列II

题目&#xff1a; 解析&#xff1a; 这题设计递归函数&#xff0c;主要把看如何剪枝 代码&#xff1a; class Solution {private List<List<Integer>> ret;private List<Integer> path;private boolean[] check;public List<List<Integer>> p…...

Nginx中Server块配置的详细解析

Nginx中Server块配置的详细解析 一、Server块简介 在Nginx配置文件中&#xff0c;server块是非常关键的部分。它用于定义虚拟主机&#xff0c;一个server块就代表一个虚拟主机。这使得我们可以在一台Nginx服务器上通过不同的配置来处理多个域名或者基于不同端口的服务请求。 …...

【后端面试总结】Redis的三种模式原理介绍及优缺点

Redis作为一款高性能的键值对数据库&#xff0c;提供了多种模式以满足不同场景下的需求。本文将详细介绍Redis的三种主要模式&#xff1a;主从复制模式、哨兵模式&#xff08;Sentinel&#xff09;和集群模式&#xff08;Cluster&#xff09;&#xff0c;包括它们的原理、配置、…...

TCP协议详解

目录 一. TCP协议概述 1. 概念 2. 特点 (1) 面向连接 (2) 可靠传输 (3) 面向字节流 (4) 全双工通信 (5) 流量控制和拥塞控制 二. TCP协议报文格式 1. 源端口号 和 目的端口号 (16位) 2. 序号 和 确认序号 (32位) 3. 首部长度 (4位) 4. 保留位 (6位) 7. 控制位 8.…...

Webpack学习笔记(2)

1.什么是loader? 上图是Webpack打包简易流程&#xff0c;webpack本身只能理解js和json这样的文件&#xff0c;loader可以让webpack解析其他类型文件&#xff0c;并且将文件转换成模块供我们使用。 test识别出那些文件被转换&#xff0c;use定义转换时使用哪个loader转换 上图…...

【漏洞复现】Grafana 安全漏洞(CVE-2024-9264)

🏘️个人主页: 点燃银河尽头的篝火(●’◡’●) 如果文章有帮到你的话记得点赞👍+收藏💗支持一下哦 一、漏洞概述 1.1漏洞简介 漏洞名称:Grafana 安全漏洞 (CVE-2024-9264)漏洞编号:CVE-2024-9264 | CNNVD-202410-1891漏洞类型:命令注入、本地文件包含漏洞威胁等级:…...

C++实现最大字段和

又是一道非常基础且经典的动态规划题目&#xff1a;假设有一个整数序列&#xff0c;我们将连续的几个元素组成的序列称为子段&#xff0c;要求我们得出所有子段和中最大的一个~ 例如&#xff1a;{-2,11&#xff0c;-4,13&#xff0c;-5&#xff0c;-2}&#xff0c;这一序列中&a…...

当我用影刀AI Power做了一个旅游攻略小助手

在线体验地址&#xff1a;旅游攻略小助手https://power.yingdao.com/assistant/ca1dfe1c-9451-450e-a5f1-d270e938a3ad/share 运行效果图展示&#xff1a; 话不多说一起看下效果图&#xff1a; 智能体的截图&#xff1a; 工作流截图&#xff1a; 搭建逻辑&#xff1a; 其实这…...

K8s HPA的常用功能介绍

Kubernetes 的 Horizontal Pod Autoscaler (HPA) 是一种自动扩展功能&#xff0c;用于根据资源使用情况&#xff08;如 CPU、内存等&#xff09;或自定义指标&#xff0c;动态调整 Pod 的副本数量&#xff0c;从而保证应用的性能和资源利用率。 以下是 HPA 的常用功能介绍&…...

web3跨链预言机协议-BandProtocol

项目简介 Band Protocol 项目最初于 2017年成立并建立在 ETH 之上。后于2020年转移到了 Cosmos 网络上&#xff0c;基于 Cosmos SDK 搭建了一条 Band Chain 。这是一条 oracle-specific chain&#xff0c;主要功能是提供跨链预言机服务。Cosmos生态上第一个&#xff0c;也是目…...

Python如何正确解决reCaptcha验证码(9)

前言 本文是该专栏的第73篇,后面会持续分享python爬虫干货知识,记得关注。 我们在处理某些国内外平台项目的时候,相信很多同学或多或少都见过,如下图所示的reCaptcha验证码。 而本文,笔者将重点来介绍在实战项目中,遇到上述中的“reCaptcha验证码”,如何正确去处理并解…...

电商数据采集电商,行业数据分析,平台数据获取|稳定的API接口数据

电商数据采集可以通过多种方式完成&#xff0c;其中包括人工采集、使用电商平台提供的API接口、以及利用爬虫技术等自动化工具。以下是一些常用的电商数据采集方法&#xff1a; 人工采集&#xff1a;人工采集主要是通过基本的“复制粘贴”的方式在电商平台上进行数据的收集&am…...

【java】Executor框架的组成部分

目录 1. 任务&#xff08;Task&#xff09;2. 执行器&#xff08;Executor&#xff09;3. 任务结果&#xff08;Future&#xff09;4. 线程池&#xff08;ThreadPool&#xff09;5. 任务队列&#xff08;Work Queue&#xff09;6. 线程工厂&#xff08;Thread Factory&#xff…...

KMP算法基础

文章一览 前言一、核心思想二、实现步骤三、图解实现四、next数组的实现总结 前言 本栏目将讲解在学习过程中遇到的各种常用算法&#xff0c;深入浅出的讲解算法的用法与使用场景。 那么话不多说&#xff0c;让我们进入第一个算法KMP算法吧&#xff01; 一、核心思想 KMP&am…...

C语言——实现百分制换算为等级分

问题描述&#xff1a;百分制换算为等级分 //百分制换算为等级分#include<stdio.h>void my_function(int x) {if(x>95 && x<100){printf("A");}else if(x>81 && x<94){printf("B");}else if(x>70 && x<8…...

@pytest.fixture() 跟 @pytest.fixture有区别吗?

在iOS UI 自动化工程里面最早我用的是pytest.fixture()&#xff0c;因为在pycharm中联想出来的fixture是带&#xff08;&#xff09;的&#xff0c;后来偶然一次我没有带&#xff08;&#xff09;发现也没有问题&#xff0c;于是详细查了一下pytest.fixture() 和 pytest.fixtur…...

docker run命令大全

docker run命令大全 基本语法常用选项基础选项资源限制网络配置存储卷和挂载环境变量重启策略其他高级选项示例总结docker run 命令是 Docker 中最常用和强大的命令之一,用于创建并启动一个新的容器。该命令支持多种选项和参数,可以满足各种使用场景的需求。以下是 docker ru…...

BootAnimation源码流程分析

BootAnimation流程 bootanimation源码位于frameworks/base/cmds/bootanimation&#xff0c;正如其名&#xff0c;主要功能是加载播放开机动画&#xff0c;是一个C程序&#xff0c;编译生成的可执行文件位于/system/bin 主要逻辑&#xff1a;解析系统路径下的bootanimation.zi…...

Vue前端开发-数据缓存

完成全局性的axios实例对象配置后&#xff0c;则可以在任意一个组件中直接调用这个对象&#xff0c;发送异步请求&#xff0c;获取服务端返回的数据&#xff0c;同时&#xff0c;针对那些不经常变化的数据&#xff0c;可以在请求过程中&#xff0c;进行数据缓存&#xff0c;并根…...

唯品会Android面试题及参考答案

HTTP 和 HTTPS 的区别是什么&#xff1f;你的项目使用的是 HTTP 还是 HTTPS&#xff1f; HTTP 和 HTTPS 主要有以下区别。 首先是安全性。HTTP 是超文本传输协议&#xff0c;数据传输是明文的&#xff0c;这意味着在数据传输过程中&#xff0c;信息很容易被窃取或者篡改。比如&…...

ARM CCA机密计算安全模型之固件启动

安全之安全(security)博客目录导读 目录 1、安全启动(Verified boot) 2、镜像格式和签名方案 3、防回滚 4、离线启动(Off-line boot) 5、CCA HES固件启动流程 6、CCA系统安全域启动过程 7、应用程序PE启动过程 8、稳健性 本节定义了将CCA固件引导至可证明状态的要…...

LeetCode:3376. 破解锁的最少时间 I(DFS回溯 Java)

目录 3376. 破解锁的最少时间 I 题目描述&#xff1a; 实现代码与解析&#xff1a; DFS 原理思路&#xff1a; 3376. 破解锁的最少时间 I 题目描述&#xff1a; Bob 被困在了一个地窖里&#xff0c;他需要破解 n 个锁才能逃出地窖&#xff0c;每一个锁都需要一定的 能量 …...

利用编程获得money?

在当今数字化时代&#xff0c;编程技能为人们开辟了众多赚钱途径。无论你是编程新手还是经验丰富的开发者&#xff0c;都能在广阔的市场中找到适合自己的盈利方式。以下是一份详细的用编程赚钱指南。 一、自由职业平台 像 Upwork、Freelancer 和 Fiverr 等知名自由职业平台&am…...

51c视觉~合集36

我自己的原文哦~ https://blog.51cto.com/whaosoft/12275223 #无监督盲超分算法MLMC 即插即用的解决方案 本文介绍了一种新的无监督盲超分辨率算法MLMC&#xff0c;该算法结合了元学习和马尔可夫链蒙特卡罗核估计&#xff0c;无需监督预训练或参数先验&#xff0c;即可实现…...

域名系统_域名展示出售系统 PC+H5 双端自适应页面

域名系统_域名展示出售系统 PCH5 双端自适应页面 PCH5 双端自适应页面 " 是一套专门用于域名展示和出售的系统源代码 它包含 PC 端和 H5 移动端两个版本&#xff0c;确保在不同设备上都能提供良好的用户体验 这套系统不仅具备基本的域名展示功能&#xff0c;还注重设计…...

Linux C/C++编程的线程结束

【图书推荐】《Linux C与C一线开发实践&#xff08;第2版&#xff09;》_linux c与c一线开发实践pdf-CSDN博客《Linux C与C一线开发实践&#xff08;第2版&#xff09;&#xff08;Linux技术丛书&#xff09;》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com…...

户口本识别、接口识别、python户口本文字识别集成

文字识别技术是数字化转型中一项重要的信息处理手段&#xff0c;正在改变政府机构、企业及个人用户之间的交互方式。通过先进的OCR&#xff08;光学字符识别&#xff09;技术和AI算法的支持&#xff0c;户口本识别不仅简化了信息采集流程&#xff0c;还提高了数据的准确性。 随…...