FreeRTOS 队列(一)

news2024/12/23 14:31:01

文章目录

  • 一、队列简介
    • 1. 数据存储
    • 2. 多任务访问
    • 3. 出队阻塞
    • 4. 入队阻塞
    • 5. 队列操作过程图示
  • 二、队列结构体
  • 三、队列创建
    • 1. 函数原型
      • (1)函数 xQueueCreate()
      • (2)函数 xQueueCreateStatic()
      • (3)函数 xQueueGenericCreate()
      • (4)函数 xQueueGenericCreateStatic()
    • 2. 队列创建函数详解
    • 3. 队列初始化函数
    • 4. 队列复位函数


一、队列简介

队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息的,所以也称为消息队列。FreeRTOS 中的信号量的也是依据队列实现的!所以有必要深入的了解FreeRTOS 的队列。

1. 数据存储

通常队列采用先进先出(FIFO)的存储缓冲机制,也就是往队列发送数据的时候(也叫入队)永远都是发送到队列的尾部,而从队列提取数据的时候(也叫出队)是从队列的头部提取的。但是也可以使用 LIFO 的存储缓冲,也就是后进先出,FreeRTOS 中的队列也提供了 LIFO 的存储缓冲机制。

数据发送到队列中会导致数据拷贝,也就是将要发送的数据拷贝到队列中,这就意味着在队列中存储的是数据的原始值,而不是原数据的引用(即只传递数据的指针),这个也叫做值传递。学过 UCOS 的同学应该知道,UCOS 的消息队列采用的是引用传递,传递的是消息指针。采用引用传递的话消息内容就必须一直保持可见性,也就是消息内容必须有效,那么局部变量这种可能会随时被删掉的东西就不能用来传递消息,但是采用引用传递会节省时间啊!因为不用进行数据拷贝。采用值传递的话虽然会导致数据拷贝,会浪费一点时间,但是一旦将消息发送到队列中原始的数据缓冲区就可以删除掉或者覆写,这样的话这些缓冲区就可以被重复的使用。FreeRTOS中使用队列传递消息的话虽然使用的是数据拷贝,但是也可以使用引用来传递消息啊,我直接往队列中发送指向这个消息的地址指针不就可以了!这样当我要发送的消息数据太大的时候就可以直接发送消息缓冲区的地址指针,比如在网络应用环境中,网络的数据量往往都很大的,采用数据拷贝的话就不现实。

2. 多任务访问

队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。

3. 出队阻塞

当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任务从队列中读取消息无效的时候任务阻塞的时间。出队就是就从队列中读取消息,出队阻塞是针对从队列中读取消息的任务而言的。比如任务 A 用于处理串口接收到的数据,串口接收到数据以后就会放到队列 Q 中,任务 A 从队列 Q 中读取数据。但是如果此时队列 Q 是空的,说明还没有数据,任务 A 这时候来读取的话肯定是获取不到任何东西,那该怎么办呢?任务 A 现在有三种选择,一:二话不说扭头就走,二:要不我在等等吧,等一会看看,说不定一会就有数据了,三:死等,死也要等到你有数据!选哪一个就是由这个阻塞时间决定的,这个阻塞时间单位是时钟节拍数。阻塞时间为 0 的话就是不阻塞,没有数据的话就马上返回任务继续执行接下来的代码,对应第一种选择。如果阻塞时间为 0~ portMAX_DELAY,当任务没有从队列中获取到消息的话就进入阻塞态,阻塞时间指定了任务进入阻塞态的时间,当阻塞时间到了以后还没有接收到数据的话就退出阻塞态,返回任务接着运行下面的代码,如果在阻塞时间内接收到了数据就立即返回,执行任务中下面的代码,这种情况对应第二种选择。当阻塞时间设置为portMAX_DELAY 的话,任务就会一直进入阻塞态等待,直到接收到数据为止!这个就是第三种选择。

4. 入队阻塞

入队说的是向队列中发送消息,将消息加入到队列中。和出队阻塞一样,当一个任务向队列发送消息的话也可以设置阻塞时间。比如任务 B 向消息队列 Q 发送消息,但是此时队列 Q 是满的,那肯定是发送失败的。此时任务 B 就会遇到和上面任务 A 一样的问题,这两种情况的处理过程是类似的,只不过一个是向队列 Q 发送消息,一个是从队列 Q 读取消息而已。

5. 队列操作过程图示

下面几幅图简单的演示了一下队列的入队和出队过程。
● 创建队列
在这里插入图片描述
上图中任务 A 要向任务 B 发送消息,这个消息是 x 变量的值。首先创建一个队列,并且指定队列的长度和每条消息的长度。这里我们创建了一个长度为 4 的队列,因为要传递的是x 值,而 x 是个 int 类型的变量,所以每条消息的长度就是 int 类型的长度,在 STM32 中就是 4字节,即每条消息是 4 个字节的。

● 向队列发送第一个消息
在这里插入图片描述
● 向队列发送第二个消息
在这里插入图片描述
上图中任务 A 又向队列发送了一个消息,即新的 x 的值,这里是 20。此时队列剩余长度为 2。

● 从队列中读取消息
在这里插入图片描述
上图中任务 B 从队列中读取消息,并将读取到的消息值赋值给 y,这样 y 就等于 10了。任务 B 从队列中读取消息完成以后可以选择清除掉这个消息或者不清除。当选择清除这个消息的话其他任务或中断就不能获取这个消息了,而且队列剩余大小就会加一,变成 3。如果不清除的话其他任务或中断也可以获取这个消息,而队列剩余大小依旧是 2。

二、队列结构体

有一个结构体用于描述队列,叫做 Queue_t,这个结构体在文件 queue.c 中定义如下:

typedef struct QueueDefinition
{
	int8_t *pcHead; //指向队列存储区开始地址。
	int8_t *pcTail; //指向队列存储区最后一个字节。
	int8_t *pcWriteTo; //指向存储区中下一个空闲区域。
	
	union
	{
		int8_t *pcReadFrom; //当用作队列的时候指向最后一个出队的队列项首地址
		UBaseType_t uxRecursiveCallCount;//当用作递归互斥量的时候用来记录递归互斥量被
		//调用的次数。
	} u;
	List_t xTasksWaitingToSend; //等待发送任务列表,那些因为队列满导致入队失败而进
	//入阻塞态的任务就会挂到此列表上。
	List_t xTasksWaitingToReceive; //等待接收任务列表,那些因为队列空导致出队失败而进
	//入阻塞态的任务就会挂到此列表上。
	volatile UBaseType_t uxMessagesWaiting; //队列中当前队列项数量,也就是消息数
	UBaseType_t uxLength; //创建队列时指定的队列长度,也就是队列中最大允许的
	//队列项(消息)数量
	UBaseType_t uxItemSize; //创建队列时指定的每个队列项(消息)最大长度,单位字节
	volatile int8_t cRxLock; //当队列上锁以后用来统计从队列中接收到的队列项数
	//量,也就是出队的队列项数量,当队列没有上锁的话此字
	//段为 queueUNLOCKED
	volatile int8_t cTxLock; //当队列上锁以后用来统计发送到队列中的队列项数量,
	//也就是入队的队列项数量,当队列没有上锁的话此字
	//段为 queueUNLOCKED
	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) &&\
	( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
	uint8_t ucStaticallyAllocated;//如果使用静态存储的话此字段设置为 pdTURE。
	#endif
	#if ( configUSE_QUEUE_SETS == 1 ) //队列集相关宏
	struct QueueDefinition *pxQueueSetContainer;
	#endif
	#if ( configUSE_TRACE_FACILITY == 1 ) //跟踪调试相关宏
	UBaseType_t uxQueueNumber;
	uint8_t ucQueueType;
	#endif
} xQUEUE;
typedef xQUEUE Queue_t;

老版本的 FreeRTOS 中队列可能会使用 xQUEUE 这个名字,新版本 FreeRTOS 中队列的名字都使用 Queue_t。

三、队列创建

1. 函数原型

在使用队列之前必须先创建队列,有两种创建队列的方法,一种是静态的,使用函数
xQueueCreateStatic();另一个是动态的,使用函数 xQueueCreate()。这两个函数本质上都是宏,真正完成队列创建的函数是 xQueueGenericCreate()和xQueueGenericCreateStatic(),这两个函数在文件 queue.c 中有定义,这四个函数的原型如下。

(1)函数 xQueueCreate()

此函数本质上是一个宏,用来动态创建队列,此宏最终调用的是函数 xQueueGenericCreate(),函数原型如下:

QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,
						   UBaseType_t uxItemSize)

参数:
**uxQueueLength:**要创建的队列的队列长度,这里是队列的项目数。
uxItemSize: 队列中每个项目(消息)的长度,单位为字节

返回值:
其他值: 队列创捷成功以后返回的队列句柄!
NULL: 队列创建失败。

(2)函数 xQueueCreateStatic()

此函数也是用于创建队列的,但是使用的静态方法创建队列,队列所需要的内存由用户自行分配,此函数本质上也是一个宏,此宏最终调用的是函数xQueueGenericCreateStatic(),函数原型如下:

QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,
								 UBaseType_t uxItemSize,
							 	 uint8_t * pucQueueStorageBuffer,
							     StaticQueue_t * pxQueueBuffer)

参数:
uxQueueLength: 要创建的队列的队列长度,这里是队列的项目数。
uxItemSize: 队列中每个项目(消息)的长度,单位为字节
pucQueueStorage: 指向队列项目的存储区,也就是消息的存储区,这个存储区需要用户自行分配。此参数必须指向一个 uint8_t 类型的数组。这个存储区要大于等
于(uxQueueLength * uxItemsSize)字节。
pxQueueBuffer: 此参数指向一个 StaticQueue_t 类型的变量,用来保存队列结构体。

返回值:
其他值: 队列创捷成功以后的队列句柄!
NULL: 队列创建失败。

(3)函数 xQueueGenericCreate()

函数 xQueueGenericCreate()用于动态创建队列,创建队列过程中需要的内存均通过
FreeRTOS 中的动态内存管理函数 pvPortMalloc()分配,函数原型如下:

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, 
								   const UBaseType_t uxItemSize,
								   const uint8_t ucQueueType )

参数:
**uxQueueLength:**要创建的队列的队列长度,这里是队列的项目数。
uxItemSize: 队列中每个项目(消息)的长度,单位为字节。
ucQueueType: 队列类型,由于 FreeRTOS 中的信号量等也是通过队列来实现的,创建信号量的函数最终也是使用此函数的,因此在创建的时候需要指定此队列的用途,也就是队列类型,一共有六种类型:
queueQUEUE_TYPE_BASE 普通的消息队列
queueQUEUE_TYPE_SET 队列集
queueQUEUE_TYPE_MUTEX 互斥信号量
queueQUEUE_TYPE_COUNTING_SEMAPHORE 计数型信号量
queueQUEUE_TYPE_BINARY_SEMAPHORE 二值信号量
queueQUEUE_TYPE_RECURSIVE_MUTEX 递归互斥信号量
函 数 xQueueCreate() 创 建 队 列 的 时 候 此 参 数 默 认 选 择 的 就 是
queueQUEUE_TYPE_BASE。

返回值:
其他值: 队列创捷成功以后的队列句柄!
NULL: 队列创建失败。

(4)函数 xQueueGenericCreateStatic()

此函数用于动态创建队列,创建队列过程中需要的内存需要由用户自行分配好,函数原型如下:

QueueHandle_t xQueueGenericCreateStatic( const UBaseType_t uxQueueLength, 
										 const UBaseType_t uxItemSize, 
										 uint8_t * pucQueueStorage, 
										 StaticQueue_t * pxStaticQueue, 
										 const uint8_t ucQueueType )

参数:
uxQueueLength: 要创建的队列的队列长度,这里是队列的项目数。
uxItemSize: 队列中每个项目(消息)的长度,单位为字节
pucQueueStorage: 指向队列项目的存储区,也就是消息的存储区,这个存储区需要用户自行分配。此参数必须指向一个 uint8_t 类型的数组。这个存储区要大于等于(uxQueueLength * uxItemsSize)字节。
pxStaticQueue: 此参数指向一个 StaticQueue_t 类型的变量,用来保存队列结构体。
ucQueueType: 队列类型。

返回值:
其他值: 队列创捷成功以后队列句柄!
NULL: 队列创建失败。

2. 队列创建函数详解

最终完成队列创建的函数有两个,一个是静态方法的 xQueueGenericCreateStatic(),另外一个 就 是 动 态 方 法 的 xQueueGenericCreate() 。 我 们 来 详 细 的 分 析 一 下 动 态 创 建 函 数xQueueGenericCreate(),静态方法大同小异,大家可以自行分析一下。函数 xQueueGenericCreate()在文件 queue.c 中有如下定义:

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, 
								   const UBaseType_t uxItemSize, 
								   const uint8_t ucQueueType )
{
	Queue_t *pxNewQueue;
	size_t xQueueSizeInBytes;
	uint8_t *pucQueueStorage;
	configASSERT( uxQueueLength > ( UBaseType_t ) 0 );
	if( uxItemSize == ( UBaseType_t ) 0 )
	{
		//队列项大小为 0,那么就不需要存储区。
		xQueueSizeInBytes = ( size_t ) 0;
	}
	else
	{
		//分配足够的存储区,确保随时随地都可以保存所有的项目(消息),
		xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); (1)
	}
	
	pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); (2)
	//内存申请成功
	if( pxNewQueue != NULL )
	{
		pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t ); (3)
		#if( configSUPPORT_STATIC_ALLOCATION == 1 )
		{
			//队列是使用动态方法创建的,所以队列字段 ucStaticallyAllocated 标
			//记为 pdFALSE。
			pxNewQueue->ucStaticallyAllocated = pdFALSE;
		}
		#endif 
			
		prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, \ (4)
		ucQueueType, pxNewQueue );
	}
	return pxNewQueue;
}

(1)、队列是要存储消息的,所以必须要有消息的存储区,函数的参数 uxQueueLength 和uxItemSize 指定了队列中最大队列项目(消息)数量和每个消息的长度,两者相乘就是消息存储区的大小。

(2)、调用函数 pvPortMalloc()给队列分配内存,注意这里申请的内存大小是队列结构体和队列中消息存储区的总大小。

(3)、计算出消息存储区的首地址,(2)中申请到的内存是队列结构体和队列中消存储区的总大小,队列结构体内存在前,紧跟在后面的就是消息存储区内存。

(4)、调用函数 prvInitialiseNewQueue()初始化队列。
可以看出函数 xQueueGenericCreate()重要的工作就是给队列分配内存,当内存分配成功以后调用函数 prvInitialiseNewQueue()来初始化队列。

3. 队列初始化函数

队列初始化函数 prvInitialiseNewQueue()用于队列的初始化,此函数在文件 queue.c 中有定义,函数代码如下:

static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, //队列长度
								   const UBaseType_t uxItemSize, //队列项目长度
								   uint8_t * pucQueueStorage, //队列项目存储区
								   const uint8_t ucQueueType, //队列类型
								   Queue_t * pxNewQueue ) //队列结构体
{
	//防止编译器报错
	( void ) ucQueueType;
	if( uxItemSize == ( UBaseType_t ) 0 )
	{
		//队列项(消息)长度为 0,说明没有队列存储区,这里将 pcHead 指向队列开始地址
		pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
	}
	else
	{
		//设置 pcHead 指向队列项存储区首地址
		pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage; (1)
	}
	//初始化队列结构体相关成员变量
	pxNewQueue->uxLength = uxQueueLength; (2)
	pxNewQueue->uxItemSize = uxItemSize;
	( void ) xQueueGenericReset( pxNewQueue, pdTRUE ); (3)
	#if ( configUSE_TRACE_FACILITY == 1 ) //跟踪调试相关字段初始化
	{
		pxNewQueue->ucQueueType = ucQueueType;
	}
	#endif /* configUSE_TRACE_FACILITY */
	#if( configUSE_QUEUE_SETS == 1 ) //队列集相关字段初始化
	{
		pxNewQueue->pxQueueSetContainer = NULL;
	}
	#endif /* configUSE_QUEUE_SETS */
	traceQUEUE_CREATE( pxNewQueue );
}

(1)、队列结构体中的成员变量 pcHead 指向队列存储区中首地址。

(2)、初始化队列结构体中的成员变量 uxQueueLength 和 uxItemSize,这两个成员变量保存队列的最大队列项目和每个队列项大小。

(3)、调用函数 xQueueGenericReset()复位队列。PS:发一句牢骚,绕来绕去的,函数调了一个又一个的。

4. 队列复位函数

队列初始化函数 prvInitialiseNewQueue()中调用了函数 xQueueGenericReset()来复位队列,函数 xQueueGenericReset()代码如下:

BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{
	Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	configASSERT( pxQueue );
	taskENTER_CRITICAL();
	{
		//初始化队列相关成员变量
		pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->\ (1)
		uxItemSize );
		pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
		pxQueue->pcWriteTo = pxQueue->pcHead;
		pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - \
		( UBaseType_t ) 1U ) * pxQueue->uxItemSize );
		pxQueue->cRxLock = queueUNLOCKED;
		pxQueue->cTxLock = queueUNLOCKED;
		if( xNewQueue == pdFALSE ) (2)
		{
			//由于复位队列以后队列依旧是空的,所以对于那些由于出队(从队列中读取消
			//息)而阻塞的任务就依旧保持阻塞壮态。但是对于那些由于入队(向队列中发送
			//消息)而阻塞的任务就不同了,这些任务要解除阻塞壮态,从队列的相应列表中
			//移除。
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
			{
				if( xTaskRemoveFromEventList( &( pxQueue->\
												xTasksWaitingToSend ) ) != pdFALSE )
				{
					queueYIELD_IF_USING_PREEMPTION();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			//初始化队列中的列表
			vListInitialise( &( pxQueue->xTasksWaitingToSend ) ); (3)
			vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
		}
	}
	taskEXIT_CRITICAL();
	return pdPASS;
}

(1)、初始化队列中的相关成员变量。

(2)、根据参数 xNewQueue 确定要复位的队列是否是新创建的队列,如果不是的话还需要做其他的处理

(3)、初始化队列中的列表 xTasksWaitingToSend 和 xTasksWaitingToReceive。
至此,队列创建成功,比如我们创建一个有 4 个队列项,每个队列项长度为 32 个字节的队列 TestQueue,创建成功的队列如下图所示:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Golang每日一练(leetDay0045)

目录 133. 克隆图 Clone Graph 🌟🌟 134. 加油站 Gas Station 🌟🌟 135. 分发糖果 Candy 🌟🌟🌟 🌟 每日一练刷题专栏 🌟 Golang每日一练 专栏 Python每日一练 …

【PR 基础】设置上下黑白边的两种方法

方法1 点击 文件-》新建-》旧版标题 点击确定 点击矩形工具 利用矩形工具框选出上下黑白边 款选完成后点击关闭 将刚创建的字幕拖入轨道 可以修改其持续时长与视频时长保持一致 如果想要修改字幕可以双击来修改 比如可以将颜色改为黑色 方法2 点击号,再选择安全边…

如何在表格里面使用VSTACK、HSTACK等函数

如何在表格里面使用VSTACK、HSTACK等函数 书接前文 ONLYOFFICE 桌面编辑器 v7.3 新功能介绍 里面介绍了最新版本的表格里面,添加的公式将帮助您更高效地进行数据计算:TEXTBEFORE、TEXTAFTER、TEXTSPLIT、VSTACK、HSTACK、TOROW、TOCOL、WRAPROWS、WRA…

IPSEC VPN

1.数据认证的介绍、作用、实现的技术手段 数据认证是指通过一系列验证过程检查数据的完整性、真实性、可靠性和准确性,以确保这些数据来自于已知或可信的来源。数据认证的目的是保护数据免受篡改、冒充或欺骗等威胁,确保数据的安全性和可信度。实现数据…

Windows 彻底卸载联软流氓软件(亲测可用)

文章目录 1、前言2、卸载步骤(1)打开服务,将所有Uni开头的服务都禁用,某些服务禁用后可能还是会自启,不过不影响(2)进入安全模式,电脑重启(3)重启后桌面显示了…

怎么将照片KB调小?压缩照片kb的几个方法

将照片KB调小的意思是将照片文件的大小缩小,使其占据更小的存储空间。在一些特定场景下,如网页设计、邮件发送、上传文件等,限制了文件大小,因此需要将照片压缩到规定大小以内。通常情况下,压缩后的照片质量会相应降低…

18.Java泛型

目录 1. Java基本介绍 2. JDK下载安装及其环境配置 3. 一个简单的java程序 4. Eclipse基本使用、数据类型、运算符 5. 控制语句(if、switch、for、while、foreach) 6. Java数组 7. Java字符串对象(String|StringBuffer|StringBuilder|StringJoiner…

关闭默认共享服务

目录 1.从网络上关闭共享协议2.禁用共享服务3.关闭防火墙共享端口4.脚本自启动删除默认共享5.修改注册表彻底屏蔽共享 共5种方法,推荐前三种搭配。 1.从网络上关闭共享协议 打开网络和共享中心->本地连接->属性把" Microsoft 网络的文件和打印机共享&qu…

Word如何插入图片?最全方法总结(2023新版)

案例:Word如何插入图片 【我最近在写毕业论文,需要在Word文档里添加一些图片,想问问大家Word如何插入图片呀?感谢回答!】 Microsoft Word是一个广泛使用的文字处理软件,在使用中如果插入图片可以很好的丰…

优思学院|精益生产和六西格玛如何把控质量?

精益生产是一种流程改善方法,旨在最大程度地减少浪费和提高效率。在实施精益生产时,质量控制是一个重要的因素,因为不合格的产品会导致延误和浪费。优思学院在本文将探讨精益生产和六西格玛管理理论如何控制质量,以及解决产品质量…

poi-tl简介与文本/表格和图片渲染

一、poi-tl简介 下面简介来自官方文档。 官方文档:http://deepoove.com/poi-tl/#_why_poi_tl 1、简介 poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建很棒的Word文档。 poi-tl是一个基于Apache POI的Word模…

【Python】【进阶篇】5、Django Admin后台管理系统

目录 5、Django Admin后台管理系统1. 后台管理系统的重要性2. 了解Django后台管理功能 5、Django Admin后台管理系统 Django 的后台管理系统是非常出色的,新建项目以后,Django 就为我们设置好了后台管理系统的各种功能,本节我们将一起认识它…

SQLServer:Win/Linux环境安装及一键部署脚本

1. Win安装SQLServer CSDN已有完整安装流程,亲测可用。----》Windows安装SQLServer流程 2. Linux安装 SQLServer 2.1 设置镜像 curl https://packages.microsoft.com/config/rhel/7/mssql-server-2017.repo > /etc/yum.repos.d/mssql-server.repo 2.2 通过y…

Adobe国际认证证书有用吗?

Adobe国际认证又称为Adobe认证(英文:Adobe Certified Professional)是Adobe公司CEO签发的权威国际认证体系,旨在为用户提供Adobe软件的专业认证。 该体系基于Adobe核心技术及岗位实际应用操作能力的测评体系得到国际ISTE协会的认可,并在全球 148 各国家推广&#x…

心理预期太大,容易失望

心理预期太大,是做事的障碍 心理预期与标准有关:亚马逊创始人谈标准 趣讲大白话:对事要有合理的心理预期 【趣讲信息科技144期】 **************************** 亚马逊创始人贝索斯在《长期主义》中 对高标准的四大要素: 1.可以通…

Node实现CSDN博客导出(后续)

前言 在2021年我实现了一个Node导出博客的功能:爬取接口及博客页面并导出为md文件格式。中途有许多迭代及优化以及解决了一些关键问题,写篇文章做个记录和review 博客更新功能 在原有的导出功能上增加了博客更新的功能,避免了每次都全部导…

Java7

Java7 (一)、集合体系(二)、Collection(三)、Collection的遍历方式3.1迭代器3.2增强for遍历3.3 Lambda表达式遍历 (四)、List(五)、数据结构5.1 栈5.2 队列5.…

C learning_8

猜数字游戏 猜数字游戏: 1.电脑会随机产生一个数 2.猜数字 a>猜大了,提醒猜大了,继续猜 b>猜小了,提醒猜小了,继续猜 c>猜对了,恭喜你,猜对了,游戏结束 3.玩完之后可以继续…

给httprunnermanager接口自动化测试平台加点颜色(一)

文章目录 一、背景1.1、部署过程略 二、使用过程2.1、新增接口列2.2、实现搜索效果 三、总结 一、背景 https://github.com/httprunner/HttpRunnerManager.git从github上找的接口测试平台,引入公司作为测试协同测试的平台,底层框架基于httprunner(reque…

SOLIDWORKS钣金设计需要考虑的折弯问题

设计需要考虑,究竟哪些是成型前加工,究竟哪些是成型后加工。 考虑工作制作工艺过程中,必须先折弯,后加工部分孔的情况有: 距离折弯边很近的圆孔,方孔,腰圆孔,螺纹等,下…