(学习日记)2024.03.12:UCOSIII第十四节:时基列表

写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。


标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。


点击此处进入学习日记的总目录

2024.03.12

  • 二十八、UCOSIII:时基列表
    • 1、实现时基列表
      • 1. 定义时基列表变量
      • 2. 修改任务控制块TCB
    • 2、实现时基列表相关函数
      • 1. OS_TickListInit()函数
      • 2. OS_TickListInsert()函数
      • 3. OS_TickListRemove()函数
      • 4. OS_TickListUpdate()函数
    • 3、修改OSTimeDly()函数
    • 4、修改OSTimeTick()函数

二十八、UCOSIII:时基列表

从本章开始,我们在OS中加入时基列表。
时基列表是跟时间相关的,处于延时的任务和等待事件有超时限制的任务都会从就绪列表中移除,然后插入时基列表。
时基列表在OSTimeTick中更新,如果任务的延时时间结束或者超时到期,就会让任务就绪,从时基列表移除,插入就绪列表。

到目前为止,我们在OS中只实现了两个列表,一个是就绪列表,一个是本章将要实现的时基列表,在本章之前,任务要么在就绪列表,要么在时基列表。

1、实现时基列表

1. 定义时基列表变量

时基列表在代码层面上由全局数组OSCfg_TickWheel[]和全局变量OSTickCtr构成,一个空的时基列表示意图见图
在这里插入图片描述

/* 时基列表大小,在os_cfg_app.h 定义 */
#define  OS_CFG_TICK_WHEEL_SIZE           17u
/* 在os_cfg_app.c 定义 */
/* 时基列表 */
							//(1)(2)
OS_TICK_SPOKE  OSCfg_TickWheel[OS_CFG_TICK_WHEEL_SIZE];
/* 时基列表大小 */
OS_OBJ_QTY const OSCfg_TickWheelSize = (OS_OBJ_QTY  )OS_CFG_TICK_WHEEL_SIZE;
/* 在os.h中声明 */
/* 时基列表 */
extern  OS_TICK_SPOKE  OSCfg_TickWheel[];
/* 时基列表大小 */
extern  OS_OBJ_QTY    const OSCfg_TickWheelSize;


/* Tick 计数器,在os.h中定义 */
OS_EXT            OS_TICK                OSTickCtr;		//(3)
  • (1)OS_TICK_SPOKE为时基列表数组OSCfg_TickWheel[]的数据类型, 在os.h文件定义
typedefstruct  os_tick_spoke       OS_TICK_SPOKE;		
//在μC/OS-III中,内核对象的数据类型都会用大写字母重新定义。

struct  os_tick_spoke {
    OS_TCB              *FirstPtr;		
    //时基列表OSCfg_TickWheel[]的每个成员都包含一条单向链表, 被插入该条链表的TCB会按照延时时间做升序排列。FirstPtr用于指向这条单向链表的第一个节点。
    OS_OBJ_QTY           NbrEntries;	
    //时基列表OSCfg_TickWheel[]的每个成员都包含一条单向链表, NbrEntries表示该条单向链表当前有多少个节点。
    OS_OBJ_QTY           NbrEntriesMax;	
    //时基列表OSCfg_TickWheel[]的每个成员都包含一条单向链表, NbrEntriesMax记录该条单向链表最多的时候有多少个节点, 在增加节点的时候会刷新,在删除节点的时候不刷新。
};
  • (2):OS_CFG_TICK_WHEEL_SIZE是一个宏, 在os_cfg_app.h中定义,用于控制时基列表的大小。
    OS_CFG_TICK_WHEEL_SIZE的推荐值为任务数/4,不推荐使用偶数,如果算出来是偶数,则加1变成质数,实际上质数是一个很好的选择。
  • (3):OSTickCtrSysTick周期计数器, 记录系统启动到现在或者从上一次复位到现在经过了多少个SysTick周期。

2. 修改任务控制块TCB

时基列表OSCfg_TickWheel[]的每个成员都包含一条单向链表,被插入该条链表的TCB会按照延时时间做升序排列,为了TCB能按照延时时间从小到大串接在一起, 需要在TCB中加入几个成员
在这里插入图片描述

struct os_tcb {
    CPU_STK         *StkPtr;
    CPU_STK_SIZE    StkSize;

    /* 任务延时周期个数 */
    OS_TICK         TaskDelayTicks;

    /* 任务优先级 */
    OS_PRIO         Prio;

    /* 就绪列表双向链表的下一个指针 */
    OS_TCB          *NextPtr;
    /* 就绪列表双向链表的前一个指针 */
    OS_TCB          *PrevPtr;

    /* 时基列表相关字段 */
    OS_TCB          *TickNextPtr;			//(1)
    OS_TCB          *TickPrevPtr;			//(2)
    OS_TICK_SPOKE   *TickSpokePtr;			//(5)

    OS_TICK         TickCtrMatch;			//(4)
    OS_TICK         TickRemain;				//(3)
};

带序号的字段可以配合上图一起理解,这样会比较容易。
上图是在时基列表 OSCfg_TickWheel[]索引11这条链表里面插入了两个TCB,一个需要延时1个时钟周期,另外一个需要延时13个时钟周期。

  • (1):TickNextPtr用于指向链表中的下一个TCB节点。
  • (2):TickPrevPtr用于指向链表中的上一个TCB节点。
  • (3):TickRemain用于设置任务还需要等待多少个时钟周期,每到来一个时钟周期,该值会递减。
  • (4):TickCtrMatch的值等于时基计数器OSTickCtr的值加上TickRemain的值, 当TickCtrMatch的值等于OSTickCtr的值的时候,表示等待到期,TCB会从链表中删除。
  • (5):每个被插入链表的TCB都包含一个字段TickSpokePtr,用于回指到链表的根部。

2、实现时基列表相关函数

时基列表相关函数在os_tick.c实现,在os.h中声明。

1. OS_TickListInit()函数

OS_TickListInit()函数用于初始化时基列表,即将全局变量OSCfg_TickWheel[]的数据域全部初始化为0

/* 初始化时基列表的数据域 */
void  OS_TickListInit (void)
{
    OS_TICK_SPOKE_IX   i;
    OS_TICK_SPOKE     *p_spoke;

    for (i = 0u; i < OSCfg_TickWheelSize; i++) {
        p_spoke                = (OS_TICK_SPOKE *)&OSCfg_TickWheel[i];
        p_spoke->FirstPtr      = (OS_TCB        *)0;
        p_spoke->NbrEntries    = (OS_OBJ_QTY     )0u;
        p_spoke->NbrEntriesMax = (OS_OBJ_QTY     )0u;
    }
}

在这里插入图片描述

2. OS_TickListInsert()函数

OS_TickListInsert()函数用于往时基列表中插入一个任务TCB
在这里插入图片描述

/* 将一个任务插入时基列表,根据延时时间的大小升序排列 */
void  OS_TickListInsert (OS_TCB *p_tcb,OS_TICK time)
{
    OS_TICK_SPOKE_IX   spoke;
    OS_TICK_SPOKE     *p_spoke;
    OS_TCB            *p_tcb0;
    OS_TCB            *p_tcb1;

    p_tcb->TickCtrMatch = OSTickCtr + time;			//(1)
    p_tcb->TickRemain   = time;			//(2)

    spoke   = (OS_TICK_SPOKE_IX)(p_tcb->TickCtrMatch % OSCfg_TickWheelSize);			//(3)
    p_spoke = &OSCfg_TickWheel[spoke];			//(4)

    /* 插入 OSCfg_TickWheel[spoke] 的第一个节点 */
    if (p_spoke->NbrEntries == (OS_OBJ_QTY)0u) 			//(5)
    {
        p_tcb->TickNextPtr   = (OS_TCB   *)0;
        p_tcb->TickPrevPtr   = (OS_TCB   *)0;
        p_spoke->FirstPtr    =  p_tcb;
        p_spoke->NbrEntries  = (OS_OBJ_QTY)1u;
    }
    /* 如果插入的不是第一个节点,则按照TickRemain大小升序排列 */
    else 			//(6)
    {
        /* 获取第一个节点指针 */
        p_tcb1 = p_spoke->FirstPtr;
        while (p_tcb1 != (OS_TCB *)0)
        {
            /* 计算比较节点的剩余时间 */
            p_tcb1->TickRemain = p_tcb1->TickCtrMatch - OSTickCtr;

            /* 插入比较节点的后面 */
            if (p_tcb->TickRemain > p_tcb1->TickRemain)
            {
                if (p_tcb1->TickNextPtr != (OS_TCB *)0)
                {
                    /* 寻找下一个比较节点 */
                    p_tcb1 =  p_tcb1->TickNextPtr;
                }
                else
                {  /* 在最后一个节点插入 */
                    p_tcb->TickNextPtr   = (OS_TCB *)0;
                    p_tcb->TickPrevPtr   =  p_tcb1;
                    p_tcb1->TickNextPtr  =  p_tcb;
                    p_tcb1               = (OS_TCB *)0;			//(7)
                }
            }
            /* 插入比较节点的前面 */
            else
            {
                /* 在第一个节点插入 */
                if (p_tcb1->TickPrevPtr == (OS_TCB *)0) {
                    p_tcb->TickPrevPtr   = (OS_TCB *)0;
                    p_tcb->TickNextPtr   =  p_tcb1;
                    p_tcb1->TickPrevPtr  =  p_tcb;
                    p_spoke->FirstPtr    =  p_tcb;
                }
                else
                {
                    /* 插入两个节点之间 */
                    p_tcb0               =  p_tcb1->TickPrevPtr;
                    p_tcb->TickPrevPtr   =  p_tcb0;
                    p_tcb->TickNextPtr   =  p_tcb1;
                    p_tcb0->TickNextPtr  =  p_tcb;
                    p_tcb1->TickPrevPtr  =  p_tcb;
                }
                /* 跳出while循环 */
                p_tcb1 = (OS_TCB *)0;			//(8)
            }
        }

        /* 节点成功插入 */
        p_spoke->NbrEntries++;			//(9)
    }

    /* 刷新NbrEntriesMax的值 */
    if (p_spoke->NbrEntriesMax < p_spoke->NbrEntries) 			//(10)
    {
        p_spoke->NbrEntriesMax = p_spoke->NbrEntries;
    }

    /* 任务TCB中的TickSpokePtr回指根节点 */
    p_tcb->TickSpokePtr = p_spoke;			//(11)
}
  • (1):TickCtrMatch的值等于当前时基计数器的值OSTickCtr加上任务要延时的时间timetime由函数形参传进来。
    OSTickCtr是一个全局变量, 记录的是系统自启动以来或者自上次复位以来经过了多少个SysTick周期。
    OSTickCtr的值每经过一个SysTick周期其值就加一,当TickCtrMatch的值与其相等时,就表示任务等待时间到期。
  • (2):将任务需要延时的时间time保存到TCBTickRemain, 它表示任务还需要延时多少个SysTick周期,每到来一个SysTick周期,TickRemain会减一。
  • (3):由任务的TickCtrMatch 对时基列表的大小OSCfg_TickWheelSize进行求余操作, 得出的值spoke作为时基列表OSCfg_TickWheel[]的索引。
    只要是任务的TickCtrMatchOSCfg_TickWheelSize求余后得到的值spoke相等, 那么任务的TCB就会被插入OSCfg_TickWheel[spoke]下的单向链表中,节点按照任务的TickCtrMatch值做升序排列。
    举例:在上图中,时基列表OSCfg_TickWheel[]的大小OSCfg_TickWheelSize等于12, 当前时基计数器OSTickCtr的值为10,有三个任务分别需要延时TickTemain=1TickTemain=23TickTemain=25个时钟周期, 三个任务的TickRemain加上OSTickCtr可分别得出它们的TickCtrMatch等于11、23和35, 这三个任务的TickCtrMatchOSCfg_TickWheelSize求余操作后的值spoke都等于11,所以这三个任务的TCB会被插入OSCfg_TickWheel[11]下的同一条链表, 节点顺序根据TickCtrMatch的值做升序排列。
  • (4):根据刚刚算出的索引值spoke,获取到该索引值下的成员的地址, 也叫根指针,因为该索引下对应的成员OSCfg_TickWheel[spoke]会维护一条双向的链表。
  • (5):将TCB插入链表中分两种情况,第一是当前链表是空的, 插入的节点将成为第一个节点,这个处理非常简单;第二是当前链表已经有节点。
  • (6):当前的链表中已经有节点,插入的时候则根据TickCtrMatch的值做升序排列, 插入的时候分三种情况,第一是在最后一个节点之间插入, 第二是在第一个节点插入,第三是在两个节点之间插入。
  • (7)(8):节点成功插入p_tcb1指针,跳出while循环
  • (9):节点成功插入,记录当前链表节点个数的计数器NbrEntries加一。
  • (10):刷新NbrEntriesMax的值,NbrEntriesMax用于记录当前链表曾经最多有多少个节点, 只有在增加节点的时候才刷新,在删除节点的时候是不刷新的。
  • (11):任务TCB被成功插入链表,TCB中的TickSpokePtr回指所在链表的根指针。

3. OS_TickListRemove()函数

OS_TickListRemove()用于从时基列表删除一个指定的TCB节点

/* 从时基列表中移除一个任务 */
void  OS_TickListRemove (OS_TCB  *p_tcb)
{
    OS_TICK_SPOKE  *p_spoke;
    OS_TCB         *p_tcb1;
    OS_TCB         *p_tcb2;

    /* 获取任务TCB所在链表的根指针 */
    p_spoke = p_tcb->TickSpokePtr;			//(1)

    /* 确保任务在链表中 */
    if (p_spoke != (OS_TICK_SPOKE *)0)
    {
        /* 将剩余时间清零 */
        p_tcb->TickRemain = (OS_TICK)0u;

        /* 要移除的刚好是第一个节点 */
        if (p_spoke->FirstPtr == p_tcb) 			//(2)
        {
            /* 更新第一个节点,原来的第一个节点需要被移除 */
            p_tcb1            = (OS_TCB *)p_tcb->TickNextPtr;
            p_spoke->FirstPtr = p_tcb1;
            if (p_tcb1 != (OS_TCB *)0)
            {
                p_tcb1->TickPrevPtr = (OS_TCB *)0;
            }
        }
        /* 要移除的不是第一个节点 */			//(3)
        else
        {
            /* 保存要移除的节点的前后节点的指针 */
            p_tcb1              = p_tcb->TickPrevPtr;
            p_tcb2              = p_tcb->TickNextPtr;

            /* 节点移除,将节点前后的两个节点连接在一起 */
            p_tcb1->TickNextPtr = p_tcb2;
            if (p_tcb2 != (OS_TCB *)0)
            {
                p_tcb2->TickPrevPtr = p_tcb1;
            }
        }

        /* 复位任务TCB中时基列表相关的字段成员 */			//(4)
        p_tcb->TickNextPtr  = (OS_TCB        *)0;
        p_tcb->TickPrevPtr  = (OS_TCB        *)0;
        p_tcb->TickSpokePtr = (OS_TICK_SPOKE *)0;
        p_tcb->TickCtrMatch = (OS_TICK        )0u;

        /* 节点减1 */
        p_spoke->NbrEntries--;			//(5)
    }
}
  • (1):获取任务TCB所在链表的根指针。
  • (2):要删除的节点是链表的第一个节点,这个操作很好处理,只需更新下第一个节点即可。
  • (3):要删除的节点不是链表的第一个节点,则先保存要删除的节点的前后节点,然后把这前后两个节点相连即可。
  • (4):复位任务TCB中时基列表相关的字段成员。
  • (5):节点删除成功,链表中的节点计数器NbrEntries减一。

4. OS_TickListUpdate()函数

OS_TickListUpdate()在每个SysTick周期到来时在OSTimeTick()被调用,用于更新时基计数器OSTickCtr, 扫描时基列表中的任务延时是否到期

void  OS_TickListUpdate (void)
{
    OS_TICK_SPOKE_IX   spoke;
    OS_TICK_SPOKE     *p_spoke;
    OS_TCB            *p_tcb;
    OS_TCB            *p_tcb_next;
    CPU_BOOLEAN        done;

    CPU_SR_ALLOC();

    /* 进入临界段 */
    OS_CRITICAL_ENTER();

    /* 时基计数器++ */
    OSTickCtr++;			//(1)

    spoke    = (OS_TICK_SPOKE_IX)(OSTickCtr % OSCfg_TickWheelSize);			//(2)
    p_spoke  = &OSCfg_TickWheel[spoke];

    p_tcb    = p_spoke->FirstPtr;
    done     = DEF_FALSE;

    while (done == DEF_FALSE)
    {
        if (p_tcb != (OS_TCB *)0) 			//(3)
        {
            p_tcb_next = p_tcb->TickNextPtr;

            p_tcb->TickRemain = p_tcb->TickCtrMatch - OSTickCtr;			//(4)

            /* 节点延时时间到 */
            if (OSTickCtr == p_tcb->TickCtrMatch) 			//(5)
            {
                /* 让任务就绪 */
                OS_TaskRdy(p_tcb);
            }
            else 			//(6)
            {
                /* 如果第一个节点延时期未满,则退出while循环
                因为链表是根据升序排列的,第一个节点延时期未满,那后面的肯定未满 */
                done = DEF_TRUE;
            }

            /* 如果第一个节点延时期满,则继续遍历链表,看看还有没有延时期满的任务
            如果有,则让它就绪 */
            p_tcb = p_tcb_next;			//(7)
        }
        else
        {
            done  = DEF_TRUE;			//(8)
        }
    }

    /* 退出临界段 */
    OS_CRITICAL_EXIT();
}
  • (1):每到来一个SysTick时钟周期,时基计数器OSTickCtr都要加一操作。
  • (2):计算要扫描的时基列表的索引,每次只扫描一条链表。
    时基列表里面有可能有多条链表,为啥只扫描其中一条链表就可以?
    因为任务在插入时基列表的时候, 插入的索引值spoke_insert是通过TickCtrMatchOSCfg_TickWheelSize求余得出。
    现在需要扫描的索引值spoke_update是通过OSTickCtrOSCfg_TickWheelSize求余得出, TickCtrMatch的值等于OSTickCtr加上TickRemain,只有在经过TickRemain个时钟周期后, spoke_update的值才有可能等于spoke_insert
    如果算出的spoke_update小于spoke_insert, 且OSCfg_TickWheel[spoke_update]下的链表的任务没有到期,那后面的肯定都没有到期,不用继续扫描。

在这里插入图片描述
举例,在上图时基列表中有三个TCB ,时基列表OSCfg_TickWheel[]的大小OSCfg_TickWheelSize等于12, 当前时基计数器OSTickCtr的值为7,有三个任务分别需要延时TickTemain=16、TickTemain=28和TickTemain=40个时钟周期, 三个任务的TickRemain加上OSTickCtr可分别得出它们的TickCtrMatch等于23、35和47
这三个任务的TickCtrMatch对OSCfg_TickWheelSize求余操作后的值spoke都等于11, 所以这三个任务的TCB会被插入OSCfg_TickWheel[11]下的同一条链表,节点顺序根据TickCtrMatch的值做升序排列。

当下一个SysTick时钟周期到来的时候,会调用OS_TickListUpdate()函数,这时OSTickCtr加一操作后等于8, 对OSCfg_TickWheelSize(等于12)求余算得要扫描更新的索引值spoke_update等8,则对OSCfg_TickWheel[8]下面的链表进行扫描, 从 图时基列表中有三个TCB 可以得知,8这个索引下没有节点,则直接退出,刚刚插入的三个TCB是在OSCfg_TickWheel[11]下的链表, 根本不用扫描,因为时间只是刚刚过了1个时钟周期而已,远远没有达到他们需要的延时时间。

  • (3):判断链表是否为空,为空则跳转到第(8)步骤。
  • (4):链表不为空,递减第一个节点的TickRemain
  • (5):判断第一个节点的延时时间是否到,如果到期,让任务就绪, 即将任务从时基列表删除,插入就绪列表,这两步由函数OS_TaskRdy()来完成, 该函数在os_core.c中定义,具体实现见 代码清单:时基列表-8。
void  OS_TaskRdy (OS_TCB  *p_tcb)
{
    /* 从时基列表删除 */
    OS_TickListRemove(p_tcb);

    /* 插入就绪列表 */
    OS_RdyListInsert(p_tcb);
}
  • (6):如果第一个节点延时期未满,则退出while循环, 因为链表是根据升序排列的,第一个节点延时期未满,那后面的肯定未满。
  • (7):如果第一个节点延时到期,则继续判断下一个节点延时是否到期。
  • (8):链表为空,退出扫描,因为其他还没到期。

3、修改OSTimeDly()函数

加入时基列表之后,OSTimeDly()函数需要被修改,迭代的代码已经用条件编译屏蔽。
在这里插入图片描述

void  OSTimeDly(OS_TICK dly)
{
    CPU_SR_ALLOC();

    /* 进入临界区 */
    OS_CRITICAL_ENTER();
#if 0
    /* 设置延时时间 */
    OSTCBCurPtr->TaskDelayTicks = dly;

    /* 从就绪列表中移除 */
    //OS_RdyListRemove(OSTCBCurPtr);
    OS_PrioRemove(OSTCBCurPtr->Prio);
#endif

    /* 插入时基列表 */
    OS_TickListInsert(OSTCBCurPtr, dly);

    /* 从就绪列表移除 */
    OS_RdyListRemove(OSTCBCurPtr);

    /* 退出临界区 */
    OS_CRITICAL_EXIT();

    /* 任务调度 */
    OSSched();
}

4、修改OSTimeTick()函数

加入时基列表之后,OSTimeTick()函数需要被修改,被迭代的代码已经用条件编译屏蔽。

void  OSTimeTick (void)
{
#if 0
    unsigned int i;
    CPU_SR_ALLOC();

    /* 进入临界区 */
    OS_CRITICAL_ENTER();

    for (i=0; i<OS_CFG_PRIO_MAX; i++)
    {
        if (OSRdyList[i].HeadPtr->TaskDelayTicks > 0)
        {
            OSRdyList[i].HeadPtr->TaskDelayTicks --;
            if (OSRdyList[i].HeadPtr->TaskDelayTicks == 0)
            {
                /* 为0则表示延时时间到,让任务就绪 */
                //OS_RdyListInsert (OSRdyList[i].HeadPtr);
                OS_PrioInsert(i);
            }
        }
    }

    /* 退出临界区 */
    OS_CRITICAL_EXIT();

#endif

    /* 更新时基列表 */
    OS_TickListUpdate();

    /* 任务调度 */
    OSSched();
}

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

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

相关文章

从大模型到Agentscope——分布式Multi-Agent应用开发与部署

目录 Why需要分布式 案例 多进程的分布书版本能快速提升速度 分布式的挑战 AgentScope分布式解决 方案 实现RPC Agent 基于Actor模式的并行调度缺点&#xff1a;需要Agent内部决定消息传递目标 被调用的Agent立即返回占位符placeholder to_dist: 开启自动将单机进行扩展…

数据结构/C++:红黑树

数据结构/C&#xff1a;红黑树 概念实现基本结构插入uncle为红色节点uncle为黑色节点 总代码展示 概念 红黑树是一种二叉搜索树&#xff0c;一般的二叉搜索会发送不平衡现象&#xff0c;导致搜索效率下降&#xff0c;于是学者们开始探索如何让二叉搜索树保持平衡&#xff0c;这…

字符函数以及字符串函数

1.strlen的使用和模拟实现 • 字符串以 \0 作为结束标志&#xff0c;strlen函数返回的是在字符串中 \0 前⾯出现的字符个数&#xff08;不包 含 \0 )。 • 参数指向的字符串必须要以 \0 结束。 • 注意函数的返回值为size_t&#xff0c;是⽆符号的&#xff08; 易错 &#xff…

springboot基于Hadoop技术下的校园二手交易系统的设计与实现

摘 要 自从新冠疫情爆发以来&#xff0c;各个线下实体越来越难做&#xff0c;线下购物的人也越来越少&#xff0c;随之带来的是一些不必要的浪费&#xff0c;尤其是即将毕业的大学生&#xff0c;各种用品不方便携带走导致被遗弃&#xff0c;造成大量的浪费。本系统目的就是让毕…

引领人工智能时代的应用安全

当生成式人工智能开始展现其编程能力时&#xff0c;开发人员自然会求助于它来帮助他们高效地编写代码。但随着大量人工智能生成的代码首次进入代码库&#xff0c;安全领导者现在正面临着人工智能对整体安全态势的潜在影响。 无论是人工智能被用来将恶意代码插入开源项目&#…

自定义协议

应用层 有许多现成的协议(HTTP协议做网站必备),也有许多需要程序员自定义的协议. 1.自定义协议 自定义协议: 1.明确传递的信息是什么 2.约定好信息按照什么样的格式来组织成二进制字符串 举个例子: 当我们点外卖时,打开软件,会显示商家列表,列表中有很多项,每一项都包含了一…

SQLiteC/C++接口详细介绍之sqlite3类(十四)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;十三&#xff09; 下一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;十五&#xff09; 43.sqlite3_preupdate_hook sqlite3_preup…

ClickHouse:一款高效且强大的列式数据库管理系统

ClickHouse是一款开源的列式数据库管理系统&#xff0c;专为大规模数据仓库和数据分析应用而设计。它允许用户快速地存储和处理海量数据&#xff0c;同时提供了简单易用的SQL接口。本文将介绍ClickHouse的概念、技术原理以及使用案例&#xff0c;并探讨其优势和挑战。 一、引言…

【leetcode热题】 分数到小数

给定两个整数&#xff0c;分别表示分数的分子 numerator 和分母 denominator&#xff0c;以 字符串形式返回小数 。 如果小数部分为循环小数&#xff0c;则将循环的部分括在括号内。 如果存在多个答案&#xff0c;只需返回 任意一个 。 对于所有给定的输入&#xff0c;保证 …

数字电子技术实验(五)

单选题 1.基本RS触发器&#xff08;与非门组成&#xff09;的状态是哪一个端口的状态&#xff1f; 答案&#xff1a;C 评语&#xff1a;10分 单选题 2. D触发器&#xff08;74LS 74&#xff09;状态方程的成立条件&#xff1f; A. CP端口高电平。 B. CP端口低电平。 C. C…

C#操作MySQL从入门到精通(4)——连接MySQL数据库

前言 我们创建好数据库、建立好数据库的表以后&#xff0c;我们就需要访问数据库了&#xff0c;比如将数据插入数据库的某张表中等一系列操作&#xff0c;在进行这些操作之前我们需要连接上数据库&#xff0c;本文就是详细讲解如何连接MySQL数据库的。 1、使用Navicat Premiu…

Visual Studio项目模板的创建与使用

Visual Studio项目模板的创建、使用、删除 创建模板项目模板的使用模板的删除 创建模板 点击项目&#xff0c;点击导出模板 选择你要创建哪个项目的项目模板&#xff0c;点击下一步 输入你的模板名称并添加模板说明&#xff0c;方便记忆 项目模板的使用 点击创建新项目 输入刚刚…

Linux-centos如何搭建yum源仓库

1.本地搭建&#xff08;无需连接外网&#xff09; 1.1检查网络配置&#xff0c;及网络连接 打开虚拟机&#xff0c;点击【编辑——虚拟网络编辑器】 点击【仅主机模式】查看子网段是否和局内IP匹配 进入局内&#xff0c;查看网络IP是否在你上述设置的网段内&#xff0c;如果不…

MyBatis plus自动生成代码

1.pom文件配置 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3</version> </dependency> <dependency><groupId>com.baomidou</groupId>…

VLC抓取m3u8视频

前言 最近想看一些网络视频&#xff0c;但是很多时候网页上是m3u8推流的&#xff0c;如果在线看&#xff0c;速度又慢&#xff0c;所以就想下载下来&#xff0c;就想到了VLC的推流&#xff0c;转换能力&#xff0c;查阅资料&#xff0c;加上实践&#xff0c;总结心得。 设置中…

<Linux> 线程的同步与互斥

目录 前言&#xff1a; 一、资源共享问题 &#xff08;一&#xff09;多线程并发访问 &#xff08;二&#xff09;临界资源与临界区 &#xff08;三&#xff09;“锁” 是什么 二、多线程抢票场景 &#xff08;一&#xff09;并发抢票 &#xff08;二&#xff09;并发访…

flink1.18.0 自定义函数 接收row类型的参数

比如sql中某字段类型 array<row<f1 string,f2 string,f3 string,f4 bigint>> 现在需要编写 tableFunction 需要接受的参数如上 解决方案 用户定义函数|阿帕奇弗林克 --- User-defined Functions | Apache Flink

React 实现下拉刷新效果

简介 本文基于react实现下拉刷新效果&#xff0c;在下拉的时候会进入loading状态。 实现效果 效果如上图所示&#xff0c;在下拉到底部时候&#xff0c;会出现loading条&#xff0c;在处理完成后loading条消失。 具体代码 布局 & 逻辑 import {useRef, useState} from …

基于Java+Springmvc+vue+element实现高校心理健康系统详细设计和实现

基于JavaSpringmvcvueelement实现高校心理健康系统详细设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐…

Docker 笔记(五)--链接

这篇笔记记录了Docker 的Link。 官方文档&#xff1a; Legacy container links - Communication across links 目录 参考Legacy container linksConnect using network port mappingConnect with the linking systemThe importance of naming Communication across linksEnviro…