μ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次,才进行一次定时器任务,这也符合宏定义本身的定义。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CRYPTO密码学

加解密算法/编码 编码base家族unicodeASCII哈希算法MD5 Message Digest AlgorithmnSM3SHA-3GBGB18030GB2312GBKutf家族恺撒二进制分区法DSADSSCRC32校验对称非对称gbk编码h264SEA初探smc动态代码保护四方密码曼彻斯特编码剖析基本概念什么是编码?什么是加密与解密寻找银弹-有没…

【前端】深入探讨 JavaScript 的 reduce() 方法

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 &#x1f4af;前言&#x1f4af;什么是 reduce() 方法&#xff1f;定义与核心概念语法结构参数解析返回值 &#x1f4af;基础用法与示例示例 1&#xff1a;计算数组元素的和解析 示例 2&#xff1a;统计…

postman关联接口用于登录(验证码会变情况)

目录 一、介绍 二、操作步骤 (一)Fiddler抓取到登录信息 (二)postman发送请求 新建请求一&#xff1a;登录值请求 (三)易变值赋值固定住 新建请求二&#xff1a;易变值验证码(uuid)请求 切换到请求一里面进行赋值绑定 一、介绍 接口有两种形式&#xff0c;一种是单…

SSC338Q SigmaStar 摄像头主控芯片

SSC338Q 是一款由 SigmaStar&#xff08;星宸科技&#xff09;推出的高集成度多媒体系统级芯片&#xff08;SoC&#xff09;&#xff0c;广泛应用于高分辨率智能视频录制设备&#xff0c;如 IP 摄像机、车载摄像机和 USB 摄像机。 处理器&#xff1a; CPU&#xff1a;32 位双…

苹果将推出超薄和折叠款iPhone,2024年带来哪些变化?

苹果公司&#xff08;AAPL&#xff09;近日宣布&#xff0c;将对其iPhone系列进行重大升级&#xff0c;以应对当前市场中的销量压力。这一改变&#xff0c;或许会为苹果带来新的增长动力。那么&#xff0c;苹果的2024年新iPhone究竟有哪些亮点呢&#xff1f;下面我们来详细了解…

QML 粒子模拟

粒子模拟 粒子模拟 粒子模拟的核心是粒子系统&#xff08;ParticleSystem&#xff09;, 它控制共享时间线。一个粒子使用发射器元素&#xff08;Emitter&#xff09;发射&#xff0c; 使用粒子画笔&#xff08;ParticlePainter&#xff09;实现可视化&#xff0c; 它可以是一张…

Java中的Consumer接口应该如何使用(通俗易懂图解)

应用场景&#xff1a; 第一次程序员A写好了个基础的遍历方法&#xff1a; public class Demo1 {public static void main(String[] args) {//假设main方法为程序员B写的,此时需要去调用A写好的一个遍历方法//1.如果此时B突然发现想将字符串以小写的形式打印出来&#xff0c;则…

WPF+MVVM案例实战与特效(四十四)- WPF多语言支持全解析:轻松实现国际化应用

文章目录 1、引言2、案例效果3、准备工作1、创建项目结构2、代码实现1、语言资源2、资源引用3、页面功能4、实现效果3、总结1、引言 在当今全球化的背景下,开发一个多语言支持的应用程序变得越来越重要。WPF提供了强大的功能来实现应用程序的国际化和本地化。本文将详细介绍如…

Java爬虫大冒险:如何征服1688商品搜索之巅

在这个信息爆炸的时代&#xff0c;数据就是力量。对于电商平台而言&#xff0c;数据更是金矿。今天&#xff0c;我们要踏上一场Java爬虫的冒险之旅&#xff0c;目标是征服1688这个B2B电商巨头&#xff0c;获取按关键字搜索的商品信息。这不仅是技术的挑战&#xff0c;更是智慧的…

《Django 5 By Example》读后感

一、 为什么选择这本书&#xff1f; 本人的工作方向为Python Web方向&#xff0c;想了解下今年该方向有哪些新书出版&#xff0c;遂上packt出版社网站上看了看&#xff0c;发现这本书出版时间比较新(2024年9月)&#xff0c;那就它了。 从2024年11月11日至2024年12月18日期间&…

TouchGFX移植(3)增加SDRAM驱动

一&#xff09;SDRAM驱动增加到工程中 1&#xff09;加入驱动sdram.c文件&#xff0c;文件在上节课里有源代码。 2&#xff09;在fmc.c文件里指定位置增加代码 SDRAM_Init();另外需要包含文件&#xff1a;#include “sdram.h” /* USER CODE BEGIN 0 / #include “sdram.h” …

Apache Kylin最简单的解析、了解

官网&#xff1a;Overview | Apache Kylin 一、Apache Kylin是什么&#xff1f; 由中国团队研发具有浓厚的中国韵味&#xff0c;使用神兽麒麟&#xff08;kylin&#xff09;为名 的一个OLAP多维数据分析引擎:&#xff08;据官方给出的数据&#xff09; 亚秒级响应&#xff…

【Token】校验、会话技术、登录请求、拦截器【期末实训】实战项目学生和班级管理系统\Day15-后端Web实战(登录认证)\讲义

登录认证 在前面的课程中&#xff0c;我们已经实现了部门管理、员工管理的基本功能&#xff0c;但是大家会发现&#xff0c;我们并没有登录&#xff0c;就直接访问到了Tlias智能学习辅助系统的后台。 这是不安全的&#xff0c;所以我们今天的主题就是登录认证。 最终我们要实现…

前端(组件传参案例)

父组件(商品详情页) 子组件上边放大图 底下缩小轮播图 需求分析&#xff1a;父组件获取图片数据&#xff0c;传给底下子组件进行进行轮播&#xff0c;实现父组件给子组件传参。然后底下子组件轮播后&#xff0c;把当前图片下标给父组件&#xff0c;实现子组件给父组件传参。父组…

【Linux网络编程】第十二弹---构建与优化HTTP请求处理:从HttpRequest到HttpServer的实战

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【Linux网络编程】 目录 1、HttpRequest类 1.1、基本结构 1.2、构造析构函数 1.3、反序列化函数 1.4、GetLine() 1.5、打印函数…

使用k6进行kafka负载测试

1.安装环境 kafka环境 参考Docker搭建kafka环境-CSDN博客 xk6-kafka环境 ./xk6 build --with github.com/mostafa/xk6-kafkalatest 查看安装情况 2.编写脚本 test_kafka.js // Either import the module object import * as kafka from "k6/x/kafka";// Or in…

Linux内存管理 --- 进程创建虚拟地址的过程

文章目录 前言一、进程虚拟地址空间二、进程号1的创建过程2.1 kernel_init2.2 kernel_execve2.2.1 alloc_bprm2.2.2 bprm_stack_limits2.2.3 copy_string_kernel2.2.4 bprm_execve 2.3 bprm_execve2.3.1 prepare_binprm2.3.2 load_binary2.3.3 interpreter 三、load_elf_binary…

uniapp blob格式转换为video .mp4文件使用ffmpeg工具

前言 介绍一下这三种对象使用场景 您前端一旦涉及到文件或图片上传Q到服务器&#xff0c;就势必离不了 Blob/File /base64 三种主流的类型它们之间 互转 也成了常态 Blob - FileBlob -Base64Base64 - BlobFile-Base64Base64 _ File uniapp 上传文件 现在已获取到了blob格式的…

springboot447教师薪酬管理系统(论文+源码)_kaic

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;老师信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广…

三、ubuntu18.04安装docker

1.使用默认ubuntu存储库安装docker 更新软件存储库 更新本地软件数据库确保可以访问最新版本。打开终端输入&#xff1a;sudo apt-get update 卸载旧版本的docker 建议继续之前卸载任何旧的docker软件。打开终端输入&#xff1a;sudo apt-get remove docker docker-engine …