FreeRTOS信号量详解第四讲(全网最全)——互斥信号量

2023-05-16

一、互斥信号量简介

互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙,当使用完资源以后就必须归还这个钥匙,这样其他的任务就可以拿着这个钥匙去使用资源。互斥信号量使用和二值信号量相同的API操作函数,所以互斥信号量也可以设置阻塞时间,不同于二值信号量的是互斥信号量具有优先级继承的特性当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的"优先级翻转"的影响降到最低。优先级继承并不能完全的消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。硬实时应用应该在设计之初就要避免优先级翻转的发生。互斥信号量不能用于中断服务函数中,原因如下:
1、互斥信号量有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。
2、中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。

二、互斥信号量相关API

FreeRTOS提供了两个互斥信号量创建函数,如下表所示:

函数描述
xSemaphoreCreateMutex()使用动态方法创建互斥信号量
xSemaphoreCreateMutexStatic()使用静态方法创建互斥信号量

1、函数xSemaphoreCreateMutex()
此函数用于创建一个互斥信号量,所需要的内存通过动态内存管理方法分配。此函数本质是一个宏,真正完成信号量创建的是函数xQueueCreateMutex(),此函数原型如下:

SemaphoreHandle_t xSemaphoreCreateMutex(void)
参数描述
返回值NULL:互斥信号量创建失败。其他值:创建成功的互斥信号量的句柄。

2、函数xSemaphoreCreateMutexStatic()
此函数也是创建互斥信号量的,只不过使用此函数创建互斥信号量的话信号量所需要的,RAM需要由用户来分配,此函数是个宏,具体创建过程是通过函数xQueueCreateMutexStatic()来完成的,函数原型如下:

SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t*pxMutexBuffer)
参数描述
pxMutexBuffer此参数指向一个StaticSemaphore_t类型的变量,用来保存信号量结构体。
返回值NULL:互斥信号量创建失败。其他值:创建成功的互斥信号量的句柄。

三、互斥信号量相关函数详细分析

1、互斥信号量创建过程分析
动态创建互斥信号量函数xSemaphoreCreateMutex(),此函数是个宏,定义如下:

#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)

可以看出,真正干事的是函数xQueueCreateMutex(),此函数在文件queue.c中有如下定义,

QueueHandle_t xQueueCreateMutex( 
const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;

	pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );(1)
	prvInitialiseMutex( pxNewQueue );(2)

	return pxNewQueue;
}
  • (1)、调用函数xQueueGenericCreate()创建一个队列,队列长度为1,队列项长度为0,队列类型为参数ucQueueType。由于本函数是创建互斥信号量的,所以参数ucQueueType为queueQUEUE_TYPE_MUTEX。
  • (2)、调用函数prvInitialiseMutex()初始化互斥信号量。

其中prvInitialiseMutex()代码如下:

static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
	if( pxNewQueue != NULL )
	{
/*虽然创建队列的时候会初始化队列结构体的成员变量,但是现在是创建互斥信号量,有些成员需要重新赋值,特别是用于优先级继承的*/
		pxNewQueue->pxMutexHolder = NULL;(1)
		pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;(2)

		//如果是递归互斥信号量
		pxNewQueue->u.uxRecursiveCallCount = 0;(3)

		traceCREATE_MUTEX( pxNewQueue );

		//释放互斥信号量
		( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
	}
	else
	{
		traceCREATE_MUTEX_FAILED();
	}
}

(1)和(2)、这里大家可能会疑惑,队列结构体Queue_t中没有pxMutexHolder和uxQueueType这两个成员变量?这两个东西是哪里来的妖孽?这两个其实是宏,专门为互斥信号量准备的,在文件queue.c中有如下定义:

#define pxMutexHolder pcTail
#define uxQueueType pcHead
#define queueQUEUE_IS_MUTEX NULL

当Queue_t用于表示队列的时候pcHead和pcTail指向队列的存储区域,当Queue_t用于表示互斥信号量的时候就不需要pcHead和pcTail了。当用于互斥信号量的时候将pcHead指向NULL来表示pcTail保存着互斥队列的所有者,pxMutexHolder指向拥有互斥信号量的那个任务的任务控制块。重命名pcTail和pcHead就是为了增强代码的可读性。
(3)、如果创建的互斥信号量是互斥信号量的话,还需要初始化队列结构体中的成员变量u.uxRecursiveCallCount。
互斥信号量创建成功以后会调用函xQueueGenericSend()释放一次信号量,说明互斥信号量默认就是有效的!互斥信号量初始化完成如下图所示:
在这里插入图片描述
2、释放互斥信号量过程分析
释放互斥信号量的时候和二值信号量、计数型信号量一样,都是用的函数xSemaphoreGive()(实际上完成信号量释放的是函数xQueueGenericSend()。)不过由于互斥信号量涉及到优先级继承的问题,所以具体处理过程会有点区别。使用函数xSemaphoreGive()释放信号量最重要的一步就是将uxMessagesWaiting加一,而这一步就是通过函数
prvCopyDataToQueue()来完成的,释放信号量的函数xQueueGenericSend()会调用prvCopyDataToQueue()。互斥信号量的优先级继承也是在函数prvCopyDataToQueue()中完成的,此函数中有如下一段代码:

static BaseType_t prvCopyDataToQueue( 
Queue_t * const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition )
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;
	uxMessagesWaiting = pxQueue->uxMessagesWaiting;

	if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
	{
	//互斥信号量
		#if ( configUSE_MUTEXES == 1 )
		{
			if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )(1)
			{
				xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );(2)
				pxQueue->pxMutexHolder = NULL;(3)
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif /* configUSE_MUTEXES */
	}
	/**********省略其他代码************/
	
  • (1)、当前操作的是互斥信号量。
  • (2)、调用函数xTaskPriorityDisinherit()处理互斥信号量的优先级继承问题。
  • (3)、互斥信号量释放以后,互斥信号量就不属于任何任务了,所以pxMutexHolder要指向NULL。

现在来看一下函数xTaskPriorityDisinherit()是怎么具体的处理优先级继承的,函数xTaskPriorityDisinherit0代码如下:

BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
BaseType_t xReturn = pdFALSE;

	if( pxMutexHolder != NULL )(1)
	{
		/* 当一个任务获取到互斥信号量就会涉及优先级继承的问题,正在释放互斥信号量的任务肯定是现在正在运行的任务pxCurrentTCB */
		configASSERT( pxTCB == pxCurrentTCB );

		configASSERT( pxTCB->uxMutexesHeld );
		( pxTCB->uxMutexesHeld )--;(2)

		/* 是否存在优先级继承?如果存在的话任务当前优先级肯定和任务基优先级不同*/
		if( pxTCB->uxPriority != pxTCB->uxBasePriority )(3)
		{
			/* 当前任务只获取到一个互斥信号量 */
			if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )(4)
			{
				if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )(5)
				{
					taskRESET_READY_PRIORITY( pxTCB->uxPriority );(6)
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}

				/* 使用新的优先级将任务重新添加到就绪列表中 */
				traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
				pxTCB->uxPriority = pxTCB->uxBasePriority;(7)
				listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority );(8) 
				prvAddTaskToReadyList( pxTCB );(9)
				xReturn = pdTRUE;(10)
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	return xReturn;
}
  • (1)、函数的参数pxMutexHolder表示拥有此互斥信号量任务控制块,所以要先判断此互斥信号量是否已经被其他任务获取。
  • (2)、有的任务可能会获取多个互斥信号量,所以就需要标记任务当前获取到的互斥信号量个数,任务控制块结构体的成员变量uxMutexesHeld用来保存当前任务获取到的互斥信号量个数。任务每释放一次互斥信号量,变量uxMutexesHeld肯定就要减一。
  • (3)、判断是否存在优先级继承,如果存在的话任务的当前优先级肯定不等于任务的基优先级。
  • (4)、判断当前释放的是不是任务所获取到的最后一个互斥信号量,因为如果任务还获取了其他互斥信号量的话就不能处理优先级继承。优先级继承的处理必须是在释放最后一个互斥信号量的时候。
  • (5)、优先级继承的处理说白了就是将任务的当前优先级降低到任务的基优先级,所以要把当前任务先从任务就绪表中移除。当任务优先级恢复为原来的优先级以后再重新加入到就绪表中。
  • (6)、如果任务继承来的这个优先级对应的就绪表中没有其他任务的话就将取消这个优先级的就绪态。
  • (7)、重新设置任务的优先级为任务的基优先级uxBasePriority。
  • (8)、复位任务的事件列表项。
  • (9)、将优先级恢复后的任务重新添加到任务就绪表中。
  • (10)、返回pdTRUE,表示需要进行任务调度。

3、获取互斥信号量过程分析
获取互斥信号量的函数同获取二值信号量和计数型信号量的函数相同,都是xSemaphoreTake()(实际执行信号量获取的函数是xQueueGenericReceive()),获取互斥信号量的过程也需要处理优先级继承的问题,函数xQueueGenericReceive()在文件queue.c中有定义,在缩减后的函数代码如下:

BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, 
void * const pvBuffer, TickType_t xTicksToWait, 
const BaseType_t xJustPeeking )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
int8_t *pcOriginalReadPosition;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	for( ;; )
	{
		taskENTER_CRITICAL();
		{
			const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;

		//判断队列是否有消息
			if( uxMessagesWaiting > ( UBaseType_t ) 0 )(1)
			{
				pcOriginalReadPosition = pxQueue->u.pcReadFrom;

				prvCopyDataFromQueue( pxQueue, pvBuffer );(2)

				if( xJustPeeking == pdFALSE )(3)
				{
					traceQUEUE_RECEIVE( pxQueue );

					//移除消息
					pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;(4)

					#if ( configUSE_MUTEXES == 1 )(5)
					{
						if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
						{
							pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount(); (6)
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
					#endif /* configUSE_MUTEXES */
/*查看是否有任务因为入队而阻塞,如果有的话就需要解除阻塞态。*/
					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )(7)
					{
						if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
						{
/*如果解除任务阻塞的任务优先级比当前任务优先级高的话就需要进行一次任务切换*/
							queueYIELD_IF_USING_PREEMPTION();
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else(8)
				{
					traceQUEUE_PEEK( pxQueue );
//读取队列中的消息以后需要删除消息
					pxQueue->u.pcReadFrom = pcOriginalReadPosition;
//如果有任务因为出队而阻塞的话就解除任务的阻塞态
					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )(9)
					{
						if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
						{
//如果解除阻塞的任务优先级比当前任务优先级高的话就需要进行一次任务切换
							queueYIELD_IF_USING_PREEMPTION();
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

				taskEXIT_CRITICAL();
				return pdPASS;
			}
			else //队列为空 (10)
			{
				if( xTicksToWait == ( TickType_t ) 0 )
				{
	/*队列为空,如果阻塞时间为0的话就直接返回errQUEUE_EMPTY*/
					taskEXIT_CRITICAL();
					traceQUEUE_RECEIVE_FAILED( pxQueue );
					return errQUEUE_EMPTY;
				}
				else if( xEntryTimeSet == pdFALSE )
				{
	//队列为空且设置了阻塞时间,需要初始化时间状态结构体。
					vTaskSetTimeOutState( &xTimeOut );
					xEntryTimeSet = pdTRUE;
				}
				else
				{
					/* Entry time was already set. */
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		taskEXIT_CRITICAL();
		vTaskSuspendAll();
		prvLockQueue( pxQueue );

//更新时间状态结构体,并且检查超时是否发送。
		if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )(11)
		{
			if( prvIsQueueEmpty( pxQueue ) != pdFALSE )(12)
			{
				traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );

				#if ( configUSE_MUTEXES == 1 )
				{
					if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )(13)
					{
						taskENTER_CRITICAL();
						{
							vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );(14)
						}
						taskEXIT_CRITICAL();
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				#endif

				vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );(15)
				prvUnlockQueue( pxQueue );
				if( xTaskResumeAll() == pdFALSE )
				{
					portYIELD_WITHIN_API();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else//再试一次
			{
				prvUnlockQueue( pxQueue );
				( void ) xTaskResumeAll();
			}
		}
		else
		{
			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();

			if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
			{
				traceQUEUE_RECEIVE_FAILED( pxQueue );
				return errQUEUE_EMPTY;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}
}
  • (1)、队列不为空,可以从队列中提取数据。
  • (2)、调用函数prvCopyDataFromQueue()使用数据拷贝的方式从队列中提取数据。
  • (3)、数据读取以后需要将数据删除掉。
  • (4)、队列的消息数量计数器uxMessagesWaiting减一,通过这一步就将数据删除掉了。
  • (5)、表示此函数是用于获取互斥信号量的。
  • (6)、获取互斥信号量成功,需要标记互斥信号量的所有者,也就是给pxMutexHolder赋值,pxMutexHolder应该是当前任务的任务控制块。但是这里是通过函数pvTaskIncrementMutexHeldCount()来赋值的,此函数很简单,只是将任务控制块中的成员变量uxMutexesHeld加一,表示任务获取到了一个互斥信号量,最后此函数返回当前任务的任务控制块。
  • (7)、出队成功以后判断是否有任务因为入队而阻塞的,如果有的话就需要解除任务的阻塞态,如果解除阻塞的任务优先级比当前任务的优先级高还需要进行一次任务切换。
  • (8)、出队的时候不需要删除消息。
  • (9)、如果出队的时候不需要删除消息的话那就相当于刚刚出队的那条消息接着有效!既然还有有效的消息存在队列中,那么就判断是否有任务因为出队而阻塞,如果有的话就解除任务的阻塞态。同样的,如果解除阻塞的任务优先级比当前任务的优先级高的话还需要进行一次任务切换。
  • (10)、上面分析的都是队列不为空的时候,那当队列为空的时候该如何处理呢?处理过程和队列的任务级通用入队函数xQueueGenericSend()类似。如果阻塞时间为0的话就就直接返回errQUEUE_EMPTY,表示队列空,如果设置了阻塞时间的话就进行相关的处理。
  • (11)、检查超时是否发生,如果没有的话就需要将任务添加到队列的xTasksWaitingToReceive列表中。
  • (12)、检查队列是否继续为空?如果不为空的话就会在重试一次出队。
  • (13)、表示此函数是用于获取互斥信号量的。
  • (14)、调用函数vTaskPriorityInherit()处理互斥信号量中的优先级继承问题,如果函数xQueueGenericReceive()用于获取互斥信号量的话,此函数执行到这里说明互斥信号量正在被其他的任务占用。此函数和函数xTaskPriorityDisinherit()过程相反。此函数会判断当前任务的任务优先级是否比正在拥有互斥信号量的那个任务的任务优先级高,如果是的话就会把拥有互斥信号量的那个低优先级任务的优先级调整为与当前任务相同的优先级
  • (15)、经过(12)步判断,队列依旧为空,那么就将任务添加到列表xTasksWaitingToReceive中。

互斥信号量的讲解就到这里啦!!!谢谢大家收看!!!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS信号量详解第四讲(全网最全)——互斥信号量 的相关文章

  • 六、PX4的ros/mavros搭建

    老规矩 xff0c 看官网 这里 我看这个上面已经在推ROS2了 不过对于我这种只接触过ROS1版本的还是MAVROS来得清切 MAVROS说白了就是MAVLINK的进一步封装 xff0c 可以使得计算机通过串口发送指令给pixhawk x
  • ajaxSubmit

    34 form 34 ajaxSubmit xff09 介绍 http www cnblogs com qiantuwuliang archive 2009 09 14 1566604 html jQuery extend 函数详解 htt
  • git的使用

    无情的标题 首先这次正对的是码云平台上的一个小deno 第一步进入码云平台fork我们的demo 登录注册一条龙 搜索这个demo 点击fork xff0c 一般帅哥美女都会随手点一个star 然后返回自己的本地空间就可以看到啦 首先我们要
  • 逻辑分析仪使用

    首先我们打开软件 软件自行下载 xff0c 我用的是这一个软件 在测试的时候 xff0c 我们确保软件界面左上角或者是上方显示connected xff0c 不然我们看到的波形会是一个相当标准的正弦 xff08 或者余弦 xff09 波 然
  • ubuntu修改pip的官方源为豆瓣源

    ubuntu修改pip的官方源为豆瓣源 编辑配置文件 如果没有就新建一份 span class token function mkdir span pip span class token function vim span pip pip
  • SDN和SD-WAN的概念别再搞混了

    最近 xff0c SD WAN 在融资领域是一个比较热的话题 国外几家 SD WAN 的头部企业不断地取得融资 xff0c 也包括被思科 VMware等巨头收购和兼并 xff0c 国内创业公司推出了各种 SD WAN 产品和解决计划 不得不
  • SD-WAN重要功能-网络加速!

    网络加速是SDWAN的特点之一 xff0c 它可以通过数据压缩和纠错来实现网络加速 在计算机通信中 xff0c 主要有两个错误 xff1a 包丢失和错误 错误的原因是一些比特数据的畸变 xff1b 丢失的原因是一些数据包没有收到 底层协议通
  • django3.x 使用haystack 报错:ImportError: cannot import name 'six' from 'django.utils'

    django3 x 使用haystack 报错 xff1a ImportError cannot import name 39 six 39 from 39 django utils 39 原因解决办法 原因 django 3 x 系列删除
  • 机器学习sklearn之预估器(estimator)使用

    预估器 xff08 估计器 xff09 estimator 概述 因为sklearn机器学习算法的实现都属于estimator的子类 xff1a 分类算法 xff1a k 近邻 贝叶斯 逻辑回归 决策树与随机森林 回归算法 xff1a 线性
  • 复杂网络分析——networkx的使用

    1 基本图操作 导包 span class token keyword import span networkx span class token keyword as span nx span class token keyword im
  • python3+android自动化之Uiautomator2

    python3 43 android自动化之Uiautomator2 1 环境准备 xff1a android sdk xff1a 下载地址 https developer android google cn studio releases
  • 复杂网络数据集处理——无向无权最大连通子图

    复杂网络数据集处理 无向无权最大连通子图 前言一 环境Python3networkx 二 代码三 使用说明总结 前言 由于来源不同 xff0c 导致网上公开的网络数据集格式也没有统一规范 xff0c 在进行科学计算时往往由数据格式的差异导致
  • CentOS 6.0 下 VNC 配置方法(带防火墙配置)

    CentOS 6 0 下 VNC 配置方法 xff08 带防火墙配置 xff09 2012 12 03 16 30 53 编辑 删除 转载 标签 xff1a linux tigervnc配置 it 分类 xff1a linux 一 安装 V
  • linux – telnet 【host】【port】,设置超时时间

    http www cocoachina com articles 51080 这将运行不超过2秒 xff1a span class token keyword echo span quit span class token operator
  • 基于python 自己动手写一个简单的web服务器

    1 前言 如今一提到服务器首先就想到 apache tomcat nginx等 虽然这些服务器很优秀 但是对于我们平时拿来练手的一些小项目来说却是大材小用 xff0c 杀鸡用牛刀 xff0c 而且上述主流服务器配置起来也略嫌麻烦 俗话说自己
  • 基于Python的逆波兰表达式的转换与求值

    一 逆波兰表达式简介 逆波兰式 xff08 Reverse Polish notation xff0c RPN xff0c 或逆波兰记法 xff09 xff0c 也叫后缀表达式 xff08 将运算符写在操作数之后 xff09 而与之对应的是
  • Invalid bound statement (not found):出现的原因和解决方法

    解决错误的步骤 出现了什么错误可能导致的原因解决办法 出现了什么错误 错误截图 xff1a BindingException 数据绑定异常 not found 找不到 org apache ibatis binding BindingExc
  • 软件设计师笔记:软件工程

    文章目录 一 软件开发模型1 1 瀑布模型 xff08 SDLC xff09 1 2 其他经典模型 xff08 原型模型 增量模型 演化模型 xff09 1 3 螺旋模型1 4 V模型1 5 喷泉模型与RAD1 6 构件组装模型 xff08
  • stm32 串口调试配置及printf重定向

    目录 前言一 stm32串口配置1 使用cubemx生成2 手敲代码通过hal库配置 二 printf重映射1 寄存器2 hal库 xff08 推荐 xff09 前言 stm32使用串口调试打印时 xff0c 需要对串口进行配置并对prin
  • 机器学习(十一)——集成学习

    集成学习 xff08 ensemble learning xff09 通过构建并结合多个学习器来完成学习任务 根据个体学习器的生成方式 xff0c 目前集成学习的方法大致分为两类 xff0c 即个体学习器之间存在强依赖关系 xff0c 必须

随机推荐