FreeRTOS-内核控制函数

2023-11-01

FreeRTOS-内核控制函数

  • FreeRTOS中有一些内核函数,一般来说这些内核函数在应用层不会使用,但是内核控制函数是理解FreeRTOS中断的基础。接下来我们逐一分析这些内核函数。

taskYIELD()

  • 该函数的作用是进行任务切换,这是一个宏定义,实际上调用了portYIELD()。portYIELD函数定义如下:
#define portYIELD()																\
{																				\
	/* Set a PendSV to request a context switch. */								\
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;								\
																				\
	/* Barriers are normally not required but do ensure the code is completely	\
	within the specified behaviour for the architecture. */						\
	__dsb( portSY_FULL_READ_WRITE );											\
	__isb( portSY_FULL_READ_WRITE );											\
}
  • 该函数中实际进行任务切换的是portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT。我们深入来看一下portNVIC_INT_CTRL_REG 的定义:
#define portNVIC_INT_CTRL_REG		( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
  • 看到这里可能会有点懵,因为这里涉及到Cortex-M3内核的一部分知识,《cortex-M3权威指南》提到这一部分如下表所示:
    在这里插入图片描述
  • 该宏定义对应的地址是0xe000ed04,这证号和中断控制及状态寄存器ICSR地址对应上,上图中我们仅截取了一部分,在分析这里时只需要知道这一部分就行了。接下来我们再看一下portNVIC_PENDSVSET_BIT的定义:
#define portNVIC_PENDSVSET_BIT		( 1UL << 28UL )
  • 到这里其实这段代码的意思就很清晰了,其实就是将ICSR的第28位置1。那么置1和进行任务切换有什么关系呢?我们在描述中可见这时会将pendSV悬起,那么pendSV又是什么?
  • pendSV是可悬起的系统调用,当软件将PendSV悬起寄存器写入1时,会执行一个任务切换,如果当前任务优先级高于其他任务,则会延缓切换,否则直接切换。
  • 到这里我们将上面的过程串起来就是:当调用taskYIELD()函数时,实际上就是想pendSV悬起寄存器里写入1,然后硬件自动进行任务切换。

taskENTER_CRITICAL()

  • 当有一部分代码希望不被FreeRTOS所管理的高优先级任务打断时,可以调用该函数进入临界区,进入临界区后将会屏蔽操作系统所能管理的任务切换和中断。该函数其实是调用了void vPortEnterCritical( void ),函数定义如下:
void vPortEnterCritical( void )
{
	portDISABLE_INTERRUPTS();
	uxCriticalNesting++;

	/* This is not the interrupt safe version of the enter critical function so
	assert() if it is being called from an interrupt context.  Only API
	functions that end in "FromISR" can be used in an interrupt.  Only assert if
	the critical nesting count is 1 to protect against recursive calls if the
	assert function also uses a critical section. */
	if( uxCriticalNesting == 1 )
	{
		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
	}
}
  • 该函数定义主要就两句,一个是调用了portDISABLE_INTERRUPTS()函数,另外就是让全局变量uxCriticalNesting自加一。我们先来分析一下portDISABLE_INTERRUPTS()函数,其定义如下(portDISABLE_INTERRUPTS()是一个宏定义,真正的函数是vPortRaiseBASEPRI):
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}
}
  • 这是一段汇编代码,其实主要就是将configMAX_SYSCALL_INTERRUPT_PRIORITY这个宏赋值给basepri寄存器,我们先来看看basepri寄存器是干什么的,在CM3权威指南中有如下说明:
    在这里插入图片描述

  • 该寄存器被称为中断可屏蔽器,作用是用于屏蔽优先级低于某个数值的中断,从而保证对时间要求严格的任务能够按照预期来完成任务。并且上表中的所有寄存器是CM3的特殊功能寄存器,只能由指令MSR\MRS访问,所以代码中的msr指令可以理解为专门为这些特殊功能寄存器设置的赋值命令。那么如何正确的赋值才能达到我们预期的屏蔽效果呢?这是一个比较复杂的问题,下面一步一步来分析。

  • 要解决这个问题,就必须要从中断优先级分组说起,CM3中划分为两种优先级,一种是抢占优先级,另一种是子优先级(或响应优先级)。CM3支持高达256级可编程优先级,相应地由一个8位的寄存器用于表达这256级的优先级。但是CM最多支持128级抢占优先级,而不是256级,这是因为内核在出厂时厂家就规定了表达抢占优先级的位只能在[7:1]这个区间内,因此抢占优先级最高为128级,相应优先级最高为256级,这是CM3中的优先级定义。下图是优先级分组表。
    在这里插入图片描述

  • 但是单片机在设计的时候并不会将这256个优先级全部使用,因为如果全部使用的话芯片架构将会变得很复杂,因此,stm32仅仅拿出了这8位中的高4位来表达它的优先级,低4位则无效。因此,stm32最高支持16级的优先级。那么如何来确定优先级的高低呢?实际上这同使用库函数编程的时候判断方法是相同的,因stm32为例,假定分组位置为3,全部设置为抢占优先级,如果往寄存器中写入0001 xxxx表达的优先级要比往寄存器中写入0011 xxxx表达的优先级高。知道了这些以外还需要明确一点的是,设置BASEPRI寄存器屏蔽优先级的方法和设置中断优先级的方法是相同的,因为这里只有4位来表示中断优先级,因此要屏蔽优先级不高于5的中断(注意CM3内核中始终认为优先级数字为0的那一个是最高优先级,优先级数字越大优先级越低),只需要以下指令即可:

    msr basepri 5<<4

  • 到这里们就知道了CM3中断优先级分组和设置中断优先级以及屏蔽中断优先级的原则了,那么回到代码中继续分析宏configMAX_SYSCALL_INTERRUPT_PRIORITY
    其定义如下:

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY	5 
#define configPRIO_BITS       		__NVIC_PRIO_BITS
#define __NVIC_PRIO_BITS          4
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
  • 所以将宏定义转化为式子就是
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (5<<(8-4))
  • 这不就相当于将那些优先级不高于5的中断给屏蔽了吗?所以从这里也可以看出,我配置的FreeRTOS最高可以管理优先级为5的中断,那些更高优先级的中断如1,2等FreeRTOS是管理不了的。到这里其实还是有一个问题,那就是,我们这里只是屏蔽了优先级不高于5的中断,而更高优先级的任务不是同样会抢占CPU使用权吗,这样不也就会导致任务切换吗?实际上,在FreeRTOS中,任务的切换也是由中断产生的而这个,在前面我们已经分析了只需要将pendSV中的悬起寄写入1即可产生中断,从而触发任务切换,另外就是SysTick这个系统时钟,当这个系统时钟产生中断时,同样可以发生任务切换。这是任务切换的两种途径,既然任务切换只能由这两种途径发生,那么如果我们进入临界区的时候也将这两个中断给屏蔽掉,那么将不再产生任务切换,也就没有高优先级任务抢占的发生了,这样不就解决问题了吗?所以下面我们来看一下这两个中断的中断优先级是多少,在port.c文件和FreeRTOSConfig.h中找到了这两个中断的优先级有关的宏,
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY			15  
#define configPRIO_BITS       		__NVIC_PRIO_BITS
#define __NVIC_PRIO_BITS          4
#define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define portNVIC_PENDSV_PRI					( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL )
#define portNVIC_SYSTICK_PRI				( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )
  • 从上面大致看出,这两个优先级很有可能会被设置为最低,如果真是这样,那么,nice!我们的问题已经解决了,但是如果不是,那么将会很头疼。接下来就深入分析一下这两个中断的优先级是否真的为最低。我们再回到CM3权威指南中找到了两个优先级设置的寄存器,表格如下:
    在这里插入图片描述
  • 从上面可以看出我们只需要设置PRI_14和PRI_15这两个寄存器就可以设置其中断优先级。我们再看看代码中是不是这样设置的,找到这两个中断优先级的引用的地方,发现在启动任务调度器中有这两个语句:
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;

portNVIC_SYSPRI2_REG定义如下:

#define portNVIC_SYSPRI2_REG				( * ( ( volatile uint32_t * ) 0xe000ed20 ) )
  • 发现它的对应地址是0xE000ED20,而PendSV对应的是0xE000ED22,SysTick对应的是0xE000ED23,似乎不对呀?但是不要忘了,portNVIC_PENDSV_PRI对应的宏定义是有2字节的移位的,这样经过移位之后刚好对应0xE000ED22,而且其中断优先级为15,即最低的。SysTick的优先级也是这么设置的。
#define portNVIC_PENDSV_PRI					( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL )
  • 到这里进入中断临界区的深入分析已经结束了,看似简单的调用和赋值其实蕴含了很多CM3核心的知识。
  • 再总结一下过程:当要进入临界区的时候,会将系统所能管理的中断全部屏蔽掉,相应的触发任务切换的中断也会屏蔽掉,从而不会受到高优先级任务抢占和中断的影响,确保了对时间要求严格的任务如期进行。但是同样的,有一些更高优先级(不由FreeRTOS管理的)的中断触发的话是会影响到任务运行的。

taskEXIT_CRITICAL()

  • 既然有进入临界区,那么必然也会有退出临界区,退出临界区和进入临界区背后的原理是相同的,下面是退出临界区的函数定义(实际上调用了vPortExitCritical()函数):
void vPortExitCritical( void )
{
	configASSERT( uxCriticalNesting );
	uxCriticalNesting--;
	if( uxCriticalNesting == 0 )
	{
		portENABLE_INTERRUPTS();
	}
}
  • 退出临界区的代码很简单,首先uxCriticalNesting自减1,uxCriticalNesting的作用就是记录进入临界区的嵌套次数,每进入一次就自加1,退出1次就自减1,知道uxCriticalNesting=0是恢复所有中断。下面来看一下portENABLE_INTERRUPTS()函数。
#define portENABLE_INTERRUPTS()					vPortSetBASEPRI( 0 )

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
	__asm
	{
		/* Barrier instructions are not used as this function is only used to
		lower the BASEPRI value. */
		msr basepri, ulBASEPRI
	}
}
  • 由此可见,只是将0赋值给basepri中断屏蔽寄存器,在CM3权威指南中提到,如果往basepri中写0,那么所有被屏蔽的中断都将被接触,从而完全退出了临界区,原理同进入一样,这里就不仔细分析了。

taskENTER_CRITICAL_FROM_ISR()

  • 中断级进入临界区和任务级进入临界区非常相似,从下面的函数定义中就可以发现:
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		mrs ulReturn, basepri
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}

	return ulReturn;
}
  • 中断级的进入临界区只是将中断屏蔽寄存器的值取出来,作为函数返回值返回了,其他并没有区别。所以这里不再赘述了。

taskEXIT_CRITICAL_FROM_ISR()

  • 原理与任务级相同,不再赘述了。

taskDISABLE_INTERRUPTS()

  • 同样是调用vPortRaiseBASEPRI()函数,将FreeRTOS所能管理的中断全部屏蔽。

taskENABLE_INTERRUPTS()

  • 同样是调用vPortSetBASEPRI( 0 )函数,将FreeRTOS所能管理的中断全部开启。

vTaskSupendAll()

  • 该函数用于挂起任务调度器,当任务调度器挂起之后,任务调度器相当于暂停工作了,不会产生任务调度。任务调度器挂起操作很简单,函数定义如下:
void vTaskSuspendAll( void )
{
	++uxSchedulerSuspended;
}
  • 代码仅有1行,就是将uxSchedulerSuspended这个全局变量自加1,任务挂起可以嵌套,当uxSchedulerSuspended=0时,才表示任务调度器解挂,这一点在讲述任务调度器解挂操作会分析。那么uxSchedulerSuspended变量自加1后会对任务切换等产生什么影响呢?下面是截取的相关函数:
BaseType_t xTaskIncrementTick( void )
{
   ···
   
	traceTASK_INCREMENT_TICK( xTickCount );
	if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
	{
		/* Minor optimisation.  The tick count cannot change in this
		block. */
		const TickType_t xConstTickCount = xTickCount + 1;
		xTickCount = xConstTickCount;

		if( xConstTickCount == ( TickType_t ) 0U )
		{
			taskSWITCH_DELAYED_LISTS();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
		···
	}
   ···
}
  • 从上面代码中可以看出,只有当uxSchedulerSuspended = 0时,即任务调度器没有被挂起时,滴答定时器计数变量才会自加1,也就相当于关闭了滴答定时器计数。下面一段代码是关于任务切换的。

void vTaskSwitchContext( void )
{
	if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
	{
		/* The scheduler is currently suspended - do not allow a context
		switch. */
		xYieldPending = pdTRUE;
	}
	else
	{
		xYieldPending = pdFALSE;
		traceTASK_SWITCHED_OUT();

		#if ( configGENERATE_RUN_TIME_STATS == 1 )
		{
				#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
					portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
				#else
					ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
				#endif

				if( ulTotalRunTime > ulTaskSwitchedInTime )
				{
					pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
				ulTaskSwitchedInTime = ulTotalRunTime;
		}
		#endif /* configGENERATE_RUN_TIME_STATS */

		/* Check for stack overflow, if configured. */
		taskCHECK_FOR_STACK_OVERFLOW();

		/* Select a new task to run using either the generic C or port
		optimised asm code. */
		taskSELECT_HIGHEST_PRIORITY_TASK();
		traceTASK_SWITCHED_IN();

		#if ( configUSE_NEWLIB_REENTRANT == 1 )
		{
			/* Switch Newlib's _impure_ptr variable to point to the _reent
			structure specific to this task. */
			_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
		}
		#endif /* configUSE_NEWLIB_REENTRANT */
	}
}
  • 从上面代码中可见,调度器挂起的时候不会进行任务切换,这样就相当于直接关掉了任务切换。
  • 所以 uxSchedulerSuspended 变量是在函数中通过判断来实现关闭任务切换和滴答定时器的计数。

xTaskResumeAll()

  • 任务调度器的解挂就是判断uxSchedulerSuspended是否为0,如果为0则进行解挂操作。系统中调用多少次vTaskSupendAll()就需要调用多少次xTaskResumeAll()才能达到任务调度器解挂的操作。下面来深入分析该函数的源码。

BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;

	/* If uxSchedulerSuspended is zero then this function does not match a
	previous call to vTaskSuspendAll(). */
	configASSERT( uxSchedulerSuspended );

	taskENTER_CRITICAL();
	/*************** 第一部分 *****************/
	{
		--uxSchedulerSuspended;----(1)

		if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )----(2)
		{
			if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )----(3)
			{

				while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )----(4)
				{
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );----(5)
					( void ) uxListRemove( &( pxTCB->xEventListItem ) );----(6)
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );----(7)
					prvAddTaskToReadyList( pxTCB );----(8)

					if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )----(9)
					{
						xYieldPending = pdTRUE;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

				if( pxTCB != NULL )----(10)
				{

					prvResetNextTaskUnblockTime();
				}
				
/*************** 第二部分 *****************/
				{
					UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */

					if( uxPendedCounts > ( UBaseType_t ) 0U )----(11)
					{
						do
						{
							if( xTaskIncrementTick() != pdFALSE )----(12)
							{
								xYieldPending = pdTRUE;
							}
							else
							{
								mtCOVERAGE_TEST_MARKER();
							}
							--uxPendedCounts;----(13)
						} while( uxPendedCounts > ( UBaseType_t ) 0U );----(14)

						uxPendedTicks = 0;----(15)
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

				if( xYieldPending != pdFALSE )----(16)
				{
					#if( configUSE_PREEMPTION != 0 )
					{
						xAlreadyYielded = pdTRUE;
					}
					#endif
					taskYIELD_IF_USING_PREEMPTION();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	taskEXIT_CRITICAL();

	return xAlreadyYielded;
}
  • xTaskResumeAll()函数主要完成两个任务,一个是任务列表的变换,另一个是时间补偿。下面分别来深入分析这两部分。
  • 更改任务列表主要是因为,当任务调度器被挂起的时候,如果有中断导致任务调度,但这时候任务调度器处于挂起状态,无法进行任务切换,所以该任务就将被分配到xPendingReadyList中,等到任务调度器解除挂起后,立刻恢复到就绪列表中。所以在代码中描述为:
    (1)、uxSchedulerSuspended自减1
    (2)、如果任务调度器解挂
    (3)、判断系统中任务数量,如果系统中没有任务则不需要继续进行了。
    (4)、循环直到将xPendingReadyList中的任务全部移动到就绪列表中
    (5)、获取处于xPendingReadyList中的任务控制块
    (6)、任务控制块中有两个列表项,一个是事件列表,另一个是状态列表,如果要将任务添加到就绪列表中,就需要将这两个列表全部移除。所以该句就是将任务控制块中的事件列表移除。
    (7)、相应的,该句是将任务控制块中的状态列表移除。
    (8)、将该任务添加到就绪列表中
    (9)、判断刚添加到就绪列表中的任务优先级是否大于正在运行的任务的任务优先级,大于则进行任务切换
    (10)、重新设置下一个任务解除阻塞的时间节点,即距现在最近的那个任务节点
  • 由于任务调度器挂起后,滴答定时器的计数器不再工作,所以中间有一段时间没有记录时间点,所以第二部分的所要干的就是将这一部分时间补偿回来。虽然滴答定时器的计数器不工作了,但是这时候另一个计数器uxPendedCounts 会代替xTickCount继续计数。这样只需要重复调用uxPendedCounts 次xTaskIncrementTick()函数就可以将这段时间补偿回来。下面来分析这部分代码。
    (11)、判断uxPendedCounts计数次数是否大于0,只有在大于0的时候才有能正常补偿时间
    (12)、调用xTaskIncrementTick()进行一次时间补偿,并判断是否需要进行任务切换,该函数通过返回pdTRUE来表示需要进行任务切换
    (13)、uxPendedCounts自减1
    (14)、循环直到时间完全补偿
    (15)、判断是否需要进行任务切换

到这里我们就将FreeRTOS的一些内核控制函数讲述完了,FreeRTOS内核控制函数相对来说比较难,而且不好理解,关系错综复杂。

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

FreeRTOS-内核控制函数 的相关文章

  • doxygen教程-6-把注释放在其他位置

    文章目录 把注释放到成员之后 注释在头文件和源文件的分布 Structural commands 把注释放到 几乎 任意位置 只有先注释高层级的结构 才会显示低层级的注释 把注释放到成员之后 这里出现了新词 成员 member 在官方手册里

随机推荐

  • ubuntu18.04更新clang的版本

    可选 添加bionic相关的源 1 备份 etc apt sources list 2 添加源到sources list文件最后 sudo vim etc apt sources list deb http archive ubuntu c
  • 模型结构可视化神器——Netron(支持tf, caffe, keras,mxnet等多种框架)

    转自 https blog csdn net leviopku article details 81980249 很多时候 复现人家工程的时候 需要了解人家的网络结构 但不同框架之间可视化网络层方法不一样 这样给研究人员造成了很大的困扰 前
  • 【华为OD机试真题2023 JS】几何平均值最大子数组

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 几何平均值最大子数组 知识点数组二分查找 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 从一个长度为N的正数数组numbers中找出长度至少为L且几何平均值最大
  • Kubernetes1.91(K8s)安装部署过程(四)--Master节点安装

    再次明确下架构 三台虚拟机 centos 7 4系统 docker为17版本 ip为10 10 90 105到107 其中105位master 接下来的master相关组件安装到此机器上 etcd集群为3台 分别复用这3台虚拟机 作为k8s
  • Docker安装(使用阿里云镜像)

    Docker安装 使用阿里云镜像 Docker从1 13版本之后采用时间线的方式作为版本号 分为社区版CE和企业版EE 社区版是免费提供给个人开发者和小型团体使用的 企业版会提供额外的收费服务 比如经过官方测试认证过的基础设施 容器 插件等
  • webp图片在html上显示,WebP 格式图片在实际项目中的使用方式

    一 前言 关于 WebP 的支持情况 以及适用场景此处不做详细说明 具体见官方文档 先说结论 目前 WebP 支持情况占比较大 数据如下 在适合的场景下可以使用 WebP 格式图片来提高页面加载速度 支持的浏览器占比达到近90 数据来源 C
  • word 安装

    亲测可用 记录一下 安装word步骤 office tool plus 概述 安装激活 1 下载安装 2 解压 3 移除 4 安装部署 5 激活 总结 概述 Office Tool Plus 是一个强大的 Office 部署工具 可以很方便
  • CentOS7安装Docker&配置阿里云镜像仓库

    安装Docker 1 安装yum utils包 yum install y yum utils 2 设置yum源为阿里云 提升下载速度 yum config manager add repo http mirrors aliyun com
  • Unity3D碰撞检测和OnTriggerEnter用法

    在目前掌握的情况分析 在Unity中参与碰撞的物体分2大块 1 发起碰撞的物体 2 接收碰撞的物体 1 发起碰撞物体有 Rigodbody CharacterController 2 接收碰撞物体由 所有的Collider 工作的原理为 发
  • 机器学习笔记 - 使用 ResNet-50 和余弦相似度的基于图像的推荐系统

    一 简述 这里的代码主要是基于图像的推荐系统 该系统利用 ResNet 50 深度学习模型作为特征提取器 并采用余弦相似度来查找给定输入图像的最相似嵌入 该系统旨在根据所提供图像的视觉内容为用户提供个性化推荐 二 所需环境 Python 3
  • 【论文不精读】Reinforced Path Reasoning for Counterfactual Explainable Recommendation

    Reinforced Path Reasoning for Counterfactual Explainable Recommendation 1 Introduction 现代推荐系统在对复杂的用户或者商品上下文进行建模时变得十分复杂并且
  • 【C++】---继承

    文章目录 继承的概念与定义 继承的定义格式 父类和子类的对象赋值转换 继承中的作用域 子类的默认成员函数 菱形继承 虚拟继承 总结 继承的概念与定义 继承是面向对象编程三大特性之一 是一种可以使代码复用最重要的手段 在原有类特性的基础上进行
  • TPS-1教学:TPS-1 PROFINET Demo Board概述视频

    TPS 1教学 TPS 1 PROFINET Demo Board概述 电子发烧友网 elecfans com
  • 用matlab进行模糊识别

    例1 设论域U x1 x2 x3 x4 x5 上的三个模糊模式分别为A 0 7 0 5 0 2 0 4 B 0 8 0 3 0 4 0 2 C 0 6 0 4 0 5 0 3 试判别A和B中 哪个和C最贴近 解 按照 格贴近度 有如下计算
  • C++堆内存分配

    C 堆内存分配 C堆内存分配 抽象与分层 如何扩展有效堆内存 brk和sbrk系统调用 如何维护有效堆内存 operator new 抽象与分层 c和c 的内存服务模型与计算机网络里面的协议分层模型有点类似 计算机网络协议大体分为 层 应用
  • 第二课if语句的练习

    知识点 if判断语句 题目1 训练 李雷想买一个价值7988元的新手机 她的旧手机在二手市场能卖1500元 而手机专卖店推出以旧换新的优惠 把她的旧手机交给店家 新手机就能够打8折优惠 为了更省钱 李雷要不要以旧换新 请在控制台输出 训练提
  • 手把手教你用Keras进行多标签分类(附代码)

    作者 Adrian Rosebrock 翻译 程思衍 校对 付宇帅 本文约7000字 建议阅读10 分钟 本文将通过拆解SmallVGGNet的架构及代码实例来讲解如何运用Keras进行多标签分类 本文的灵感来源于我收到的一封来自PyIma
  • 「Kubernetes」- 使用 Fluentd 收集日志 @20210128

    服务搭建流程概览 1 确定需要收集的日志及位置 2 搭建日志收集服务 Elasticsearch Kibana Fluentd 3 验证日志收集成功 能够查看 集群环境概述 操作系统 CentOS Linux release 7 4 170
  • 3d wallpaper android,3D Parallax Wallpaper

    Increase your home screen liveliness with 3D Parallax Wallpaper StoreThis app is not the usual 3D Live wallpaper but it
  • FreeRTOS-内核控制函数

    FreeRTOS 内核控制函数 FreeRTOS中有一些内核函数 一般来说这些内核函数在应用层不会使用 但是内核控制函数是理解FreeRTOS中断的基础 接下来我们逐一分析这些内核函数 taskYIELD 该函数的作用是进行任务切换 这是一