04.FreeRTOS任务创建

news2024/9/19 10:51:10

04. FreeRTOS任务创建与任务删除

1. FreeRTOS创建和删除任务相关API函数

函数描述
xTaskCreate()动态方式创建任务
xTaskCreateStatic()静态方式创建任务
xTaskCreateRestricted()动态方式创建使用 MPU 限制的任务
xTaskCreateRestrictedStatic()静态方式创建使用 MPU 限制的任务
vTaskDelete()删除任务

在这里插入图片描述

  1. 函数xTaskCreate()

    动态方式创建函数,任务的任务控制块及栈空间所需的内存,均由FreeRTOS从FreeRTOS管理的堆中分配,若使用此函数,需要在FreeRTOSConfig.h文件中将宏configSUPPORT_DYNAMIC_ALLOCATION配置为1。此函数创建的任务会立刻进入就绪态,由任务调度器运行。函数原型如下所示:

    在这里插入图片描述
    在这里插入图片描述

  2. 函数xTaskCreateStatic()

    静态方式创建任务,任务的任务控制块及任务的栈空间所需的内存,需要由用户分配提供,若使用此函数,需要在FreeRTOSConfig.h文件中将宏 configSUPPORT_STATIC_ALLOCATION配置为1。此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。函数原型如下所示:

    在这里插入图片描述
    在这里插入图片描述

  3. 函数vTaskDelete()

    此函数用于删除已被创建的任务,被删除的任务将被从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除,要注意的是,空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露。若使用此函数,需要在FreeRTOSConfig.h文件中将宏INCLUDE_vTaskDelete配置为1。函数原型如下所示:

    在这里插入图片描述

2. 动态创建任务及具体实现

  • 步骤:

    1. 将宏configSUPPORT_DYNAMIC_ALLOCATION 配置为 1
    2. 定义函数入口参数
    3. 编写任务函数
  • 动态创建任务函数内部实现:

    1. 申请堆栈内存和任务控制块内存
    2. TCB结构体成员赋值
    3. 添加新任务到就绪列表中
  • 任务控制块:

    在这里插入图片描述

  • 具体代码实现:

    本实验共创建6个任务,1个开始任务和5个不同功能的任务函数

    任务优先级、任务堆栈大小、任务句柄:

    #define START_TASK_PRIO       1
    #define START_TASK_STACK_SIZE 128
    TaskHandle_t start_task_handler;
    
    #define TASK1_TASK_PRIO       2
    #define TASK1_TASK_STACK_SIZE 128
    TaskHandle_t task1_task_handler;
    
    #define TASK2_TASK_PRIO       3
    #define TASK2_TASK_STACK_SIZE 128
    TaskHandle_t task2_task_handler;
    
    #define TASK3_TASK_PRIO       4
    #define TASK3_TASK_STACK_SIZE 128
    TaskHandle_t task3_task_handler;
    
    #define TASK4_TASK_PRIO       5
    #define TASK4_TASK_STACK_SIZE 128
    TaskHandle_t task4_task_handler;
    
    #define TASK5_TASK_PRIO       6
    #define TASK5_TASK_STACK_SIZE 128
    TaskHandle_t task5_task_handler;
    
    //LCD刷屏时使用的颜色
    uint16_t lcd_discolor[11] = {WHITE, BLACK, BLUE, RED, MAGENTA, GREEN, CYAN, YELLOW,BROWN, BRRED, GRAY};
    

    主函数入口:

    void freertos_Dynamic_Create(void)
    {
    	lcd_show_string(10, 10, 220, 32, 32, "STM32", RED);
        lcd_show_string(10, 47, 220, 24, 24, "Task Create&Delete", LIGHTGREEN);
        
        lcd_draw_rectangle(5, 110, 115, 314, BLACK);
        lcd_draw_rectangle(125, 110, 234, 314, BLACK);
        lcd_draw_line(5, 130, 115, 130, BLACK);
        lcd_draw_line(125, 130, 234, 130, BLACK);
    	lcd_show_string(15, 80, 110, 16, 16, "Task1: 000", GREEN);
    	lcd_show_string(135, 80, 110, 16, 16, "Task2: 000", GREEN);
        lcd_show_string(15, 111, 110, 16, 16, "Task4: 000", BLUE);
        lcd_show_string(135, 111, 110, 16, 16, "Task5: 000", BLUE);
    	
    	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();  //开启任务调度
    }
    

    开始任务:

    void start_task(void* pvParamter)
    {
    	taskENTER_CRITICAL();   // 进入临界区 
    	
    	xTaskCreate((TaskFunction_t        )   task1,                 //指向任务函数的指针
    				(char *                )   "task1",               //任务名称
    				(configSTACK_DEPTH_TYPE)   TASK1_TASK_STACK_SIZE, //任务堆栈大小,字节为单位
    				(void *                )   NULL,                  //传递给任务函数的参数
    				(UBaseType_t           )   TASK1_TASK_PRIO,       //任务优先级
    				(TaskHandle_t *        )   &task1_task_handler    //任务句柄:任务控制块
    	);
    				
    	xTaskCreate((TaskFunction_t        )   task2,                 //指向任务函数的指针
    				(char *                )   "task2",               //任务名称
    				(configSTACK_DEPTH_TYPE)   TASK2_TASK_STACK_SIZE, //任务堆栈大小,字节为单位
    				(void *                )   NULL,                  //传递给任务函数的参数
    				(UBaseType_t           )   TASK2_TASK_PRIO,       //任务优先级
    				(TaskHandle_t *        )   &task2_task_handler    //任务句柄:任务控制块
    	);	
    
    	xTaskCreate((TaskFunction_t        )   task3,                  //指向任务函数的指针
    				(char *                )   "task3",                //任务名称
    				(configSTACK_DEPTH_TYPE)   TASK3_TASK_STACK_SIZE,  //任务堆栈大小,字节为单位
    				(void *                )   NULL,                   //传递给任务函数的参数
    				(UBaseType_t           )   TASK3_TASK_PRIO,        //任务优先级
    				(TaskHandle_t *        )   &task3_task_handler     //任务句柄:任务控制块
    	);
    				
    	xTaskCreate((TaskFunction_t        )   task4,                  //指向任务函数的指针
    				(char *                )   "task4",                //任务名称
    				(configSTACK_DEPTH_TYPE)   TASK4_TASK_STACK_SIZE,  //任务堆栈大小,字节为单位
    				(void *                )   NULL,                   //传递给任务函数的参数
    				(UBaseType_t           )   TASK4_TASK_PRIO,        //任务优先级
    				(TaskHandle_t *        )   &task4_task_handler     //任务句柄:任务控制块
    	);
    
    	xTaskCreate((TaskFunction_t        )   task5,                  //指向任务函数的指针
    				(char *                )   "task5",                //任务名称
    				(configSTACK_DEPTH_TYPE)   TASK5_TASK_STACK_SIZE,  //任务堆栈大小,字节为单位
    				(void *                )   NULL,                   //传递给任务函数的参数
    				(UBaseType_t           )   TASK5_TASK_PRIO,        //任务优先级
    				(TaskHandle_t *        )   &task5_task_handler     //任务句柄:任务控制块				
    	);	
    
    	vTaskDelete(NULL);
    				
    	taskEXIT_CRITICAL();    // 退出临界区 
    }
    

    任务一:实现LED0每500ms翻转一次

    void task1(void* pvParamter)
    {
    	uint32_t task1_num = 0;
    	
    	while(1)
    	{
    		printf("task1正在运行!!!\r\n");
    		lcd_show_xnum(71, 80, ++task1_num, 3, 16, 0x80, GREEN);
    		LED0_TOGGLE();
    		vTaskDelay(500);
    	}
    }
    

    任务二:实现LED1每500ms翻转一次

    void task2(void* pvParamter)
    {
    	uint32_t task2_num = 0;
    	
    	while(1)
    	{
    		printf("task2正在运行!!!\r\n");
    		lcd_show_xnum(191, 80, ++task2_num, 3, 16, 0x80, GREEN);
    		LED1_TOGGLE();
    		vTaskDelay(500);
    	}
    }
    

    任务三:删除任务一

    void task3(void* pvParamter)
    {
    	uint8_t key = 0;
    	while(1)
    	{
    		printf("task3正在运行!!!\r\n");
    		key = key_scan(0);
    		
    		if(key == KEY0)
    		{
    			if(task1_task_handler != NULL)
    			{
    				printf("删除task1任务!!!\r\n");
    				vTaskDelete(task1_task_handler);
    				task1_task_handler = NULL;
    			}			
    		}
    			
    		vTaskDelay(10);
    	}
    }
    

    任务四:不同颜色填充块一

    void task4(void *pvParameter)
    {
        uint32_t task4_num = 0;
        
        while(1)
        {
            lcd_fill(6, 131, 114, 313, lcd_discolor[++task4_num % 11]);
            lcd_show_xnum(71, 111, task4_num, 3, 16, 0x80, BLUE);
            
            vTaskDelay(500);
        }
    }
    

    任务五:不同颜色填充块二

    void task5(void *pvParameter)
    {
        uint32_t task5_num = 0;
        
        while(1)
        {
            lcd_fill(126, 131, 233, 313, lcd_discolor[11 - (++task5_num % 11)]);
            lcd_show_xnum(191, 111, task5_num, 3, 16, 0x80, BLUE);
            
            vTaskDelay(500);
        }
    }
    
  • 函数xTaskCreate()的内部实现(源码):

    在这里插入图片描述

    步骤:

    1. 申请堆栈内存(返回首地址)

      pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
      
    2. 申请任务控制块内存(返回首地址)

      pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); 
      
    3. 把前面申请的堆栈地址 赋值给控制块的堆栈成员

      pxNewTCB->pxStack = pxStack;
      
    4. 调用prvInitialiseNewTask初始化任务控制块中的成员

      prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
      
    5. 调用prvAddNewTaskToReadyList添加新创建的任务到就绪列表

      prvAddNewTaskToReadyList( pxNewTCB );
      

      具体源码:

          BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                                  const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                                  const configSTACK_DEPTH_TYPE usStackDepth,
                                  void * const pvParameters,
                                  UBaseType_t uxPriority,
                                  TaskHandle_t * const pxCreatedTask )
          {
              TCB_t * pxNewTCB;
              BaseType_t xReturn;
      
              /* If the stack grows down then allocate the stack then the TCB so the stack
               * does not grow into the TCB.  Likewise if the stack grows up then allocate
               * the TCB then the stack.
      			如果堆栈向下扩展,则先分配堆栈然后分配TCB,这样堆栈*不会扩展到TCB。
      		    同样,如果堆栈向上扩展,则再分配TCB然后堆栈*/
              #if ( portSTACK_GROWTH > 0 )
                  {
                      /* Allocate space for the TCB.  Where the memory comes from depends on
                       * the implementation of the port malloc function and whether or not static
                       * allocation is being used. */
                      pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
      
                      if( pxNewTCB != NULL )
                      {
                          /* Allocate space for the stack used by the task being created.
                           * The base of the stack memory stored in the TCB so the task can
                           * be deleted later if required. */
                          pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
      
                          if( pxNewTCB->pxStack == NULL )
                          {
                              /* Could not allocate the stack.  Delete the allocated TCB. */
                              vPortFree( pxNewTCB );
                              pxNewTCB = NULL;
                          }
                      }
                  }
              #else /* portSTACK_GROWTH */
                  {
                      StackType_t * pxStack;
      
                      /* Allocate space for the stack used by the task being created.
      				   为正在创建的任务所使用的堆栈分配空间*/
      				//1.申请堆栈内存(返回首地址)
                      pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */
      
                      if( pxStack != NULL )
                      {
                          /* Allocate space for the TCB. */
      					//2.申请任务控制块内存(返回首地址)
                          pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */
      
                          if( pxNewTCB != NULL )
                          {
                              /* Store the stack location in the TCB. */
      						//3.把前面申请的堆栈地址 赋值给控制块的堆栈成员
                              pxNewTCB->pxStack = pxStack;
                          }
                          else
                          {
                              /* The stack cannot be used as the TCB was not created.  Free
                               * it again. */
                              vPortFreeStack( pxStack );
                          }
                      }
                      else
                      {
                          pxNewTCB = NULL;
                      }
                  }
              #endif /* portSTACK_GROWTH */
      
              if( pxNewTCB != NULL )
              {
                  #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
                      {
                          /* Tasks can be created statically or dynamically, so note this
                           * task was created dynamically in case it is later deleted. */
                          pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
                      }
                  #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */
      			
      			//4.调用prvInitialiseNewTask初始化任务控制块中的成员
                  prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
                  //5.调用prvAddNewTaskToReadyList添加新创建的任务到就绪列表
      			prvAddNewTaskToReadyList( pxNewTCB );
                  xReturn = pdPASS;
              }
              else
              {
                  xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
              }
      
              return xReturn;
          }
      

      上述第4步调用prvInitialiseNewTask初始化任务控制块中的成员的步骤:

      1. 用已知值填充堆栈以协助调试,初始化堆栈0xa5

        ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
        
      2. 记录栈顶,保存在pxTopOfStack,并8字节向下对齐

        pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
        pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
        
      3. 保存任务名字到pxNewTCB->pcTaskName[x]

        for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
        {
        	pxNewTCB->pcTaskName[ x ] = pcName[ x ];
        
        	if( pcName[ x ] == ( char ) 0x00 )
        	{
        		break;
        	}
        	else
        	{
        		mtCOVERAGE_TEST_MARKER();
        	}
        }
        
      4. 保存任务优先级到pxNewTCB->uxPriority中

        pxNewTCB->uxPriority = uxPriority;
        
      5. 设置状态列表项的所属控制块,设置事件列表项的值

        vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
        vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
        
      6. 列表项的插入是从小到大插入,所以这里将越高优先级的任务他的事件列表项值设置越小,这样就可以拍到前面

        listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
        
      7. 调用pxPortInitialiseStack:初始化任务堆栈,用于保存当前任务上下文寄存器信息已备后续任务切换使用

        pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
        
      8. 将任务句柄等于任务控制块

        *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
        

      具体源码:

      static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
                                        const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                                        const uint32_t ulStackDepth,
                                        void * const pvParameters,
                                        UBaseType_t uxPriority,
                                        TaskHandle_t * const pxCreatedTask,
                                        TCB_t * pxNewTCB,
                                        const MemoryRegion_t * const xRegions )
      {
          StackType_t * pxTopOfStack;
          UBaseType_t x;
      
          #if ( portUSING_MPU_WRAPPERS == 1 )
              /* Should the task be created in privileged mode? */
              BaseType_t xRunPrivileged;
      
              if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
              {
                  xRunPrivileged = pdTRUE;
              }
              else
              {
                  xRunPrivileged = pdFALSE;
              }
              uxPriority &= ~portPRIVILEGE_BIT;
          #endif /* portUSING_MPU_WRAPPERS == 1 */
      
          /* Avoid dependency on memset() if it is not required. */
          #if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
              {
                  /* Fill the stack with a known value to assist debugging. */
      			//1.用已知值填充堆栈以协助调试,初始化堆栈0xa5
                  ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
              }
          #endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */
      
          /* Calculate the top of stack address.  This depends on whether the stack
           * grows from high memory to low (as per the 80x86) or vice versa.
           * portSTACK_GROWTH is used to make the result positive or negative as required
           * by the port. */
          #if ( portSTACK_GROWTH < 0 )
              {
      			//2.记录栈顶,保存在pxTopOfStack
                  pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
      			//8字节向下对齐
                  pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 !e9033 !e9078 MISRA exception.  Avoiding casts between pointers and integers is not practical.  Size differences accounted for using portPOINTER_SIZE_TYPE type.  Checked by assert(). */
      
                  /* Check the alignment of the calculated top of stack is correct. */
                  configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
      
                  #if ( configRECORD_STACK_HIGH_ADDRESS == 1 )
                      {
                          /* Also record the stack's high address, which may assist
                           * debugging. */
                          pxNewTCB->pxEndOfStack = pxTopOfStack;
                      }
                  #endif /* configRECORD_STACK_HIGH_ADDRESS */
              }
          #else /* portSTACK_GROWTH */
              {
                  pxTopOfStack = pxNewTCB->pxStack;
      
                  /* Check the alignment of the stack buffer is correct. */
                  configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
      
                  /* The other extreme of the stack space is required if stack checking is
                   * performed. */
                  pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
              }
          #endif /* portSTACK_GROWTH */
      
          /* Store the task name in the TCB. */
          if( pcName != NULL )
          {
      		//3.保存任务名字到pxNewTCB->pcTaskName[x]
              for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
              {
                  pxNewTCB->pcTaskName[ x ] = pcName[ x ];
      
                  /* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than
                   * configMAX_TASK_NAME_LEN characters just in case the memory after the
                   * string is not accessible (extremely unlikely). */
                  if( pcName[ x ] == ( char ) 0x00 )
                  {
                      break;
                  }
                  else
                  {
                      mtCOVERAGE_TEST_MARKER();
                  }
              }
      
              /* Ensure the name string is terminated in the case that the string length
               * was greater or equal to configMAX_TASK_NAME_LEN. */
      		//末尾加结束符
              pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
          }
          else
          {
              /* The task has not been given a name, so just ensure there is a NULL
               * terminator when it is read out. */
              pxNewTCB->pcTaskName[ 0 ] = 0x00;
          }
      
          /* This is used as an array index so must ensure it's not too large. */
          configASSERT( uxPriority < configMAX_PRIORITIES );
      
          if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
          {
              uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
          }
          else
          {
              mtCOVERAGE_TEST_MARKER();
          }
      	//4.保存任务优先级到pxNewTCB->uxPriority中
          pxNewTCB->uxPriority = uxPriority;
          #if ( configUSE_MUTEXES == 1 )
              {
                  pxNewTCB->uxBasePriority = uxPriority;
                  pxNewTCB->uxMutexesHeld = 0;
              }
          #endif /* configUSE_MUTEXES */
      	
      	//5.设置状态列表项的所属控制块,设置事件列表项的值
          vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
          vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
      
          /* Set the pxNewTCB as a link back from the ListItem_t.  This is so we can get
           * back to  the containing TCB from a generic item in a list. */
      	//6.列表项的插入是从小到大插入,所以这里将越高优先级的任务他的事件列表项值设置越小,这样就可以拍到前面
          listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
      
          /* Event lists are always in priority order. */
          listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
          listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
      
          #if ( portCRITICAL_NESTING_IN_TCB == 1 )
              {
                  pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
              }
          #endif /* portCRITICAL_NESTING_IN_TCB */
      
          #if ( configUSE_APPLICATION_TASK_TAG == 1 )
              {
                  pxNewTCB->pxTaskTag = NULL;
              }
          #endif /* configUSE_APPLICATION_TASK_TAG */
      
          #if ( configGENERATE_RUN_TIME_STATS == 1 )
              {
                  pxNewTCB->ulRunTimeCounter = ( configRUN_TIME_COUNTER_TYPE ) 0;
              }
          #endif /* configGENERATE_RUN_TIME_STATS */
      
          #if ( portUSING_MPU_WRAPPERS == 1 )
              {
                  vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
              }
          #else
              {
                  /* Avoid compiler warning about unreferenced parameter. */
                  ( void ) xRegions;
              }
          #endif
      
          #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
              {
                  memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );
              }
          #endif
      
          #if ( configUSE_TASK_NOTIFICATIONS == 1 )
              {
                  memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );
                  memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );
              }
          #endif
      
          #if ( configUSE_NEWLIB_REENTRANT == 1 )
              {
                  /* Initialise this task's Newlib reent structure.
                   * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
                   * for additional information. */
                  _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
              }
          #endif
      
          #if ( INCLUDE_xTaskAbortDelay == 1 )
              {
                  pxNewTCB->ucDelayAborted = pdFALSE;
              }
          #endif
      
          /* Initialize the TCB stack to look as if the task was already running,
           * but had been interrupted by the scheduler.  The return address is set
           * to the start of the task function. Once the stack has been initialised
           * the top of stack variable is updated. */
          #if ( portUSING_MPU_WRAPPERS == 1 )
              {
                  /* If the port has capability to detect stack overflow,
                   * pass the stack end address to the stack initialization
                   * function as well. */
                  #if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
                      {
                          #if ( portSTACK_GROWTH < 0 )
                              {
                                  pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );
                              }
                          #else /* portSTACK_GROWTH */
                              {
                                  pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );
                              }
                          #endif /* portSTACK_GROWTH */
                      }
                  #else /* portHAS_STACK_OVERFLOW_CHECKING */
                      {
                          pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
                      }
                  #endif /* portHAS_STACK_OVERFLOW_CHECKING */
              }
          #else /* portUSING_MPU_WRAPPERS */
              {
                  /* If the port has capability to detect stack overflow,
                   * pass the stack end address to the stack initialization
                   * function as well. */
                  #if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
                      {
                          #if ( portSTACK_GROWTH < 0 )
                              {
                                  pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
                              }
                          #else /* portSTACK_GROWTH */
                              {
                                  pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
                              }
                          #endif /* portSTACK_GROWTH */
                      }
                  #else /* portHAS_STACK_OVERFLOW_CHECKING */
                      {
      					//7.调用pxPortInitialiseStack:初始化任务堆栈,用于保存当前任务上下文寄存器信息已备后续任务切换使用
                          pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
                      }
                  #endif /* portHAS_STACK_OVERFLOW_CHECKING */
              }
          #endif /* portUSING_MPU_WRAPPERS */
      
          if( pxCreatedTask != NULL )
          {
              /* Pass the handle out in an anonymous way.  The handle can be used to
               * change the created task's priority, delete the created task, etc.*/
      		//8.将任务句柄等于任务控制块
              *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
          }
          else
          {
              mtCOVERAGE_TEST_MARKER();
          }
      }
      

      上述第5步调用prvAddNewTaskToReadyList添加新创建的任务到就绪列表的步骤:

      1. 记录任务数量uxCurrentNumberOfTasks++

        uxCurrentNumberOfTasks++;
        
      2. 判断新创建的任务是否为第一个任务

        //如果创建的是第一个任务,初始化任务列表prvInitialiseTaskLists
        if( pxCurrentTCB == NULL )
        {
             pxCurrentTCB = pxNewTCB;
        
             if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
             { 
                  prvInitialiseTaskLists();
             }
             else
             {
                  mtCOVERAGE_TEST_MARKER();
             }
        }
        //如果创建的不是第一个任务,并且调度器还未开始启动,比较新任务与正在执行的任务
        //优先级大小,新任务优先级大的话,将当前控制块重新指向新的控制块
        else
        {
              if( xSchedulerRunning == pdFALSE )
              {
                   if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
                   {
                       pxCurrentTCB = pxNewTCB;
                   }
                   else
                   {
                       mtCOVERAGE_TEST_MARKER();
                   }
               }
               else
               {
                    mtCOVERAGE_TEST_MARKER();
               }
        }
        
      3. 将新的任务控制块添加到就绪列表中,使用函数prvAddTaskToReadyList。将uxTopReadyPriority相应bit置一,表示相应优先级有就绪任务,比如任务优先级为5,就将该变量的位5置一,方便后续任务切换判断,对应的就绪列表是否有任务存在。

        prvAddTaskToReadyList( pxNewTCB );
        
      4. 如果调度器已经开始运行,并且新任务的优先级更大的话,进行一次任务切换

        if( xSchedulerRunning != pdFALSE )
        {
            if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
            {
                taskYIELD_IF_USING_PREEMPTION();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
        

      具体源码:

      static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
      {
          /* Ensure interrupts don't access the task lists while the lists are being
           * updated. */
          taskENTER_CRITICAL();
          {
      		//1.记录任务数量uxCurrentNumberOfTasks++
              uxCurrentNumberOfTasks++;
      		
      		//2.判断新创建的任务是否为第一个任务
      		//如果创建的是第一个任务,初始化任务列表prvInitialiseTaskLists
              if( pxCurrentTCB == NULL )
              {
                  /* There are no other tasks, or all the other tasks are in
                   * the suspended state - make this the current task. */
                  pxCurrentTCB = pxNewTCB;
      
                  if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
                  {
                      /* This is the first task to be created so do the preliminary
                       * initialisation required.  We will not recover if this call
                       * fails, but we will report the failure. */
                      prvInitialiseTaskLists();
                  }
                  else
                  {
                      mtCOVERAGE_TEST_MARKER();
                  }
              }
      		//如果创建的不是第一个任务,并且调度器还未开始启动,比较新任务与正在执行的任务
      		//优先级大小,新任务优先级大的话,将当前控制块重新指向新的控制块
              else
              {
                  /* If the scheduler is not already running, make this task the
                   * current task if it is the highest priority task to be created
                   * so far. */
                  if( xSchedulerRunning == pdFALSE )
                  {
                      if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
                      {
                          pxCurrentTCB = pxNewTCB;
                      }
                      else
                      {
                          mtCOVERAGE_TEST_MARKER();
                      }
                  }
                  else
                  {
                      mtCOVERAGE_TEST_MARKER();
                  }
              }
      
              uxTaskNumber++;
      
              #if ( configUSE_TRACE_FACILITY == 1 )
                  {
                      /* Add a counter into the TCB for tracing only. */
                      pxNewTCB->uxTCBNumber = uxTaskNumber;
                  }
              #endif /* configUSE_TRACE_FACILITY */
              traceTASK_CREATE( pxNewTCB );
      		
      		//3.将新的任务控制块添加到就绪列表中,使用函数prvAddTaskToReadyList
      		//将uxTopReadyPriority相应bit置一,表示相应优先级有就绪任务,比如任务优先级为5,就将该
      		//变量的位5置一,方便后续任务切换判断,对应的就绪列表是否有任务存在
              prvAddTaskToReadyList( pxNewTCB );
      
              portSETUP_TCB( pxNewTCB );
          }
          taskEXIT_CRITICAL();
      	
      	//4.如果调度器已经开始运行,并且新任务的优先级更大的话,进行一次任务切换
          if( xSchedulerRunning != pdFALSE )
          {
              /* If the created task is of a higher priority than the current task
               * then it should run now. */
              if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
              {
                  taskYIELD_IF_USING_PREEMPTION();
              }
              else
              {
                  mtCOVERAGE_TEST_MARKER();
              }
          }
          else
          {
              mtCOVERAGE_TEST_MARKER();
          }
      }
      
  • 实验结果:
    一共有五个任务,任务点亮LED0;任务二点亮LED;任务三删除任务1;任务四填充块一;任务五填充块二。从实验结果可以看出,LED0和LED1同时闪烁,间隔500ms;两个填充块已填充不同的颜色;当按键KEY0按下时,任务1被删除,LED0停止闪烁。

3. 静态创建任务及具体实现

  • 步骤:

    1. 需将宏configSUPPORT_STATIC_ALLOCATION 配置为 1

      // 1: 支持静态申请内存, 默认: 0 
      #define configSUPPORT_STATIC_ALLOCATION                 1                       
      
    2. 定义空闲任务和定时器任务的任务堆栈及TCB

      //空闲任务配置
      StaticTask_t idle_task_tcb;
      StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];
      
      //软件定时器任务配置
      StaticTask_t timer_task_tcb;
      StackType_t timer_task_stack[configTIMER_TASK_STACK_DPTH];
      
    3. 实现两个接口函数

      /*空闲任务内存分配*/
      void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
      									StackType_t ** ppxIdleTaskStackBuffer,
      									uint32_t * pulIdleTaskStackSize )
      {
      	* ppxIdleTaskTCBBuffer = &idle_task_tcb;
      	* ppxIdleTaskStackBuffer = idle_task_stack;
      	* pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
      }
      
      /*软件定时器内存分配*/
      void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
      									 StackType_t ** ppxTimerTaskStackBuffer,
      									 uint32_t * pulTimerTaskStackSize )
      {
      	* ppxTimerTaskTCBBuffer = &timer_task_tcb;
      	* ppxTimerTaskStackBuffer = timer_task_stack;
      	* pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
      }
      
    4. 定义函数入口参数

      freertos_Static_Create();
      
    5. 编写任务函数

      #define START_TASK_PRIO       1
      #define START_TASK_STACK_SIZE 128
      TaskHandle_t start_task_handler;
      StackType_t start_task_stack[START_TASK_STACK_SIZE];
      StaticTask_t start_task_tcb;
      
      #define TASK1_TASK_PRIO       2
      #define TASK1_TASK_STACK_SIZE 128
      TaskHandle_t task1_task_handler;
      StackType_t  task1_task_stack[TASK1_TASK_STACK_SIZE];
      StaticTask_t task1_task_tcb;
      
      #define TASK2_TASK_PRIO       3
      #define TASK2_TASK_STACK_SIZE 128
      TaskHandle_t task2_task_handler;
      StackType_t  task2_task_stack[TASK2_TASK_STACK_SIZE];
      StaticTask_t task2_task_tcb;
      
      #define TASK3_TASK_PRIO       4
      #define TASK3_TASK_STACK_SIZE 128
      TaskHandle_t task3_task_handler;
      StackType_t  task3_task_stack[TASK3_TASK_STACK_SIZE];
      StaticTask_t task3_task_tcb;
      
      
      void start_task(void * pvParamter)
      {
      	taskENTER_CRITICAL();
      	
          task1_task_handler = xTaskCreateStatic(
                          (TaskFunction_t)  task1,       
                          (char *        )  "task1",         
                          (uint32_t      )   TASK1_TASK_STACK_SIZE,      
                          ( void *       )   NULL,  
                          (UBaseType_t   )   TASK1_TASK_PRIO,
                          (StackType_t * )   task1_task_stack,          
                          (StaticTask_t *)   &task1_task_tcb); 
      					
      	task2_task_handler = xTaskCreateStatic(
                          (TaskFunction_t)  task2,       
                          (char *        )  "task2",         
                          (uint32_t      )   TASK2_TASK_STACK_SIZE,      
                          ( void *       )   NULL,  
                          (UBaseType_t   )   TASK2_TASK_PRIO,
                          (StackType_t * )   task2_task_stack,          
                          (StaticTask_t *)   &task2_task_tcb);  	
      
      	task3_task_handler = xTaskCreateStatic(
                          (TaskFunction_t)  task3,       
                          (char *        )  "task3",         
                          (uint32_t      )   TASK3_TASK_STACK_SIZE,      
                          ( void *       )   NULL,  
                          (UBaseType_t   )   TASK3_TASK_PRIO,
                          (StackType_t * )   task3_task_stack,          
                          (StaticTask_t *)   &task3_task_tcb);  
      
      	vTaskDelete(start_task_handler);
      	taskEXIT_CRITICAL();
      }
      
      /*任务一:实现LED0每500ms翻转一次*/
      void task1(void* pvParamter)
      {
      	uint32_t task1_num = 0;
      	
      	while(1)
      	{
      		printf("task1正在运行!!!\r\n");
      		lcd_show_xnum(71, 80, ++task1_num, 3, 16, 0x80, GREEN);
      		LED0_TOGGLE();
      		vTaskDelay(500);
      	}
      }
      
      /*任务二:实现LED1每500ms翻转一次*/
      void task2(void* pvParamter)
      {
      	uint32_t task2_num = 0;
      	
      	while(1)
      	{
      		printf("task2正在运行!!!\r\n");
      		lcd_show_xnum(191, 80, ++task2_num, 3, 16, 0x80, GREEN);
      		LED1_TOGGLE();
      		vTaskDelay(500);
      	}
      }
      
      /*任务三:删除任务一*/
      void task3(void* pvParamter)
      {
      	uint8_t key = 0;
      	while(1)
      	{
      		printf("task3正在运行!!!\r\n");
      		key = key_scan(0);
      		
      		if(key == KEY0)
      		{
      			if(task1_task_handler != NULL)
      			{
      				printf("删除task1任务!!!\r\n");
      				vTaskDelete(task1_task_handler);
      				task1_task_handler = NULL;
      			}			
      		}
      		vTaskDelay(10);
      	}
      }
      

4. 删除任务及具体实现

在这里插入图片描述

内部实现:

  1. 获取所要删除任务的控制块:通过传入的任务句柄,判断所需要删除哪个任务,NULL代表删除自身

    pxTCB = prvGetTCBFromHandle( xTaskToDelete );
    
  2. 将被删除任务,移除所在列表:将该任务从列表中移除,包括:就绪、阻塞、挂起、事件等列表

    //2.将被删除任务,移除所在列表:将该任务从列表中移除,包括:就绪、阻塞、挂起、事件等列表
    if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
    {
         taskRESET_READY_PRIORITY( pxTCB->uxPriority );
    }
    else
    {
         mtCOVERAGE_TEST_MARKER();
    }
    
  3. 判断所需要删除的任务

    删除任务自身,需先添加到等待删除列表,内存释放将在空闲任务执行
    if( pxTCB == pxCurrentTCB )
    {
        vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
        ++uxDeletedTasksWaitingCleanUp;
        traceTASK_DELETE( pxTCB );
        portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
    }
    //删除其他任务,当前任务数量--
    else
    {
        --uxCurrentNumberOfTasks;
        traceTASK_DELETE( pxTCB );
        prvResetNextTaskUnblockTime();
    }
    
    
  4. 更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务

    prvResetNextTaskUnblockTime();
    

具体实现:

    void vTaskDelete( TaskHandle_t xTaskToDelete )
    {
        TCB_t * pxTCB;

        taskENTER_CRITICAL();
        {
            /* If null is passed in here then it is the calling task that is
             * being deleted. */
			//1.获取所要删除任务的控制块:通过传入的任务句柄,判断所需要删除哪个任务,NULL代表删除自身
            pxTCB = prvGetTCBFromHandle( xTaskToDelete );

            /* Remove task from the ready/delayed list. */
			//2.将被删除任务,移除所在列表:将该任务从列表中移除,包括:就绪、阻塞、挂起、事件等列表
            if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
            {
                taskRESET_READY_PRIORITY( pxTCB->uxPriority );
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }

            /* Is the task waiting on an event also? */
            if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
            {
                ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }

            /* Increment the uxTaskNumber also so kernel aware debuggers can
             * detect that the task lists need re-generating.  This is done before
             * portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will
             * not return. */
            uxTaskNumber++;
			
			//判断所需要删除的任务
			//删除任务自身,需先添加到等待删除列表,内存释放将在空闲任务执行
            if( pxTCB == pxCurrentTCB )
            {
                /* A task is deleting itself.  This cannot complete within the
                 * task itself, as a context switch to another task is required.
                 * Place the task in the termination list.  The idle task will
                 * check the termination list and free up any memory allocated by
                 * the scheduler for the TCB and stack of the deleted task. */
                vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );

                /* Increment the ucTasksDeleted variable so the idle task knows
                 * there is a task that has been deleted and that it should therefore
                 * check the xTasksWaitingTermination list. */
                ++uxDeletedTasksWaitingCleanUp;

                /* Call the delete hook before portPRE_TASK_DELETE_HOOK() as
                 * portPRE_TASK_DELETE_HOOK() does not return in the Win32 port. */
                traceTASK_DELETE( pxTCB );

                /* The pre-delete hook is primarily for the Windows simulator,
                 * in which Windows specific clean up operations are performed,
                 * after which it is not possible to yield away from this task -
                 * hence xYieldPending is used to latch that a context switch is
                 * required. */
                portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
            }
			//删除其他任务,当前任务数量--
			//更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务
            else
            {
                --uxCurrentNumberOfTasks;
                traceTASK_DELETE( pxTCB );

                /* Reset the next expected unblock time in case it referred to
                 * the task that has just been deleted. */
                prvResetNextTaskUnblockTime();
            }
        }
        taskEXIT_CRITICAL();

        /* If the task is not deleting itself, call prvDeleteTCB from outside of
         * critical section. If a task deletes itself, prvDeleteTCB is called
         * from prvCheckTasksWaitingTermination which is called from Idle task. */
        if( pxTCB != pxCurrentTCB )
        {
            prvDeleteTCB( pxTCB );
        }

        /* Force a reschedule if it is the currently running task that has just
         * been deleted. */
        if( xSchedulerRunning != pdFALSE )
        {
            if( pxTCB == pxCurrentTCB )
            {
                configASSERT( uxSchedulerSuspended == 0 );
                portYIELD_WITHIN_API();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    }

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

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

相关文章

js_拳皇(下)

文章目录 架构设计视频演示碰撞检测碰撞检测函数 构想血条和计时器全屏后续工作 架构设计 一图胜千言 #mermaid-svg-erOUDyAO5t0XgYyU {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-erOUDyAO5t0XgYyU .error-icon{…

塞尔维亚皇家科学与艺术学院向迪科维奇将军颁发奖章

2015 年 6 月 28 日&#xff0c;在托波拉文化中心礼堂&#xff0c;由塞尔维亚皇家科学家与艺术学院&#xff08;SKANU&#xff09;组织举行了一场颁奖仪式&#xff0c;向过去一年里为科学院做出无私贡献的杰出个人和集体表示感谢。 经塞尔维亚皇家科学与艺术学院一致决定&#…

RWKV 社区近期有哪些学术研究进展?

2024 年 5 月 7 日&#xff0c;我们呼吁大家使用 RWKV-6 替代 Mamba 进行科研。截至 7 月 29 日&#xff0c;来自全球各地的科研团队已经陆续发表了 7 篇基于 RWKV 架构、在各个领域进行深入研究的论文。 新的 RWKV 学术研究主要聚焦于具身智能、图像处理、模型架构三个方面。…

[Windows] 简单易用的图片去水印工具,Inpaint 9.1 单文件版

很多软件都有这个功能&#xff0c;但这个算法非常自然&#xff0c;软件小巧。 而且极为简单&#xff0c;涂鸦笔一抹&#xff0c;点绿色的《处理图像》 &#xff0c;一秒完成。 我从它6.0的版本一直用过来。现在这个是9.1 发现论坛里的 都是几年前的&#xff0c;全部都失效了。 …

【策略工厂模式】记录策略工厂模式简单实现

策略工厂模式 1. 需求背景2. 代码实现2.1 定义基类接口2.2 排序策略接口定义2.3 定义抽象类&#xff0c;实现策略接口2.4 具体的排序策略实现类2.5 实现策略工厂类2.6 控制类 3. 启动测试4. 总结 1. 需求背景 现在需要你创建一个策略工厂类&#xff0c;来根据策略实现各种排序…

达梦数据库的系统视图v$buffer_lru_first

达梦数据库的系统视图v$buffer_lru_first 达梦数据库系统视图V$BUFFER_LRU_FIRST的主要作用是显示所有缓冲区LRU链首页信息。这个视图帮助数据库管理员监控和管理缓冲池中LRU&#xff08;Least Recently Used&#xff0c;最近最少使用&#xff09;链的性能&#xff0c;通过查看…

专业级享受:2024年视频剪辑工具深度评测与推荐

说到视屏剪辑很多人都会想要到剪映吧&#xff0c;那剪映怎么剪辑视屏呢&#xff1f;剪映之外还有没有其他好用到视屏剪辑软件呢&#xff1f;这次就分享一些我自己用过到工具吧。 1.福昕视频编辑 链接直达>>https://www.pdf365.cn/foxit-clip/ 这个视频剪辑软件大大的…

【Stable Diffusion】(基础篇六)—— embedding

embedding 本系列博客笔记主要参考B站nenly同学的视频教程&#xff0c;传送门&#xff1a;B站第一套系统的AI绘画课&#xff01;零基础学会Stable Diffusion&#xff0c;这绝对是你看过的最容易上手的AI绘画教程 | SD WebUI 保姆级攻略_哔哩哔哩_bilibili 除了大模型和VAE之外…

普明服务小程序正式招募合伙人,共绘家政保洁行业新蓝图

随着家政保洁行业的快速发展和消费者对高品质服务需求的日益增长&#xff0c;普明服务小程序凭借其专业、高效、便捷的服务体验&#xff0c;迅速在市场上崭露头角。为了进一步拓展业务&#xff0c;提升品牌影响力&#xff0c;普明服务小程序现正式面向社会招募合伙人&#xff0…

你还在为PDF转Word烦恼?试试这四款免费工具吧!

悄咪咪问一句&#xff0c;大家在平时上班时最头疼的事情有哪些&#xff1f;我想会有很多朋友也有pdf如何在线转换word文档的免费方式&#xff0c;毕竟这些办公文档是非常常见的问题了&#xff0c;所以今天就专门准备这么一篇文章来分享我个人喜欢的四款好用工具&#xff1a; 第…

基于ChatGPT的“看图说话”

重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…

dockerd --debugr排查服务无法启动的异常

1、查看docker服务运行状态 [rootlocalhost ~]# systemctl status docker 2、使用dockerd --debug排查错误 [rootlocalhost ~]# dockerd --debug 3、使用dockerd --debug获取的错误 4、根错误在网上查找解决方法 上图错误为二进制安装docker服务&#xff0c;/usr/local/bin/…

从里到外刨析一台电脑的性能,认识电脑很重要,妈妈再也不用担心我买电脑被骗了

现如今时代随着技术的发展&#xff0c;骗子的手段也越来越高明&#xff0c;因此从里到外刨析一台电脑的性能&#xff0c;认识电脑很重要&#xff0c;特别是准备购买电脑的小伙伴&#xff0c;建议看完这篇文章&#xff0c;其他听别人说电脑的好坏都是虚的&#xff0c;只有自己真…

创新概念:柯尔莫哥洛夫-阿诺德网络

文章目录 一、说明二、基础概念三、kolmogorov-Arnold 网络性质3.1 KAN 的潜在优势3.2 挑战和注意事项 四、基本 KAN 超参数五、COLAB 代码六、注意点 一、说明 kolmogorov-Arnold 网络 (KAN) 是深度学习领域的一项“创新”&#xff0c;它提供了一种受现有 Kolmogorov-Arnold …

【时时三省】(C语言基础)循环语句do while循环

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ——csdn时时三省 do语句的语法 do 循环语句&#xff1b; while(表达式)&#xff1b; 表达式为真继续循环 表达式为假停止 示例: break和cobtinue的运用 示例: cobtinue会跳出这个括号 到while 接着一直循…

Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 | 1/2(含分析过程)

Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 | 1/2&#xff08;含分析过程&#xff09; 目录 Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 | 1/2&#xff08;含分析过程&#xff09; 一、简单介绍 二、机器学习 1、为什么使用机器学习&a…

Qt6 qml文件导入系统组件时不再需要版本号

qt开发中&#xff0c;以往在Qt5中&#xff0c;我们导入quick组件的时候总是要写版本号&#xff0c;挺麻烦的&#xff0c;而现在Qt6中qml导入组件无需再使用版本号了。直接导入即可。 import QtQuick import QtQuick.Controls import QtQuick.Window 想要看是否有这个组件&…

【题解】2024牛客多校第5场

E 安 https://ac.nowcoder.com/acm/contest/81600/E 分析 简单博弈 / 思维题。 当 ai > bi 时&#xff0c;当前骑士一定存活。 当 ai < bi 时&#xff0c;当前骑士一定死亡。 为了使得自己存活的骑士尽可能多&#xff0c;若存在 ai bi 的情况&#xff0c;一定会选…

XXE-lab-master靶场:PHP_xxe

目录 有回显 测试回显位置 构建payload 无回显数据外带 构建payload 漏洞修复 XXE-lab是一个一个包含php,java,python,C#等各种语言版本的XXE漏洞靶场。 下载地址&#xff1a;https://github.com/c0ny1/xxe-lab 将PHPStudy的中间件与版本信息调制为 php-5.4.29Apache 以…

【测试】博客系统的测试报告

项目背景 个人博客系统采用了 SSM 框架与 Redis 缓存技术的组合 &#xff0c;为用户提供了一个功能丰富、性能优越的博客平台。 在技术架构上 &#xff0c;SSM 框架确保了系统的稳定性和可扩展性。Spring 负责管理项目的各种组件 &#xff0c;Spring MVC 实现了清晰的请求处理…