FreeRTOS基础(九):FreeRTOS的列表和列表项

        今天我们将探讨FreeRTOS中的一个核心概念——列表(List)和列表项(List Item)。在实时操作系统(RTOS)中,任务的管理和调度是至关重要的,而FreeRTOS使用列表来实现这一功能。列表可以说是FreeRTOS中的一种数据结构,通过列表,我们可以高效地组织、排序和管理任务、定时器、事件等各种系统资源。具体来说,列表项是组成列表的基本单位,每个列表项都包含指向具体任务或事件的指针,以及用于排序的数值。希望通过今天的学习,大家能够掌握利用列表和列表项来实现任务调度的原理,并能够独立看懂源码。

目录

一、列表和列表项简介

1.1 列表和列表项的概念

1.2 列表结构体

1.3 列表项结构体

1.4 迷你列表项结构体

1.5 列表和列表项的关系

二、列表相关API函数介绍

2.1 初始化列表函数vListInitialise()

2.2 初始化列表项函数vListInitialiseItem()

2.3 列表项升序插入函数vListInsert()

2.3.1 源码分析

2.3.2 举例理解

2.4 列表项尾插函数vListInsertEnd()

2.4.1 源码分析

2.4.2 举例理解

2.5 列表项删除函数uxListRemove()

三、列表项的插入和删除实验

3.1  创建任务及实现任务函数

3.2 主函数调用入口函数,操作系统开始进行任务的切换和调度

3.3  实验结果


一、列表和列表项简介

1.1 列表和列表项的概念

       列表是 FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS中的任务。任务处在不同的状态下,就挂载在不同的列表中(比如就绪列表、阻塞列表、挂起列表)。 而列表项就是存放在列表中的项目,一个列表项就关联着一个任务,可以说:列表代表处于某种状态的任务的集合,列表项就代表处于当前状态下的某一个任务。如下图所示:

 

       列表相当于链表,列表项相当于节点,FreeRTOS 中的列表是一个双向循环链表,每个列表项有前驱结点指针prev,同时又有后继结点指针next,这样,双向循环链表的增删改查非常方便,动态改变,节省内存!列表的特点:列表项间的地址非连续的,是人为的连接到一起的。列表项的数目是由后期添加的个数决定的,随时可以改变,而数组的特点:数组成员地址是连续的,数组在最初确定了成员数量后期无法改变。

       在操作系统FreeRTOS中,任务的数量是不确定的,随时发生变化,并且任务状态是会发生改变的,所以非常适用列表(链表)这种数据结构。

1.2 列表结构体

 有关于列表的东西均在文件 list.c 和 list.h 中,首先我们先看下在list.h中的,列表相关结构体:

  1. 在该结构体中, 包含了两个宏(第一个和最后一个),这两个宏是确定的已知常量, FreeRTOS通过检查这两个常量的值,来判断列表的数据在程序运行过程中,是否遭到破坏 ,该功能一般用于调试, 默认是不开启的;
  2. 成员uxNumberOfItems,用于记录列表中列表项的个数(不包含末尾列表项,即最后一个列表项 xListEnd);
  3. 成员 pxIndex 用于指向列表中的某个列表项,一般用于遍历列表中的所有列表项(其实就是列表项指针);
  4. 成员变量 xListEnd 是一个迷你列表项,排在最末尾;

1.3 列表项结构体

       列表项是列表中用于存放数据的地方(每一个列表项关联着一个任务,在列表项里面有一个成员变量,用来存放任务控制块,描述这个任务的相关属性信息),在 list.h 文件中,有列表项的相关结构体定义:

1、成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序,插入列表项的时候可根据此值来确定插入的位置。比如通过延时阻塞的任务(对应一个列表项),它有一个延时时间,那么就根据延时时间在列表中进行排列,这样就可以实现优先解除时间短的任务。

2、成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项 ,其实就是双向循环链表的前驱结点指针和后继结点指针;

3、成员变量 pxOwner 用于指向包含列表项的对象(通常是任务控制块,存储该任务的相关属性信息)

4、成员变量 pxContainer 用于指向列表项所在的列表(当前列表项在哪种列表中,就绪?阻塞?挂起?)。即指向归属的列表!

        通过成员3和成员4就可以确定该列表项属于哪个任务(任务控制块),并且在哪一种状态下。例如:第3个成员变量为任务1的控制块,第4个成员变量为就绪列表,那么就可以确定任务1处在就绪状态下!

1.4 迷你列表项结构体

          迷你列表项也是列表项,但迷你列表项仅用于标记列表的末尾挂载其他插入列表中的列表项,挂载如何理解:列表初始化的时候,只有一个迷你列表项,当插入新的列表项时,就把新的列表项对应的两只手(前驱指针和后继指针)牵上迷你列表项即可!迷你列表项没有存储数据,不会存储实际的任务数据信息,因此不需要成员变量 pxOwner 和 pxContainer,以节省内存开销!

1、成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序

2、成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项

1.5 列表和列表项的关系

宏观上看如下图:

二、列表相关API函数介绍

         常见的列表的相关API函数如下,其实从数据结构上来讲:就是双向循环链表的初始化、增删操作。

2.1 初始化列表函数vListInitialise()

        初始化列表就是为列表的结构体成员赋初值,此时列表只有一个末尾列表项。

函数原型
void vListInitialise( List_t * const pxList );

形参

描述

pxList

待初始化列表

 

 一开始,列表为空,只有一个末尾列表项,因此:

  1. 用于记录列表中列表项的个数的变量uxNumberOfItems应为0;
  2. 成员 pxIndex 用于指向列表中的某个列表项,此时应该指向末尾列表项;
  3. 末尾列表项的成员变量 xItemValue 应该是最大的,因为要保证放在最后一个位置;
  4. 末尾列表项的前驱结点指针和后继结点指针都应该指向它自己(和双向循环链表初始化一样);

2.2 初始化列表项函数vListInitialiseItem()

函数原型
void vListInitialiseItem( ListItem_t * const pxItem );

形参

描述

pxItem

待初始化列表项

 

2.3 列表项升序插入函数vListInsert()

2.3.1 源码分析

函数原型
void vListInsert  (  List_t * const pxList ,   ListItem_t * const pxNewListItem  )

形参

描述

pxList

插入到哪个列表

pxNewListItem

待插入列表项

此函数用于将待插入列表的列表项按照列表项值升序进行排序,有序地插入到列表中!

 

可以看到:函数vListInsert() 跟双向循环链表的按位置插入结点思想相同。

2.3.2 举例理解

总结:函数vListInsert(),是将待插入列表的列表项按照列表项值升序进行排序,有序地插入到列表中

2.4 列表项尾插函数vListInsertEnd()

2.4.1 源码分析

函数原型
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem );

       形参

描述

       pxList

插入到哪个列表

pxNewListItem

待插入列表项

2.4.2 举例理解

总结:函数vListInsertEnd(),是将待插入的列表项插入到列表 pxIndex 指针指向的列表项前面;它是一种无序的插入方法!!

2.5 列表项删除函数uxListRemove()

函数原型
UBaseType_t  uxListRemove (   ListItem_t *  const    pxItemToRemove ) 

此函数用于将列表项从列表项所在列表中移除。

形参

描述

pxItemToRemove

待移除的列表项

返回值

描述

整数

待移除列表项移除后,所在列表剩余列表项的数量

三、列表项的插入和删除实验

      本实验本质上其实就是对双向链表进行增删操作,为了验证结果,打印列表项的地址和前驱结点地址以及后继结点地址进行验证即可。

3.1  创建任务及实现任务函数

#include "stm32f4xx.h"                  // Device header
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"
#include "mydelay.h"



/**********************START_TASK任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/

#define        START_TASK_STACK_SIZE  128   //定义堆栈大小为128字(1字等于4字节)
#define        START_TASK_PRIO         1    //定义任务优先级,0-31根据任务需求
TaskHandle_t   start_task_handler;    //定义任务句柄(结构体指针)
void start_task(void* args);




/**********************TASK1任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK1_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK1_PRIO         2             //定义任务优先级,0-31根据任务需求
TaskHandle_t   task1_handler;           //定义任务句柄(结构体指针)
void task1(void* args);




/**********************TASK2任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK2_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK2_PRIO         3             //定义任务优先级,0-31根据任务需求
TaskHandle_t   task2_handler;           //定义任务句柄(结构体指针)
void task2(void* args);



/*********开始任务用来创建一个任务,只创建一次,不能是死循环,创建完这个任务后删除开始任务本身***********/
void start_task(void* args)
{
	taskENTER_CRITICAL();        /*进入临界区*/
	
	xTaskCreate( (TaskFunction_t)         task1,
                             (char *)     "task1",  
              ( configSTACK_DEPTH_TYPE)   TASK1_STACK_SIZE,
                            (void *)      NULL,
                            (UBaseType_t) TASK1_PRIO ,
                        (TaskHandle_t *)  &task1_handler );
	

     xTaskCreate( (TaskFunction_t)         task2,
                             (char *)     "task2",  
              ( configSTACK_DEPTH_TYPE)   TASK2_STACK_SIZE,
                            (void *)      NULL,
                            (UBaseType_t) TASK2_PRIO ,
                        (TaskHandle_t *)  &task2_handler );							

							
							
	vTaskDelete(NULL);    //删除开始任务自身,传参NULL
							
	taskEXIT_CRITICAL();   /*退出临界区*/
		

    //临界区内不会进行任务的调度切换,出了临界区才会进行任务调度,抢占式						
}




/********任务1的任务函数,无返回值且是死循环***********/

/***任务1:实现LED0每500ms翻转一次*******/
void task1(void* args)
{
	
	while(1)
	{
		 printf("22222\n");
		 GPIO_ToggleBits(GPIOF,GPIO_Pin_9 );
         vTaskDelay(500);       //FreeRTOS自带的延时函数,延时500毫秒
	
	}	
}


List_t       TestList;      /*定义测试列表*/
ListItem_t   ListItem1;    /*定义测试列表项1*/
ListItem_t   ListItem2;   /*定义测试列表项2*/
ListItem_t   ListItem3;   /*定义测试列表项3*/



/********任务2的任务函数,无返回值且是死循环***********/

/***任务2:列表项插入和删除实验*******/
void task2(void* args)
{
	vListInitialise( &TestList );         /*初始化列表*/
	vListInitialiseItem( &ListItem1);     /*初始化列表项1*/
	vListInitialiseItem( &ListItem2);     /*初始化列表项2*/
	vListInitialiseItem( &ListItem3);     /*初始化列表项3*/
	ListItem1.xItemValue=40;
	ListItem2.xItemValue=60;
	ListItem3.xItemValue=50;
	
	/*第二步:打印列表和其他列表项的地址*/
    printf("/************第二步:打印列表和列表项的地址************/\r\n");
    printf("项目\t\t\t地址\r\n" );
    printf("TestList\t\t0x%p\t\r\n",&TestList);                       //列表地址
    printf("TestList->pxIndex\t0x%p\t\r\n",TestList.pxIndex);        //列表中的列表项指针指向的列表项的地址
    printf("TestList- >xListEnd\t0x%p\t\r\n",(&TestList.xListEnd)); //末尾列表项的地址
    printf("ListIteml\t\t0x%p\t\r\n",&ListItem1);                   //列表项1的地址
    printf("ListItem2\t\t0x%p\t\r\n",&ListItem2);                   //列表项2的地址
    printf("ListItem3\t\t0x%p\t\r\n",&ListItem3);                   //列表项3的地址
    printf ("/***********************结束**********************/\r\n"); 

	
	/*第三步:列表项1插入列表: */
    printf("\r\n***************第三步:列表项1插入列表***************/\r\n");
    vListInsert((List_t* )&TestList,                                 /* 列表  */
              (ListItem_t*) &ListItem1);                            /* 列表项 */
    printf("项目\t\t\t\t地址:\r\n");
    printf("TestList->xListEnd->pxNext\tOx%p\r\n ",(TestList.xListEnd.pxNext));
    printf("ListIteml->pxNext\t\t0x%p\r\n",(ListItem1.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n",(TestList.xListEnd.pxPrevious));
    printf("ListIteml->pxPrevious\t\t0x%p\r\n",(ListItem1.pxPrevious));
    printf ("/*************************结束**************************/\r\n");

    
	/*第四步:列表项2插入列表: */
    printf ("\r\n/************第四步:列表项2插入列表**************/\r\n");
    vListInsert((List_t* )&TestList,                                  /*  列表  */
              (ListItem_t*)&ListItem2);                              /*  列表项  */
    printf("项目\t\t\t\t地址\r\n" );
    printf("TestList->xListEnd->pxNext\t0x%p\r\n",(TestList.xListEnd.pxNext));
    printf("ListIteml->pxNext\t\t0x%p\r\n",(ListItem1.pxNext));
    printf("ListItem2->pxNext\t\t0x%p\r\n",(ListItem2.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n",(TestList.xListEnd.pxPrevious));
    printf("ListIteml->pxPrevious\t\t0x%p\r\n",(ListItem1. pxPrevious));
    printf("ListItem2->pxPrevious\t\t0x%p\r\n",(ListItem2. pxPrevious));
    printf ("/**************************结束***************************/\r\n");


    /* 第五步:列表项3插入列表 */
    printf("\r\n***************第五步:列表项3插入列表***************/\r\n");
    vListInsert((List_t* ) &TestList,                                /*  列表  */
               (ListItem_t*)&ListItem3);                            /*  列表项  */
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->px.Next\t0x%p\r\n",(TestList.xListEnd.pxNext));
    printf("ListIteml->pxNext\t\t0x%p\r\n",(ListItem1.pxNext));
    printf("ListItem2->pxNext\t\t0x%p\r\n",(ListItem2.pxNext));
    printf("ListItem3->pxNext\t\t0x%p\r\n",(ListItem3.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n",(TestList.xListEnd.pxPrevious));
    printf("ListIteml->pxPrevious\t\t0x%p\r\n",(ListItem1.pxPrevious));
    printf("ListItem2->pxPrevious\t\t0x%p\r\n",(ListItem2.pxPrevious));
    printf("ListItem3->pxPrevious\t\t0x%p\r\n",(ListItem3.pxPrevious));
    printf("/**************************结束***************************/\r\n");
	 
	 
	/*  第六步:移除列表项2 */
    printf("\r\n ****************第六步:移除列表项2******************/\r\n");
    uxListRemove((ListItem_t* )&ListItem2);                         /*   移除列表项   */
    printf("项月\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n",(TestList.xListEnd.pxNext));
    printf("ListIteml->px.Next\t\t0x%p\r\n",(ListItem1.pxNext));
    printf("ListItem3->pxNext\t\t0x%p\r\n",(ListItem3.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n",(TestList.xListEnd.pxPrevious));
    printf("ListIteml->pxPrevious\t\t0x%p\r\n",(ListItem1.pxPrevious));
    printf("ListItem3->pxPrevious\t\t0x%p\r\n",(ListItem3.pxPrevious));
    printf("/**************************结束***************************/\r\n");
 
	/* 第七步:列表末尾添加列表项2 */
    printf("\r\n/ *************第七步:列表末尾添加列表项2**************/\r\n");
    vListInsertEnd((List_t*)&TestList,                              /*   列表     */
                  (ListItem_t* )&ListItem2);                       /*   列表项   */
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->pxIndex\t\t0x%p\r\n",TestList.pxIndex);
    printf("TestList->xListEnd->pxNext\t0x%p\r\n",(TestList.xListEnd.pxNext));
    printf("ListIteml->pxNext\t\t0x%p\r\n",(ListItem1.pxNext));
    printf("ListItem2->pxNext\t\t0x%p\r\n",(ListItem2.pxNext));
    printf("ListItem3->pxNext\t\t0x%p\r\n",(ListItem3.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n",(TestList.xListEnd.pxPrevious));
    printf("ListIteml->pxPrevious\t\t0x%p\r\n",(ListItem1.pxPrevious));
    printf("ListItem2->pxPrevious\t\t0x%p\r\n",(ListItem2.pxPrevious));
    printf("ListItem3->pxPrevious\t\t0x%p\r\n",(ListItem3.pxPrevious));
    printf("/*********************实验结束***********************/\r\n");
 
	while(1)
	{
		printf("11111\n");		
        vTaskDelay(1000);       //FreeRTOS自带的延时函数,延时1秒
	
	}	
}




//FreeRTO入口例程函数,无参数,无返回值
void freertos_demo(void)
{
	    xTaskCreate( (TaskFunction_t)     start_task,
                             (char *)     "start_task",  
              ( configSTACK_DEPTH_TYPE)   START_TASK_STACK_SIZE,
                            (void *)      NULL,
                            (UBaseType_t) START_TASK_PRIO ,
                        (TaskHandle_t *)  &start_task_handler );
							
							
	vTaskStartScheduler();  //开启任务调度器
	
}


3.2 主函数调用入口函数,操作系统开始进行任务的切换和调度

#include "stm32f4xx.h"                  // Device header
#include "stdio.h"
#include "myled.h"
#include "myusart.h"


#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"

extern TaskHandle_t Start_Handle;

int main(void)
{
    //硬件初始化
     My_UsartInit();
    
	
	 //调用入口函数
     freertos_demo();
	   
}

3.3  实验结果

根据每一步操作的地址,符合理论!

至此,已经讲解完毕!初次学习,循序渐进,一步步掌握即可!以上就是全部内容!请务必掌握,创作不易,欢迎大家点赞加关注评论,您的支持是我前进最大的动力!下期再见!

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

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

相关文章

城市低空经济“链接力”指数报告(2024)

来源:城市进化论&火石创造 近期历史回顾:2024年NoETL开启自动化数据管理新时代白皮书.pdf 创新引领用户“换新生活”-从AWE2024看家电及消费电子行业发展趋势报告(精简版).pdf 2024智能网联汽车“车路云一体化”规模建设与应用…

鬼刀画风扁平化粒子炫动引导页美化版

源码介绍 分享一款引导页,响应式布局,支持移动PC 添加背景图片,美化高斯模糊 ,删除蒙版人物部分,更图片人物画风更美好 删除雪花特效 替换字体颜色 添加底备案号 预留友情连接 效果预览 源码下载 https://www.qqmu.com/3381.h…

总结2024/6/3

省流,蓝桥杯国优,还是太菜了,听说都是板子题但是还是写不出来,靠暴力好歹没有爆0,还是得多练,明年加油了

分享5款.NET开源免费的Redis客户端组件库

前言 今天大姚给大家分享5款.NET开源、免费的Redis客户端组件库,希望可以帮助到有需要的同学。 StackExchange.Redis StackExchange.Redis是一个基于.NET的高性能Redis客户端,提供了完整的Redis数据库功能支持,并且具有多节点支持、异步编…

Python中的元素相乘与矩阵相乘(附Demo)

目录 前言1. 元素相乘2. 矩阵相乘3. 差异 前言 深度学习的矩阵相乘引发的Bug,由此深刻学习这方面的相关知识 在Python中,特别是使用NumPy库时,元素相乘和矩阵相乘是处理数组和矩阵时的常见操作 1. 元素相乘 元素相乘是指对两个相同形状的…

Windows端口本地转发

参考 微软Netsh interface portproxy 命令 界面端口代理的 Netsh 命令 | Microsoft Learn 使用Windows系统的portproxy功能配置端口转发 使用Windows系统的portproxy功能配置端口转发-阿里云帮助中心 (aliyun.com) 将来自0.0.0.0地址对端口35623的访问转发到172.18.106.16…

Python中degrees怎么用

degrees() 函数可以将弧度转换为角度。 语法 以下是 degrees() 方法的语法: import math math.degrees(x) 注意:degrees() 是不能直接访问的,需要导入 math 模块,然后通过 math 静态对象调用该方法。 参数 x -- 一个数值。 返…

苹果设备mac/Paid/phone 下载使用anki记忆卡

安卓的设备直接可以下 如果你这个,如图。 首先点击下列网址,下载,在里面搜索anki记忆卡 https://www.i4.cn 下载好,打开应用软件爱思助手。搜索anki记忆卡,下载,然后用数据线一端连接电脑一端连接手机或者…

【C++练级之路】【Lv.23】C++11——可变参数模板、lambda表达式和函数包装器

快乐的流畅:个人主页 个人专栏:《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火,在为久候之人燃烧! 文章目录 一、可变参数模板1.1 参数包的概念1.2 参数包的展开1.3 emplace系列 二、lambda表达式2.1 lambda的格式2.2 捕…

二级指针简单介绍

我们之前学习的&#xff1a;变量的地址是存入指针变量中的&#xff0c;然而指针变量也是变量&#xff0c;是变量就有地址&#xff0c;那么指针变量的地址存放在哪里 &#xff1f; 这也就是二级指针 #include<stdio.h> int main() {int a10;int*p&a;int**pp&p;re…

假指纹与活体指纹检测

目录 1. 假指纹简介 2. 假指纹制作流程 3. 活体指纹检测 4. 活体指纹检测竞赛 1. 假指纹简介 随着科学技术的发展&#xff0c;指纹技术以各种各样的形式进入了我们的生活。在大多数情况下&#xff0c;指纹识别应用于移动设备和桌面设备解决方案&#xff0c;以提供安全方便的…

Linux命令篇(一):文件管理部分

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 1、cat命令常用参…

2.3Docker部署java工程

2.3Docker部署java工程 1.导入jar包 2.在Docker部署jdk&#xff08;容器名为myjdk17&#xff09; 3.修改jar包名 mv 原包名 新包名4. 配置启动脚本 Dockerfile是一个文本文件&#xff0c;其中包含了构建 Docker 镜像所需的一系列步骤和指令。通过编写 Dockerfile 文件&…

华为交换机的基本配置

实验拓扑&#xff1a; 实验目的&#xff1a;认识二层交换机和二层交换技术的工作原理&#xff1b;认识三层交换和三层交换技术。 三层功能简而言之就是了具有路由的功能&#xff0c;设备可以充当网关和路由器。 实验要求&#xff1a;公司的两个部门用vlan进行划分&#xff0c…

Vitis HLS 学习笔记--HLS流水线类型

目录 1. 简介 2. 优缺点对比 2.1 Stalled Pipeline 2.2 Free-Running/Flushable Pipeline 2.3 Flushable Pipeline 3. 设置方法 4. FRP的特殊优势 5. 总结 1. 简介 Vitis HLS 会自动选择正确的流水线样式&#xff0c;用于流水打拍函数或循环。 停滞的流水线&#xff…

MaxKey本地运行实战指南

MaxKey 本地运行总结 概述开发环境准备 主页传送门 &#xff1a; &#x1f4c0; 传送 概述 MaxKey单点登录认证系统&#xff0c;谐音为马克思的钥匙寓意是最大钥匙&#xff0c;是业界领先的IAM-IDaas身份管理和认证产品&#xff1b;支持OAuth 2.x/OpenID Connect、SAML 2.0、J…

Layui2.5.6树形表格TreeTable使用

1、问题概述? Layui2.5.6的树形表格-TreeTable终于用明白了,步骤详细,提供源码下载。 如果你使用的是Layui2.8+版本,那么点个赞,赶紧去官网看吧,官网更行了。 更新地址:树表组件 treeTable - Layui 文档 最近在项目中需要使用到树形表格,用来显示菜单的层级关系,当…

集成Google Authenticator实现多因素认证(MFA)

目录 参考1、应用背景2、多因素认证3、谷歌google authenticator集成用法3.1、原理3.2、 MFA绑定3.2.1、 用户输入用户名密码登录3.2.2、检查是否已经绑定MFA&#xff08;检查数据库是否保存该用户的google secret&#xff09;3.2.3、谷歌身份证认证器扫描绑定3.2.4、手动测试验…

016、集合_命令

集合(set)保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。如图集合user:1:follow包含着"it"、“music”、“his”、"sports"四个元素,一个集合最多可以存储2(32)-1个元…

基于web的网上村委会业务办理系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;基础数据管理&#xff0c;办事记录管理&#xff0c;办事申请管理&#xff0c;党员发展管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;补贴活动通知…