FreeRTOS学习笔记(二)——内核机制

2023-05-16

文章目录

    • 0x01 临界段
      • Cortex-M内核快速关中断指令
      • 关中断
      • 开中断
      • 进入和退出临界段的宏
      • 进入临界段
      • 退出临界段
      • 临界段代码应用
    • 0x02 空闲任务与阻塞延时的实现
      • 空闲任务的创建
      • 实现阻塞延时
      • 阻塞等待总结
    • 0x03 多优先级
      • 通用方法:
      • taskRECORD_READY_PRIORITY()
      • taskSELECT_HIGHEST_PRIORITY_TASK()
      • 优化方法:
      • taskRECORD_READY_PRIORITY()
      • taskRESET_READY_PRIORITY()
      • taskSELECT_HIGHEST_PRIORITY_TASK()
      • prvAddTaskToReadyList()
    • 0x04 任务延时列表
      • 任务延时列表初始化
      • 定义xNextTaskUnblockTime
      • prvAddCurrentTaskToDelayedList()
      • xTaskIncrementTick()
      • taskSWITCH_DELAYED_LISTS()
      • taskRESET_READY_PRIORITY()
    • 0x05 时间片
    • 0x06 总结

0x01 临界段

临界段用一句话概括就是一段在执行的时候不能被中断的代码段。临界段会被打断是在以下两种情况下:一个是系统调度,还有一个就是外部中断。在FreeRTOS,系统调度,最终也是产生 PendSV 中断,在 PendSV Handler 里面实现任务的切换,所以还是可以归结为中断。既然这样,FreeRTOS 对临界段的保护最终还是回到对中断的开和关的控制

Cortex-M内核快速关中断指令

三个中断屏蔽寄存器有:PRIMASKFAULTMASTBASEPRI

在FreeRTOS上对中断的开和关使用的寄存器是BASEPRI,在 FreeRTOS 中,对中断的开和关是通过操作 BASEPRI 寄存器来实现的,即大于等于 BASEPRI 的值的中断会被屏蔽,小于 BASEPRI 的值的中断则不会被屏蔽,不受FreeRTOS 管理。用户可以设置 BASEPRI 的值来选择性的给一些非常紧急的中断留一条后路。

关中断

FreeRTOS 关中断的函数在 portmacro.h 中定义,分不带返回值带返回值两种:

对于不带返回值的关中断函数,不可以进行嵌套,不能在中断中进行使用:

#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
	uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}
}
  • 不带返回值的关中断函数,不能嵌套,不能在中断里面使用。不带返回值的意思是:在往 BASEPRI 写入新的值的时候,不用先将 BASEPRI 的值保存起来,即不用管当前的中断状态是怎么样的,既然不用管当前的中断状态,也就意味着这样的函数不能在中断里面调用。
  • configMAX_SYSCALL_INTERRUPT_PRIORITY 是 一 个 在FreeRTOSConfig.h 中定义的宏,即要写入到 BASEPRI 寄存器的值。存入后高四位有效,优先级大于此值会被屏蔽,小于此值的中断不受RTOS管理。
  • 将 configMAX_SYSCALL_INTERRUPT_PRIORITY 的值写入BASEPRI 寄存器,实现关中断(准确来说是关部分中断)。

对于带返回值的关中断函数,可以进行嵌套,可以在中断中进行使用:

#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()

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;
}
  • 带返回值的关中断函数,可以嵌套,可以在中断里面使用。带返回值的意思是:在往 BASEPRI 写入新的值的时候,先将 BASEPRI 的值保存起来,在更新完BASEPRI 的值的时候,将之前保存好的 BASEPRI 的值返回,返回的值作为形参传入开中断函数

  • 其configMAX_SYSCALL_INTERRUPT_PRIORITY的作用同上。

  • 保存 BASEPRI 的值,记录当前哪些中断被关闭。

  • 更新 BASEPRI 的值。

  • 返回原来 BASEPRI 的值。

开中断

#define portENABLE_INTERRUPTS()					vPortSetBASEPRI(0)   	// 不带中断保护的开中断函数
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)	vPortSetBASEPRI(x)	   // 带中断保护的开中断函数

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
	}
}
  • 开中断函数,具体是将传进来的形参更新到 BASEPRI 寄存器。根据传进来形参的不同,分为中断保护版本与非中断保护版本。
  • 不带中断保护的开中断函数,直接将 BASEPRI 的值设置为 0,与portDISABLE_INTERRUPTS()成对使用。
  • 带中断保护的开中断函数,将上一次关中断时保存的 BASEPRI 的值作为形参 ,与 portSET_INTERRUPT_MASK_FROM_ISR()成对使用。

进入和退出临界段的宏

进入和退出临界段的宏分中断保护版本非中断版本,但最终都是通过开/关中断来实现,主要是由如下的宏配置:

#define taskENTER_CRITICAL() 			portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() 	portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL() 			portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

进入临界段

不带中断保护,不可以进行嵌套:

#define portENTER_CRITICAL() 			vPortEnterCritical()
#define portENTER_CRITICAL() 			vPortEnterCritical()
void vPortEnterCritical( void )
{
	portDISABLE_INTERRUPTS();
    
   	// uxCriticalNesting 是在 port.c 中定义的静态变量,表示临界段嵌套计数 器 , 默认初始化为 0xaaaaaaaa , 在 调 度 器 启动时会被重新初始化为0 :vTaskStartScheduler()->xPortStartScheduler()->uxCriticalNesting = 0。
	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 )
	{
        // 如果 uxCriticalNesting 等于 1,即一层嵌套,要确保当前没有中断活跃,即内核外设 SCB 中的中断和控制寄存器 SCB_ICSR 的低 8 位要等于 0。
		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
	}
}

#define portDISABLE_INTERRUPTS() 			vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
	uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}
}

带中断保护保本,可以进行嵌套:

#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()

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;
}

退出临界段

不带中断保护版本,不能嵌套:

#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define portEXIT_CRITICAL() vPortExitCritical()

void vPortExitCritical( void )
{
	configASSERT( uxCriticalNesting );
	uxCriticalNesting--;
	if( 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
	}
}

带中断的保护版本,可以嵌套:

#define taskEXIT_CRITICAL_FROM_ISR( x ) 		portCLEAR_INTERRUPT_MASK_FROM_ISR( x ) 
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) 	vPortSetBASEPRI(x)

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
	}
}

临界段代码应用

进程进入临界区的调度原则是:

  • 如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。
  • 任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待。
  • 进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。
  • 如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象

访问一个被多任务共享,或是被多任务和中断共享的资源时,需要采用**“互斥”技术**以保证数据在任何时候都保持一致性。这样做的目的是要确保任务从开始访问资源就具有排它性,直到这个资源又恢复到完整状态。

可以使用在中断场合场合,可以使用如下代码进行操作:

uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();

/* 临界段代码 */

/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR( ulReturn );

对于在非中断场合,临界段不可以进行嵌套:

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

/* 临界段代码 */

/* 退出临界段*/
taskEXIT_CRITICAL();

0x02 空闲任务与阻塞延时的实现

在实现打印效果的时候,可以发现其打印时间并不是“一起发生的”,而是间隔着一秒一秒的时间发生的,类似于裸机系统的处理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PgcLnxwp-1682495949162)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230417115221475.png)]

这里使用的延时效果是使用软件延时,也就是还让CPU空着等来达到延时的效果,这样其实也是一种浪费资源的行为。RTOS中的延时称为阻塞延时,即任务需要延时的时候会放弃CPU的使用权,CPU可以去干其他事情。当任务进行延时时,如果CPU没有其他任务需要运行,RTOS会给CPU创建一个空闲任务给CPU执行。

空闲任务为FreeRTOS中启动调度器的时候创建的优先级最低的任务。空闲任务主要是做一些系统内存的清理工作。在实际应用中,当系统进入空闲任务时,可在空闲任务中让单片机进入休眠或者低功耗等操作。

空闲任务的创建

  • 定义空闲任务的栈

    #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) 
    StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; 
    
  • 定义空闲任务的任务控制块

    TCB_t IdleTaskTCB;
    
  • 创建空闲任务

    extern TCB_t IdleTaskTCB;
    void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer,
                                        StackType_t **ppxIdleTaskStackBuffer,
                                        uint32_t *pulIdleTaskStackSize );
    void vTaskStartScheduler( void )
    {
        TCB_t *pxIdleTaskTCBBuffer = NULL; 			/* 用于指向空闲任务控制块 */ 
        StackType_t *pxIdleTaskStackBuffer = NULL;	/* 用于空闲任务栈起始地址 */ 
        uint32_t ulIdleTaskStackSize;
        
        /* 获取空闲任务的内存:任务栈和任务 TCB */ 
        vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, 
                                        &pxIdleTaskStackBuffer, 
                                        &ulIdleTaskStackSize );
        /* 创建空闲任务 */ 
        xIdleTaskHandle = 
                    xTaskCreateStatic( (TaskFunction_t)prvIdleTask, /* 任务入口 */ 
                    (char *)"IDLE", /* 任务名称,字符串形式 */ 
                    (uint32_t)ulIdleTaskStackSize , /* 任务栈大小,单位为字 */ 
                    (void *) NULL, /* 任务形参 */ 
                    (StackType_t *)pxIdleTaskStackBuffer, /* 任务栈起始地址 */ 
                    (TCB_t *)pxIdleTaskTCBBuffer ); /* 任务控制块 */ 
        /* 将任务添加到就绪列表 */ 
        // 将空闲任务插入到就绪列表的开头。
        vListInsertEnd( &( pxReadyTasksLists[0] ), 
        &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) );
        
        ...
    }
    

    函数vApplicationGetIdleTaskMemory用于获取空闲任务的内存,即将pxIdleTaskTCBBufferpxIdleTaskStackBuffer这两个接下来要作为形参传到xTaskCreateStatic函数中的指针分别指向空闲任务的TCB和栈的起始地址,这个操作由函数vApplicationGetIdleTaskMemory()来实现,该函数需要用户自定义。

    void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
    {
          *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
          *ppxIdleTaskStackBuffer = &xIdleStack[0];
          *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
          /* place for user code */
    }
    

实现阻塞延时

  • 使用vTaskDelay()

阻塞延时的阻塞是指任务调用该延时函数后,任务会被剥离 CPU 使用权,然后进入阻塞状态,直到延时结束,任务重新获取 CPU 使用权才可以继续运行。在任务阻塞的这段时间,CPU 可以去执行其它的任务,如果其它的任务也在延时状态,那么 CPU 就将运行空闲任务。

#if ( INCLUDE_vTaskDelay == 1 )

	void vTaskDelay( const TickType_t xTicksToDelay )
	{
		BaseType_t xAlreadyYielded = pdFALSE;

		/*延迟时间为零只是强制重新安排。 */
		if( xTicksToDelay > ( TickType_t ) 0U )
		{
			configASSERT( uxSchedulerSuspended == 0 );
			vTaskSuspendAll();
			{
				traceTASK_DELAY();

                /*从事件列表中删除任务,调度器被挂起不会被放置在准备中,列表或从阻塞列表中删除,直到调度程序已恢复*/
                /*此任务不能在事件列表中,因为它是当前任务执行任务*/
				prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
			}
			xAlreadyYielded = xTaskResumeAll();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/* Force a reschedule if xTaskResumeAll has not already done so, we may
		have put ourselves to sleep. */
		if( xAlreadyYielded == pdFALSE )
		{
			portYIELD_WITHIN_API();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

#endif /* INCLUDE_vTaskDelay */

xTicksToDelay 是任务控制块的一个成员,用于记录任务需要延时的时间,单位为 SysTick 的中断周期

  • 修改**vTaskSwitchContext()**函数,在这里多了个空闲任务,在这里可以通过判断当前任务是否为空闲任务,之后执行对应的程序,去判断延时时间是否已经到达,若到达则执行,若没有到达则切换

  • SysTick中断服务函数xPortSysTickHandler(),在FreeRTOS中,xTicksToDelay是以SysTick中断提供,操作系统里面的最小的时间单位就是SysTick 的中断周期。

    void xPortSysTickHandler( void )
    {
        // 关中断
    	vPortRaiseBASEPRI();
    	{
            //更新系统时基
    		if( xTaskIncrementTick() != pdFALSE )
    		{
    			/*需要上下文切换。进行上下文切换PendSV中断。*/
    			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
    		}
    	}
        // 开中断
    	vPortClearBASEPRIFromISR();
    }
    
  • **xTaskIncrementTick()**更新系统时基函数,这个函数主要用于xTicksToDelay变量的递减,更新系统时基计数器 xTickCount,加一操作。xTickCount 是一个在port.c 中定义的全局变量,在函数 vTaskStartScheduler()中调用 xPortStartScheduler()函数前初始化。

    BaseType_t xTaskIncrementTick( void )
    {
    	TCB_t * pxTCB;
    	TickType_t xItemValue;
    	BaseType_t xSwitchRequired = pdFALSE;
    
    	/* 每次发生tick中断时由可移植层调用。增加刻度,然后检查新的刻度值是否会产生任何刻度要解除阻塞的任务*/
    	traceTASK_INCREMENT_TICK( xTickCount );
    	if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
    	{
    		/* 在此块中,刻度计数不能更改。 */
    		const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;
    
    		/* 增加RTOS滴答,如果它包装为0,则切换延迟和溢出的延迟列表。 */
    		xTickCount = xConstTickCount;
    
    		if( xConstTickCount == ( TickType_t ) 0U ) /*防止溢出*/
    		{
    			taskSWITCH_DELAYED_LISTS();
    		}
    		else
    		{
    			mtCOVERAGE_TEST_MARKER();
    		}
    
    		/* 看看这个标记是否使超时过期。任务按照唤醒时间的顺序存储在队列中——这意味着一旦找到了一个没有超时的任务,就不需要再往下看了 */
    		if( xConstTickCount >= xNextTaskUnblockTime )
    		{
    			for( ;; )
    			{
    				if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
    				{
    					/*延迟列表为空。将xNextTaskUnblockTime设置为可能的最大值,这样if(xTickCount >= xNextTaskUnblockTime)测试下次通过的可能性极小。 */
    					xNextTaskUnblockTime = portMAX_DELAY; /*MISRA异常,因为强制转换仅对某些端口是冗余的。 */
    					break;
    				}
    				else
    				{
    					/*延迟列表不为空,获取延迟列表头部项的值。这是必须将处于延迟列表头部的任务从阻塞状态中移除的时间。 */
    					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
    					xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
    
    					if( xConstTickCount < xItemValue )
    					{
    						/*现在还不是解除阻塞的时候,但是项目值是阻塞列表头部的任务必须从阻塞状态中移除的时间,因此在xNextTaskUnblockTime中记录项目值。 */
    						xNextTaskUnblockTime = xItemValue;
    						break;
    					}
    					else
    					{
    						mtCOVERAGE_TEST_MARKER();
    					}
    
    					/* 是时候从Blocked状态中移除项目了。 */
    					( void ) uxListRemove( &( pxTCB->xStateListItem ) );
    
    					/* 任务是否也在等待事件?如果是,将其从事件列表中移除。 */
    					if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
    					{
    						( void ) uxListRemove( &( pxTCB->xEventListItem ) );
    					}
    					else
    					{
    						mtCOVERAGE_TEST_MARKER();
    					}
    
    					/* 将未阻塞的任务放入适当的就绪列表中。 */
    					prvAddTaskToReadyList( pxTCB );
    
    					/* 如果抢占被关闭,正在解除阻塞的任务不会立即导致上下文切换。 */
    					#if (  configUSE_PREEMPTION == 1 )
    					{
    						/* 抢占是打开的,但是只有当未阻塞的任务的优先级等于或高于当前正在执行的任务时,才应该执行上下文切换。 */
    						if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
    						{
    							xSwitchRequired = pdTRUE;
    						}
    						else
    						{
    							mtCOVERAGE_TEST_MARKER();
    						}
    					}
    					#endif /* configUSE_PREEMPTION */
    				}
    			}
    		}
    
    		/*如果抢占开启,且应用程序编写人员没有显式地关闭时间切片,那么与当前运行任务具有相同优先级的任务将共享处理时间(时间切片) */
    		#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
    		{
    			if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
    			{
    				xSwitchRequired = pdTRUE;
    			}
    			else
    			{
    				mtCOVERAGE_TEST_MARKER();
    			}
    		}
    		#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
    
    		#if ( configUSE_TICK_HOOK == 1 )
    		{
    			if( uxPendedTicks == ( UBaseType_t ) 0U )
    			{
    				vApplicationTickHook();
    			}
    			else
    			{
    				mtCOVERAGE_TEST_MARKER();
    			}
    		}
    		#endif /* configUSE_TICK_HOOK */
    	}
    	else
    	{
    		++uxPendedTicks;
    
    		/*即使调度程序被锁定,tick钩子也会定期被调用*/
    		#if ( configUSE_TICK_HOOK == 1 )
    		{
    			vApplicationTickHook();
    		}
    		#endif
    	}
    
    	#if ( configUSE_PREEMPTION == 1 )
    	{
    		if( xYieldPending != pdFALSE )
    		{
    			xSwitchRequired = pdTRUE;
    		}
    		else
    		{
    			mtCOVERAGE_TEST_MARKER();
    		}
    	}
    	#endif /* configUSE_PREEMPTION */
    
    	return xSwitchRequired;
    }
    

    在这里执行完任务切换后,则会退出临界段,然后开中断。

  • 若需要将SysTick的中断服务函数顺利执行,这个时候需要将Systick进行初始化,使用函数vPortSetupTimerInterrupt()

// 配置 SysTick 需要用到的寄存器和宏定义
#define portNVIC_SYSTICK_CTRL_REG			( * ( ( volatile uint32_t * ) 0xe000e010 ) )
#define portNVIC_SYSTICK_LOAD_REG			( * ( ( volatile uint32_t * ) 0xe000e014 ) )

#ifndef configSYSTICK_CLOCK_HZ
	#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ
	/*确保SysTick的时钟频率与内核相同*/
	#define portNVIC_SYSTICK_CLK_BIT	( 1UL << 2UL )
#else
	/*SysTick的打卡方式不会被修改,以防它与内核不一样。*/
	#define portNVIC_SYSTICK_CLK_BIT	( 0 )
#endif

#define portNVIC_SYSTICK_INT_BIT			( 1UL << 1UL )
#define portNVIC_SYSTICK_ENABLE_BIT			( 1UL << 0UL )

SysTick 初始化函 数 vPortSetupTimerInterrupt() , 在**xPortStartScheduler()**中被调用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5JNbyN4t-1682495949163)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230417170359749.png)]

设置重装载寄存器的值,决定 SysTick 的中断周期。同时 configTICK_RATE_HZ 也在 FreeRTOSConfig.h 中定义:

#define configCPU_CLOCK_HZ (( unsigned long ) 25000000) 
#define configTICK_RATE_HZ (( TickType_t ) 100)

在函数vPortSetupTimerInterrupt()中,配置SysTick 每秒中断多少次,目前配置为100,即每10ms中断一次,最后设置系统定时器的时钟等于内核时钟,使能 SysTick 定时器中断,使能 SysTick 定时器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1aoZXpo8-1682495949164)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230417165015597.png)]

阻塞等待总结

在这里总结一下如何实现阻塞等待:

  • 在前面我们在配置时钟的时候得到了我们当前系统时钟与RTOS的时钟并非同一个时钟,RTOS使用的时钟为SysTick,后续跟rtos相关的API都是用改时钟源,例vtaskdelay。它会在每次计数的时候判断此时 就绪队列中是否有delay为0的任务如果有则进行调度。

  • 单片机系统的时钟源可以自己设置,比如TIMx,跟单片机相关的API都用该时钟源,例如hal delay,里面就是用TIMx来计数从而实现延时。

最后实现的效果伪代码如下:

vtaskdelay的流程:
{
    装入延时的值进行任务调度(假设目前只有task1和task2,此次发起延时的任务是task1,则调用YIELD之后会进行task2)
}

运行task2过程中:
{
    systick不断计数,每次计数都会判断task1的delay是否到期,到期则重新切换回task1
}

在这个过程中使用的一直为RTOS的时钟源。

0x03 多优先级

在就绪列表pxReadyTasksLists[ configMAX_PRIORITIES ]是一个数组,数组里面存的是就绪任务的 TCB(准确来说是 TCB 里面的 xStateListItem 节点),数组的下标对应任务的优先级,优先级越低对应的数组下标越小。空闲任务的优先级最低,对应的是下标为 0 的链表。任务在创建的时候,会根据任务的优先级将任务插入到就绪列表不同的位置。相同优先级的任务插入到就绪列表里面的同一条链表中,这个时候就需要用到时间片。

pxCurrenTCB 是一个全局的 TCB 指针,用于指向优先级最高的就绪任务的 TCB,即当前正在运行的 TCB 。要做到多优先级只需要在任务切换的时候将指针指向最高优先级的就绪任务TCB即可。

如果找到最高优先级的就绪任务的 TCB,在FreeRTOS上具有两套方法,于task.c中定义,查找最高优先级的就绪任务有两种方法 , 具 体 由configUSE_PORT_OPTIMISED_TASK_SELECTION 这个宏控制,定义为 0 选择通用方法,定义为 1 选择根据处理器优化的方法,该宏默认在 portmacro.h 中定义为 1,即使用优化过的方法:

#ifndef configUSE_PORT_OPTIMISED_TASK_SELECTION
	#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
#endif

通用方法:

taskRECORD_READY_PRIORITY()

此函数用于更新uxTopReadyPriority的值。这个是一个在task.c定义的静态变量,用于创建任务的最高优先级,默认初始化为0,即空闲任务的优先级:

#define tskIDLE_PRIORITY			( ( UBaseType_t ) 0U )
PRIVILEGED_DATA static volatile UBaseType_t uxTopReadyPriority 		= tskIDLE_PRIORITY;

通用方法:

#define taskRECORD_READY_PRIORITY( uxPriority )														\
{																									\
    if( ( uxPriority ) > uxTopReadyPriority )														\
    {																								\
        uxTopReadyPriority = ( uxPriority );														\
    }																								\
} /* taskRECORD_READY_PRIORITY */

taskSELECT_HIGHEST_PRIORITY_TASK()

#define taskSELECT_HIGHEST_PRIORITY_TASK()															\
{																									\
	UBaseType_t uxTopPriority = uxTopReadyPriority;														\
                                                                                                    \
    /* 从最高优先级对应的就绪列表数组下标开始寻找当前链表下是否有任务存在,如果没有,则 uxTopPriority 减一操作,继续寻找下一个优先级对应的链表中是否有任务存在,如果有则跳出 while 循环,表示找到了最高优先级的就绪任务。之所以可以采用从最高优先级往下搜索,是因为任务的优先级与就绪列表的下标是一一对应的,优先级越高,对应的就绪列表数组的下标越大。 */								\
    while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )							\
    {																								\
        configASSERT( uxTopPriority );																\
        --uxTopPriority;																			\
    }																								\
                                                                                                    \
    /* 获取优先级最高的就绪任务的 TCB,然后更新到 pxCurrentTCB */									\
    listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );			\
    //更新uxTopReadyPriority
    uxTopReadyPriority = uxTopPriority;																\
} /* taskSELECT_HIGHEST_PRIORITY_TASK */

此函数用于查找优先级最高的就绪任务,更新uxTopReadyPrioritypxCurrentTCB的值。

优化方法:

Cortex-M 内核有一个计算前导零的指令CLZ,所谓前导零就是计算一个变量(Cortex-M 内核单片机的变量为 32位)从高位开始第一次出现 1 的位的前面的零的个数。

如果 uxTopReadyPriority 的每个位号对应的是任务的优先级,任务就绪时,则将对应的位置 1,反之则清零。那么可以利用前导零指令计算出当前就绪任务的最高优先级,将高优先级的放在高位。

taskRECORD_READY_PRIORITY()

taskRECORD_READY_PRIORITY()用于根据传进来的形参(通常形参就是任务的优先级)将变量 uxTopReadyPriority 的某个位置 1。与通用方法中用来表示创建的任务的最高优先级不一样,它在优化方法中担任的是一个优先级位图表的角色,即该变量的每个位对应任务的优先级,如果任务就绪,则将对应的位置 1,反之清零。

其主要实现是靠如下的宏:

#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )

#define taskRESET_READY_PRIORITY( uxPriority )														\
{																									\
    if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 )	\
    {																								\
        portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) );							\
    }																								\
}

taskRESET_READY_PRIORITY()

#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )

#define taskRESET_READY_PRIORITY( uxPriority )														\
{																									\
    if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 )	\
    {																								\
        portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) );							\
    }																								\
}

此函数是将变量uxTopReadyPriority 的某个位清零。实际上根据优先级调用 taskRESET_READY_PRIORITY()函数复位 uxTopReadyPriority 变量中对应的位时,要先确保就绪列表中对应该优先级下的链表没有任务才行,如果此时列表中具有延时任务,不能将一个非就绪任务从就绪列表中移除,所以只能通过优先级的变量在对应的位中进行清零。

taskSELECT_HIGHEST_PRIORITY_TASK()

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

#define taskSELECT_HIGHEST_PRIORITY_TASK()														\
{																								\
UBaseType_t uxTopPriority;																		\
                                                                                                \
    /* Find the highest priority list that contains ready tasks. */								\
    portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );								\
    configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );		\
    listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );		\
} /* 

根据 uxTopReadyPriority 的值,找到最高优先级,然后更新到uxTopPriority 这个局部变量中。之后根据 uxTopPriority 的值,从就绪列表中找到就绪的最高优先级的任务的 TCB,然后将 TCB 更新到 pxCurrentTCB。

prvAddTaskToReadyList()

根据优先级将位图uxTopReadyPriority中的对应位置位,并且根据优先级将任务插入就绪列表pxReadyTasksLists[]

#define prvAddTaskToReadyList( pxTCB )																\
	traceMOVED_TASK_TO_READY_STATE( pxTCB );														\
	taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );												\
	vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
	tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )

0x04 任务延时列表

工作原理:在FreeRTOS中,当任务需要延时的时候,先将任务进行挂起,即先将任务从就绪列表删除,然后插入到任务延时列表,同时更新下一个任务的解锁时刻变量:xNextTaskUnblockTime的值。

xNextTaskUnblockTime 的值等于系统时基计数器的值 xTickCount 加上任务需要延时的值 xTicksToDelay。当系统时基计数器xTickCount 的值与 xNextTaskUnblockTime 相等时,就表示有任务延时到期了,需要将该任务就绪。

任务延时列表表维护着一条双向链表,每个节点代表了正在延时的任务,节点按照延时时间大小做升序排列。当每次时基中断(SysTick 中断)来临时,就拿系统时基计数器的值 xTickCount 与下一个任务的解锁时刻变量 xNextTaskUnblockTime 的值相比较,如果相等,则表示有任务延时到期,需要将该任务就绪,否则只是单纯地更新系统时基计数器xTickCount 的值,然后进行任务切换。FreeRTOS定义了如下的延时列表:

PRIVILEGED_DATA static List_t xDelayedTaskList1 = {0};			/*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2 = {0};			/*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList = NULL;	/*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList = NULL;	/*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */

前面为两个任务延时列表,当系统时基计数器xTickCount没有溢出时,只用一条列表,当xTickCount溢出后,用另外一条列表。

后面为指向这两个列表的指针。

任务延时列表初始化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IweM4qv1-1682495949164)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230418113746933.png)]

使用函数vListInitialise进行初始化。

定义xNextTaskUnblockTime

PRIVILEGED_DATA static volatile TickType_t xNextTaskUnblockTime	= ( TickType_t ) 0U; /* Initialised to portMAX_DELAY before the scheduler starts. */

xNextTaskUnblockTime 是一个在 task.c 中定义的静态变量,用于表示下一个任务的解锁时刻。xNextTaskUnblockTime 的值等于系统时基计数器的值 xTickCount 加上任务需要延时值 xTicksToDelay。当系统时基计数器 xTickCount 的值与 xNextTaskUnblockTime 相等时,就表示有任务延时到期了,需要将该任务就绪。

初始化xNextTaskUnblockTime位于函数vTaskStartScheduler()中,初始化为portMAX_DELAY,位于portmacro.h

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ITaf3mO-1682495949165)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230418114253008.png)]

并且在延时函数vTaskDelay()中加入延时列表prvAddCurrentTaskToDelayedList

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qotkXcxa-1682495949165)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230418115318534.png)]

prvAddCurrentTaskToDelayedList()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MlQVUthU-1682495949165)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230418200238699.png)]

通过判断当前时间是否已经满足等待的时间,如果是则加入溢出队列,否则时间还没到,继续加入等待队列。只需要让系统时基计数器 xTickCount 与 xNextTaskUnblockTime 的值先比较就知道延时最快结束的任务是否到期。

xTaskIncrementTick()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mTJH5Ka4-1682495949166)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230418200938069.png)]

对于此函数,主要是在判断变量xConstTickCount是否溢出,若溢出则切换延时列表。如果延时任务列表为空,则设置xNextTaskUnblockTime为可能的最大值。如果不为空,则要知道将延时任务中所有延时到期的任务移除才能跳出for循环。若找到已经延时结束的任务,则消除等待状态,将解除等待的任务添加到就绪列表。

taskSWITCH_DELAYED_LISTS()

#define taskSWITCH_DELAYED_LISTS()																	\
{																									\
	List_t *pxTemp;																					\
																									\
	/* The delayed tasks list should be empty when the lists are switched. */						\
	configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) );										\
																									\
	pxTemp = pxDelayedTaskList;																		\
	pxDelayedTaskList = pxOverflowDelayedTaskList;													\
	pxOverflowDelayedTaskList = pxTemp;																\
	xNumOfOverflows++;																				\
	prvResetNextTaskUnblockTime();																	\
}

在这切换了延时列表,实际就是更换pxDelayedTaskListpxOverflowDelayedTaskList这两个指针的指向。之后复位xNextTaskUnblockTime的值。

static void prvResetNextTaskUnblockTime( void )
{
TCB_t *pxTCB;

	if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
	{
		/* 当前延时列表为空,则设置 xNextTaskUnblockTime 等于最大值 */
		xNextTaskUnblockTime = portMAX_DELAY;
	}
	else
	{
		/* 当前列表不为空,则有任务在延时,则获取当前列表下第一个节点的排序值然后将该节点的排序值更新到 xNextTaskUnblockTime */
		( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
		xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xStateListItem ) );
	}
}

taskRESET_READY_PRIORITY()

#define taskRESET_READY_PRIORITY( uxPriority )														\
{																									\
    if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 )	\
    {																								\
        portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) );							\
    }																								\
}

需要判断就绪列表 pxReadyTasksLists[]在当前优先级下对应的链表的节点是否为 0,只有当该链表下没有任务时才真正地将任务在优先级位图表 uxTopReadyPriority 中对应的位清零。

0x05 时间片

在同一个优先级下可以有多个任务,每个任务轮流地享有相同的 CPU 时间,享有 CPU 的时间叫时间片。

在 RTOS 中,最小的时间单位为一个 tick,即 SysTick 的中断周期,RT-Thread 和 μC/OS 可以指定时间片的大小为多个 tick,但是 FreeRTOS 不一样,时间片只能是一个 tick。

如何实现一个优先级下多个任务的时间片处理,主要由函数taskRESET_READY_PRIORITY()和taskSELECT_HIGHEST_PRIORITY_TASK()这两个函数的实现方法。

系统在任务系统切换时,总会从就绪列表中寻找优先级最高的任务来执行,寻找优先级最高的任务使用函数taskSELECT_HIGHEST_PRIORITY_TASK

#define taskSELECT_HIGHEST_PRIORITY_TASK()														\
{																								\
	UBaseType_t uxTopPriority;																		\
                                                                                                \
    /* 寻找就绪任务的最高优先级 */								\
    portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );								\
    configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );		\
    //  获取优先级最高的就绪任务的 TCB,然后更新到 pxCurrentTCB
    listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );		\
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */

如果当前优先级上具有任务1和任务2,那么如何进行选择和切换其任务,具体在函数listGET_OWNER_OF_NEXT_ENTRY

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
{																							\
	List_t * const pxConstList = ( pxList );													\
	/* 节点索引指向链表第一个节点调整节点索引指针,指向下一个节点,如果当前链表有 N 个节点,当第 N 次调用该函数时,pxIndex 则指向第 N 个节点*/				\						\
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
	//  当遍历完链表后,pxIndex 回指到根节点
     if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
	{																						\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}																						\
	// 获取节点的 OWNER,即 TCB
   	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											\
}

在这个函数中不断执行链表的遍历操作,每个任务都轮流执行,享有相同的CPU时间,即所谓的时间片。

其实 FreeRTOS 的这种时间片功能不能说是真正意义的时间片,因为它不能随意的设置时间为多少个 tick,而是默认一个 tick,然后默认在每个 tick 中断周期中进行任务切换而已。

0x06 总结

对于具有多个优先级的任务,他们是通过设置优先级进行抢占式运行,但是对于同个优先级下的任务,则是通过时间片分配来执行的。但如果你的同优先级的任务中加入了死循环,那么在这个优先级后的任务则无法执行,所以这个时候需要函数vTaskDelay()来将任务进行挂起,之后执行低优先级的任务,才可以顺序执行。

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

FreeRTOS学习笔记(二)——内核机制 的相关文章

  • python函数参数*args**kwargs用法实例

    http www jb51 net article 44104 htm python当函数的参数不确定时 xff0c 可以使用 args和 kwargs args没有key值 xff0c kwargs有key值 下面看例子 复制代码 代码如
  • 简易图解移轴镜头 (Tilt-Shift Lens) 原理 简易图解移轴镜头 (Tilt-Shift Lens) 原理

    http fotomen cn 2012 10 tilt shift lens 移轴镜 Tilt Shift Lens 是颇昂贵的玩意 xff0c 例如 Canon 的 TS E 24mm f 3 5L II xff0c 官方零售价是 HK
  • GTA5最新线上小助手

    https wwr lanzoui com ivR9Wsuixmb 密码 4ug1
  • DELL-R730服务器U盘安装操作系统指南

    一 系统安装注意事项 xff1a 1 DELL服务器安装系统 xff0c 根据实际情况先做raid5 xff0c 因为我们有3块硬盘 xff1b 2 安装系统前先把U盘做成启动盘 xff0c 然后下载相应的阵列卡驱动 xff0c 阵列卡驱动
  • VCS2018 linux 安装

    VCS linux 安装 自己去网上找2018版本的vcs 和verdi xff0c 就不贴出来了 xff0c 这里把安装过程中遇到的一些问题留作记录 声明 xff1a 只做学术研究 xff0c 不做商业用途 xff0c 公司使用推荐购买正
  • Ubuntu mate 20.04及无vnc的Ubuntu 系统开启vnc

    Ubuntu mate 20 04及无vnc的Ubuntu 系统开启vnc 目录 Ubuntu mate 20 04及无vnc的Ubuntu 系统开启vnc1 介绍2 步骤 1 介绍 2 步骤 1 介绍 我学习ros机器人的过程中 xff0
  • OpenCV入门(四)——边缘检测

    目录 0x01 梯度算子 0x02 一阶微分算子 0x03 二阶微分算子 0x04 图像差分运算 0x05 非极大值抑制 0x06 基本边缘算子 Sobel 0x07 基本边缘算子 Laplace 0x08 基本边缘检测算子 Roberts
  • python教程:9种元组常用操作方法

    基础知识 xff5e 总之多记多看就对了 一 元组定义 元组不可变 xff0c 当我们需要创建一组不可改变的数据时 xff0c 通常是将这些数据放进元组中 tu 61 1 2 3 39 a 39 39 b 39 39 c 39 小括号定义元
  • 3D打印Gcode命令指令简析

    G0 xff1a 快速移动 G1 xff1a 控制移动 坐标轴XYZE移动控制 xff08 G0和G1一样 xff09 例子 xff1a G0 F2000 X30 Y30 Z30 E3 G2 xff1a 顺时针画弧 G3 xff1a 逆时针
  • 数学建模(一)—— 人口增长模型的确定

    一 题目要求二 相关的基础知识2 1 马尔萨斯 Malthus 人口指数增长模型2 2 逻辑斯蒂 Logistic 增长模型2 3 改进的Logistic模型2 4 BP神经网络模型 三 模型的建立与求解3 1 Malthus模型3 2 L
  • 将火狐浏览器视频播放倍速设置为3倍速及其以上

    看视频最快只能两倍速 xff1f 使用火狐浏览器看视频时将视频播放倍速提升到最快16倍速的方法来了 xff01 xff01 xff01 step1 xff1a 打开火狐浏览器页面左上角的应用程序菜单 xff0c 点击扩展和主题 xff1b
  • 折半插入排序算法

    折半插入排序 xff08 Binary Insertion Sort xff09 是对插入排序算法的一种改进 所谓插入排序 xff0c 就是不断的依次将元素插入前面已排好序的序列中 由于前半部分为已排好序的数列 xff0c 这样我们不用按顺
  • VOC2007数据集详细分析

    VOC数据集官网链接 http host robots ox ac uk pascal VOC VOC2007数据集官网链接 http host robots ox ac uk pascal VOC voc2007 index html V
  • Requirement already satisfied解决办法

    遇到的问题 xff1a 当使用电脑中安装的Python 3 7的IDLE去运行某一个python文件时 xff0c 会出现ModuleNotFoundError No module named 39 numpy 39 的报错 xff0c 需
  • 使用Python将DOTA数据集的格式转换成VOC2007数据集的格式

    一 VOC2007数据集二 DOTA数据集三 将DOTA数据集的格式转换成VOC2007数据集的格式 一 VOC2007数据集 VOC2007数据集的文件结构如下图所示 其中 xff0c 文件夹Annotations中存放的是图像的标注信息
  • 设计一个卷积神经网络模型用于遥感图像的场景分类

    遥感图像场景分类是指对遥感图像中场景语义内容标签的映射过程 xff0c 对高分辨率遥感影像的信息提取及内容理解有着重要的意义 主要的场景分类方法可以分为三类 xff1a 第一类是基于底层视觉特征的场景分类方法 xff0c 第二类是基于中层视
  • 使用Python随机生成数据的一些方法

    通过Python Faker生成测试数据通过Python基础语法生成一些随机数利用26个字母和10个数字随机生成5个八位密码生成5个2位小数点的随机数生成5个随机整数数 通过Python Faker生成测试数据 通过Python Faker
  • 源码阅读及理论详解《 Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting 》

    Informer论文 xff1a https arxiv org pdf 2012 07436 pdf Informer源码 xff1a GitHub zhouhaoyi Informer2020 The GitHub repository
  • Python实现排序

    在Python中可以使用提供的sort排序法对list实现排序 Python提供两种内置排序的函数分别是sort 和sorted xff0c 这两种函数用法差别在于sort 会直接修改原始的list进行排序 xff0c sorted 可迭代
  • Python排序进阶版:根据一个列表的顺序对其他列表进行排序

    在Python中如果需要根据列表A对列表B进行排序的问题时有以下2种方法 xff1a 方法1 xff1a 根据列表B中每个元素的下标来获取列表A中对应位置的元素 xff0c 将其作为排序依据即可 a span class token ope

随机推荐

  • 基于MATLAB GUI的系统设计(四)

    接下来学习的是关于图像处理方面的知识 实例一 xff1a 对图像进行灰度处理 直方图均衡化处理 二值处理以及用Canny算子对图像进行边缘检测 第一步 xff1a GUIDE画界面 第二步 xff1a 编辑代码 function varar
  • HashMap的工作原理(图文+例子)详解,绝对简单通俗易懂

    目录 什么是HashMap xff1f HashMap的内部结构 内部结构之数组 内部结构之链表 Put方法与Get方法原理 JDK1 7月JDK1 8中HashMap的区别 什么是HashMap xff1f 基于哈希表的 Map 接口的实
  • vnc序列号

    5D7L8 ZQXSA 2L5D4 4UFB4 PWDLA 8KNFU MZZHE WA449 2SLLH 48Q7A
  • A星(A*、A Star)路径规划算法详解(附MATLAB代码)

    首先看看运行效果 xff0c 分别有三种模式 xff0c 代码运行前需要通过鼠标点击设置起点和终点 第一种模式直接输出最短路径 第二种模式输出最短路径的生成过程 第三种模式输出最短路径的生成过程和详细探索的过程 代码获取 gitee链接 x
  • ubuntu系统支持GIGABYTE X570 I AORUS PRO WIFI主板温度及风扇转速检测

    原文链接 xff1a How to fix bugs in Ubuntu 20 04 LTS after installation 43 all necessary programs ITCooky Recipes Not getting
  • 计算机启动过程(windows XP 系统)

    计算机启动过程 xff08 windows XP 系统 xff09 共五步 1 预引导 Pre Boot 阶段 xff1b 2 引导阶段 xff1b 3 加载内核阶段 xff1b 4 初始化内核阶段 xff1b 5 登陆 第一个阶段 首先
  • 2006年6月21日

    最近住了一段时间医院 xff0c 在那里除了休息就是休息 xff0c 回来都一周了 xff0c 我都没有适应了学校的生活 xff0c 一天除了睡觉就是看世界杯 哈哈 xff0c 得要有个好心态 看一点东西就算一点 xff0c 写出来 xff
  • 2006 年6月23日

    我为网上下载的一个TDI的源代码写了一个source文件 xff0c 可是编译时却出现了如下问题 can 39 t open include file 34 netpnp h 34 以及 can 39 t open input file 3
  • Linux搭建Web服务器(三)——服务器编程基本框架以及事件处理模式

    目录 0x01 服务器编程基本框架 0x02 两种高效的事件处理模式 Reactor 模式 Proactor 模式 模拟Proactor 模式 0x01 服务器编程基本框架 虽然服务器程序的种类繁多 xff0c 但是其基本框架都是一样的 x
  • 解决docker容器IP不固定问题

    创建了两个容器 xff0c 做好mysql的主从配置了 xff0c 重启docker容器之后 xff0c 发现容器的ip地址变了 xff0c 这就尴尬了 xff0c 首先了解到了docker默认采用 bridge 连接 xff0c 启动容器
  • VSCode 基本使用

    一 VSCode 界面功能介绍 1 Git History xff0c 相当于 git log 3 打开上一提交版本的更改记录 4 显示提交注释 5 向右分屏 6 直接定位到第几行 7 解析当前文件的语言 8 切换项目 xff08 基于 P
  • keras中实现简单的反卷积

    我这里将反卷积分为两个操作 xff0c 一个是UpSampling2D xff0c 用上采样将原始图片扩大 xff0c 然后用Conv2D 这个函数进行卷积操作 xff0c 就可以完成简单的反卷积 xff1a UpSampling2D xf
  • STM32学习笔记:蜂鸣器实验

    STM32学习笔记 xff1a 蜂鸣器实验 蜂鸣器实现原理和led点亮大同小异 xff0c 所以就不重新编写了 一 所使用的函数 span class token number 1 span 时钟使能函数 span class token
  • centos6/7通用查看系统版本

    方法一 可以用lsb release a来查看系统版本 root 64 centos6 lsb release a LSB Version base 4 0 amd64 base 4 0 noarch core 4 0 amd64 core
  • 欢迎使用CSDN-markdown编辑器

    ADB操作命令及其详解 adb是什么 xff1a adb的全称为Android Debug Bridge xff0c 就是起到调试桥的作用 通过adb我们可以在Eclipse中方面通过DDMS来调试android程序 xff0c 说白了就是
  • 私有云上创建与配置虚拟机

    目录 一 登录私有云 二 创建网络 新建路由等 xff08 一 xff09 创建网络 hy 二 新建路由 xff08 三 xff09 添加接口 xff08 四 xff09 查看网络拓扑 xff08 五 xff09 添加端口 三 创建实例 x
  • 远程桌面打开mayavi,基于VirtualGL + Turbovnc

    实现内容 xff1a 远程桌面打开mayavi xff0c 基于VirtualGL 43 Turbovnc 参考链接 xff1a 链接1 下面是复制过来的内容 xff0c 附加上我的注释 xff1a Setup VirtualGL and
  • twisted笔记三:當Deferred遇上Thread(转载)

    Deferred不會自動實現將阻塞過程轉為非阻塞過程 xff0c 雖然它已經有那樣的機制但還是要你去多走一步 要將阻塞過程轉為真正的非阻塞過程 xff0c 那麼只有借用線程 但至於線程調用你不用太擔心 xff0c twisted已為你準備好
  • pyspider安装与初次使用的那些坑

    pyspider是一个python的爬虫框架 xff0c 安装过程遇到不少坑 xff0c 在这里总结一下 安装 安装只需要简单用pip安装就可以了 pip install pyspider 测试 使用 pyspider 或 pyspider
  • FreeRTOS学习笔记(二)——内核机制

    文章目录 0x01 临界段Cortex M内核快速关中断指令关中断开中断进入和退出临界段的宏进入临界段退出临界段临界段代码应用 0x02 空闲任务与阻塞延时的实现空闲任务的创建实现阻塞延时阻塞等待总结 0x03 多优先级通用方法 xff1a