FreeRTOS源码分析二:task启动(RISCV架构)

系列文章目录

FreeRTOS源码分析一:task创建(RISCV架构)


文章目录

  • 系列文章目录
  • 前言
  • vTaskStartScheduler 调度器启动函数
    • xPortStartScheduler架构特定调度器启动函数
      • vPortSetupTimerInterrupt启动 RISCV 定时器中断
      • xPortStartFirstTask启动第一个任务
    • 空闲任务
  • 总结


前言

本文继续看 task 的运行。主要解析函数 vTaskStartScheduler

主函数中调用函数 vTaskStartScheduler 开始调度。

int main_blinky( void )
{vSendString( "Hello FreeRTOS!" );/* Create the queue. */xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );if( xQueue != NULL ){/* Start the two tasks as described in the comments at the top of this* file. */xTaskCreate( prvQueueReceiveTask, "Rx", configMINIMAL_STACK_SIZE * 2U, NULL,mainQUEUE_RECEIVE_TASK_PRIORITY, NULL );xTaskCreate( prvQueueSendTask, "Tx", configMINIMAL_STACK_SIZE * 2U, NULL,mainQUEUE_SEND_TASK_PRIORITY, NULL );}vTaskStartScheduler();return 0;
}

vTaskStartScheduler 调度器启动函数

初始化核心调度数据结构、创建必要的后台任务(如 idle task 和 timer service task),并启动第一个任务的上下文切换,从而进入多任务运行模式。

void vTaskStartScheduler( void )
{BaseType_t xReturn;//  创建 Idle Task(空闲任务)xReturn = prvCreateIdleTasks();// 启用了软件定时器(configUSE_TIMERS)#if ( configUSE_TIMERS == 1 ){if( xReturn == pdPASS ){// 创建 Timer Service Task。xReturn = xTimerCreateTimerTask();}}#endif /* configUSE_TIMERS */if( xReturn == pdPASS ){/* Interrupts are turned off here, to ensure a tick does not occur* before or during the call to xPortStartScheduler().  The stacks of* the created tasks contain a status word with interrupts switched on* so interrupts will automatically get re-enabled when the first task* starts to run. */// 关闭中断,防止 tick 提前进入,xPortStartScheduler中我们会开启中断portDISABLE_INTERRUPTS();// 设置 Tick 计数器、调度状态。xNextTaskUnblockTime = portMAX_DELAY;xSchedulerRunning = pdTRUE;xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;/* Setting up the timer tick is hardware specific and thus in the* portable interface. */// 开启调度( void ) xPortStartScheduler();// 大部分情况下不会返回,除非内存不足}else{configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );}( void ) xIdleTaskHandles;( void ) uxTopUsedPriority;
}

我们暂时先跳过空闲任务创建和时钟服务创建这两个地方,着重看 xPortStartScheduler 这个架构特定的调度器启动函数的实现。

xPortStartScheduler架构特定调度器启动函数

检查中断服务的堆栈对齐并填充字节,初始化必要的硬件设置并启动多任务调度

BaseType_t xPortStartScheduler( void )
{/* 声明外部函数:启动第一个任务的汇编函数 */extern void xPortStartFirstTask( void );/* 如果启用了断言检查 */#if ( configASSERT_DEFINED == 1 ){/* 检查中断栈顶地址的字节对齐* 中断栈与调度器启动前main()函数使用的栈是同一个* 确保栈顶地址符合平台的字节对齐要求 */configASSERT( ( xISRStackTop & portBYTE_ALIGNMENT_MASK ) == 0 );/* 如果配置了中断栈大小(以字为单位) */#ifdef configISR_STACK_SIZE_WORDS{/* 使用特定的填充字节初始化整个中断栈内存区域* 这有助于调试时检测栈溢出和栈使用情况 */memset( ( void * ) xISRStack, portISR_STACK_FILL_BYTE, sizeof( xISRStack ) );}#endif /* configISR_STACK_SIZE_WORDS */}#endif /* configASSERT_DEFINED *//* 设置定时器中断 */vPortSetupTimerInterrupt();/* 如果配置了MTIME和MTIMECMP寄存器的基地址(RISC-V标准定时器) */#if ( ( configMTIME_BASE_ADDRESS != 0 ) && ( configMTIMECMP_BASE_ADDRESS != 0 ) ){/* 启用machine模式下的定时器中断和外部中断* 通过设置mie(Machine Interrupt Enable)寄存器:* - 位7 (0x80): 启用定时器中断* - 位11 (0x800): 启用外部中断* 0x880 = 0x80 | 0x800 */__asm volatile ( "csrs mie, %0" ::"r" ( 0x880 ) );}#endif /* ( configMTIME_BASE_ADDRESS != 0 ) && ( configMTIMECMP_BASE_ADDRESS != 0 ) *//* 启动第一个任务* 这个函数会切换到第一个就绪任务的上下文* 从这点开始,系统进入多任务调度模式 */xPortStartFirstTask();/* 正常情况下不应该执行到这里* 因为调用xPortStartFirstTask()后,只有任务应该在执行* 如果执行到这里,说明调度器启动失败 */return pdFAIL;
}

这里我们需要关注两个函数,一个是 vPortSetupTimerInterrupt 用于设置定时器中断。一个是 xPortStartFirstTask,启动第一个任务。

vPortSetupTimerInterrupt启动 RISCV 定时器中断

简单来说,只需要读取 mtime 得到当前系统运行时间,并加上余量,设置 mtimecmp 即可在 mtime > mtimecmp 时触发始终中断。
实际实现也是这样。

void vPortSetupTimerInterrupt( void )
{uint32_t ulCurrentTimeHigh, ulCurrentTimeLow;/* 设置指向MTIME寄存器的指针,MTIME是64位寄存器,高32位在+4字节偏移处 */volatile uint32_t * const pulTimeHigh = ( volatile uint32_t * const ) ( ( configMTIME_BASE_ADDRESS ) + 4UL ); /* 8-byte type so high 32-bit word is 4 bytes up. */volatile uint32_t * const pulTimeLow = ( volatile uint32_t * const ) ( configMTIME_BASE_ADDRESS );volatile uint32_t ulHartId;/* 读取当前硬件线程ID(Hart ID),用于确定使用哪个定时器比较寄存器 */__asm volatile ( "csrr %0, mhartid" : "=r" ( ulHartId ) );/* 根据Hart ID计算对应的MTIMECMP寄存器地址,每个hart有独立的比较寄存器 */pullMachineTimerCompareRegister = ( volatile uint64_t * ) ( ullMachineTimerCompareRegisterBase + ( ulHartId * sizeof( uint64_t ) ) );/* 原子地读取64位MTIME寄存器值,防止在读取过程中高位发生变化 */do{ulCurrentTimeHigh = *pulTimeHigh;  /* 先读取高32位 */ulCurrentTimeLow = *pulTimeLow;    /* 再读取低32位 */} while( ulCurrentTimeHigh != *pulTimeHigh ); /* 确保读取期间高位没有变化 *//* 将32位的高低位组合成64位的当前时间值 */ullNextTime = ( uint64_t ) ulCurrentTimeHigh;ullNextTime <<= 32ULL; /* 高32位左移到正确位置 */ullNextTime |= ( uint64_t ) ulCurrentTimeLow; /* 或上低32位 *//* 计算下次定时器中断的时间点:当前时间 + 一个tick的时间增量 */ullNextTime += ( uint64_t ) uxTimerIncrementsForOneTick;/* 设置机器定时器比较寄存器,当MTIME达到这个值时触发中断 */*pullMachineTimerCompareRegister = ullNextTime;/* 预先计算下下次中断的时间,为下次中断处理做准备 */ullNextTime += ( uint64_t ) uxTimerIncrementsForOneTick;
}

xPortStartFirstTask启动第一个任务

xPortStartFirstTask 只需按照堆栈中放置数据的约定,把数据放入合适的寄存器,把PC放入RA寄存器,调用ret返回即可执行任务

xPortStartFirstTask:/* 任务启动函数 - 启动第一个FreeRTOS任务 */load_x  sp, pxCurrentTCB            /* 将当前任务控制块(TCB)的地址加载到栈指针寄存器 */load_x  sp, 0( sp )                 /* 从TCB的第一个成员读取该任务的栈指针值,更新sp *//* 恢复任务的上下文 - 按照栈中保存的顺序恢复寄存器 */load_x  x1, 0( sp )                 /* 恢复x1寄存器(ra - 返回地址),用作任务函数的返回地址 */load_x  x5, 1 * portWORD_SIZE( sp ) /* 恢复初始mstatus寄存器值到x5(t0) */addi    x5, x5, 0x08                /* 设置MIE位(Machine Interrupt Enable),使任务启动时中断使能 */csrw    mstatus, x5                 /* 将修改后的mstatus写入控制状态寄存器,从此处开始中断使能! */portasmRESTORE_ADDITIONAL_REGISTERS /* 恢复RISC-V实现特有的额外寄存器(在freertos_risc_v_chip_specific_extensions.h中定义) *//* 恢复通用寄存器 - 临时寄存器和参数寄存器 */load_x  x7,  5  * portWORD_SIZE( sp )   /* 恢复t2寄存器 */load_x  x8,  6  * portWORD_SIZE( sp )   /* 恢复s0/fp寄存器(帧指针) */load_x  x9,  7  * portWORD_SIZE( sp )   /* 恢复s1寄存器 */load_x  x10, 8  * portWORD_SIZE( sp )   /* 恢复a0寄存器(第一个参数/返回值) */load_x  x11, 9  * portWORD_SIZE( sp )   /* 恢复a1寄存器(第二个参数) */load_x  x12, 10 * portWORD_SIZE( sp )   /* 恢复a2寄存器(第三个参数) */load_x  x13, 11 * portWORD_SIZE( sp )   /* 恢复a3寄存器(第四个参数) */load_x  x14, 12 * portWORD_SIZE( sp )   /* 恢复a4寄存器(第五个参数) */load_x  x15, 13 * portWORD_SIZE( sp )   /* 恢复a5寄存器(第六个参数) */#ifndef __riscv_32e/* 非RV32E架构(完整寄存器集)才需要恢复以下寄存器 */load_x  x16, 14 * portWORD_SIZE( sp )   /* 恢复a6寄存器(第七个参数) */load_x  x17, 15 * portWORD_SIZE( sp )   /* 恢复a7寄存器(第八个参数) */load_x  x18, 16 * portWORD_SIZE( sp )   /* 恢复s2寄存器(保存寄存器) */load_x  x19, 17 * portWORD_SIZE( sp )   /* 恢复s3寄存器(保存寄存器) */load_x  x20, 18 * portWORD_SIZE( sp )   /* 恢复s4寄存器(保存寄存器) */load_x  x21, 19 * portWORD_SIZE( sp )   /* 恢复s5寄存器(保存寄存器) */load_x  x22, 20 * portWORD_SIZE( sp )   /* 恢复s6寄存器(保存寄存器) */load_x  x23, 21 * portWORD_SIZE( sp )   /* 恢复s7寄存器(保存寄存器) */load_x  x24, 22 * portWORD_SIZE( sp )   /* 恢复s8寄存器(保存寄存器) */load_x  x25, 23 * portWORD_SIZE( sp )   /* 恢复s9寄存器(保存寄存器) */load_x  x26, 24 * portWORD_SIZE( sp )   /* 恢复s10寄存器(保存寄存器) */load_x  x27, 25 * portWORD_SIZE( sp )   /* 恢复s11寄存器(保存寄存器) */load_x  x28, 26 * portWORD_SIZE( sp )   /* 恢复t3寄存器(临时寄存器) */load_x  x29, 27 * portWORD_SIZE( sp )   /* 恢复t4寄存器(临时寄存器) */load_x  x30, 28 * portWORD_SIZE( sp )   /* 恢复t5寄存器(临时寄存器) */load_x  x31, 29 * portWORD_SIZE( sp )   /* 恢复t6寄存器(临时寄存器) */
#endif/* 恢复任务的临界区嵌套计数器 */load_x  x5, portCRITICAL_NESTING_OFFSET * portWORD_SIZE( sp )    /* 从任务栈中获取该任务的临界嵌套计数值 */load_x  x6, pxCriticalNesting           /* 将全局临界嵌套变量的地址加载到x6 */store_x x5, 0( x6 )                     /* 恢复该任务的临界嵌套计数值到全局变量 *//* 恢复最后两个临时寄存器 */load_x  x5, 3 * portWORD_SIZE( sp )     /* 恢复x5(t0)寄存器的初始值 */load_x  x6, 4 * portWORD_SIZE( sp )     /* 恢复x6(t1)寄存器的初始值 *//* 调整栈指针,释放上下文保存空间 */addi    sp, sp, portCONTEXT_SIZE        /* 栈指针向上调整,跳过已恢复的上下文数据 *//* 跳转到任务函数开始执行 */ret                                     /* 返回到x1(ra)寄存器中保存的任务函数地址,开始执行任务 */

总结调用链如下所示:

main_blinky()                           // 主函数:创建队列和任务,启动调度器└── vTaskStartScheduler()           // 调度器启动:初始化系统任务和调度状态├── prvCreateIdleTasks()    // 创建空闲任务(系统必需的后台任务)├── xTimerCreateTimerTask() // 创建定时器服务任务(软件定时器功能)└── xPortStartScheduler()   // 架构相关启动:硬件初始化和任务切换├── vPortSetupTimerInterrupt()  // 设置RISC-V定时器中断(任务切换时基)└── xPortStartFirstTask()       // 启动第一个任务(上下文切换到用户任务)

这里我们简单看一下前面在开始第一次调度的时候,创建的空闲任务具体内容。

空闲任务

为每一个CPU创建空闲任务,当前仅一个CPU

static BaseType_t prvCreateIdleTasks( void )
{BaseType_t xReturn = pdPASS;                    // 函数返回值,初始化为成功BaseType_t xCoreID;                             // 当前处理的CPU核心IDchar cIdleName[ configMAX_TASK_NAME_LEN ] = { 0 };  // 空闲任务名称缓冲区TaskFunction_t pxIdleTaskFunction = NULL;       // 空闲任务函数指针UBaseType_t xIdleTaskNameIndex;                 // 任务名称字符索引// 第一步:构建空闲任务的基础名称// 从配置文件中的空闲任务名称复制字符,直到遇到空字符或达到最大长度for( xIdleTaskNameIndex = 0U; xIdleTaskNameIndex < ( configMAX_TASK_NAME_LEN - taskRESERVED_TASK_NAME_LENGTH ); xIdleTaskNameIndex++ ){// 逐字符复制配置的空闲任务名称cIdleName[ xIdleTaskNameIndex ] = configIDLE_TASK_NAME[ xIdleTaskNameIndex ];// 如果遇到字符串结束符,停止复制if( cIdleName[ xIdleTaskNameIndex ] == ( char ) 0x00 ){break;}}// 确保字符串以空字符结尾cIdleName[ xIdleTaskNameIndex ] = '\0';// 第二步:为每个CPU核心创建空闲任务// 以最低优先级为每个核心添加空闲任务for( xCoreID = ( BaseType_t ) 0; xCoreID < ( BaseType_t ) configNUMBER_OF_CORES; xCoreID++ ){// 根据系统配置选择合适的空闲任务函数#if ( configNUMBER_OF_CORES == 1 ){// 单核系统:使用标准空闲任务函数pxIdleTaskFunction = &prvIdleTask;}#else /* #if (  configNUMBER_OF_CORES == 1 ) */{/* 在FreeRTOS SMP中,除了主空闲任务外,还会创建 configNUMBER_OF_CORES - 1 个* 被动空闲任务,确保每个核心在没有其他任务可运行时都有空闲任务可执行 */if( xCoreID == 0 ){// 核心0:使用主空闲任务函数pxIdleTaskFunction = &prvIdleTask;}else{// 其他核心:使用被动空闲任务函数pxIdleTaskFunction = &prvPassiveIdleTask;}}#endif /* #if (  configNUMBER_OF_CORES == 1 ) */// 第三步:为多核系统更新空闲任务名称,添加核心ID后缀以区分不同核心的空闲任务/* 在单核FreeRTOS中不需要此功能,因为只有一个空闲任务 */#if ( configNUMBER_OF_CORES > 1 ){// 宏不成立}#else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */{// 动态内存分配方式创建空闲任务/* 空闲任务使用动态分配的RAM创建 */xReturn = xTaskCreate( pxIdleTaskFunction,           // 任务函数cIdleName,                    // 任务名称configMINIMAL_STACK_SIZE,     // 最小栈大小( void * ) NULL,              // 任务参数portPRIVILEGE_BIT,           // 优先级(实际上是 tskIDLE_PRIORITY | portPRIVILEGE_BIT,但tskIDLE_PRIORITY为0)&xIdleTaskHandles[ xCoreID ] ); // 任务句柄存储位置}#endif /* configSUPPORT_STATIC_ALLOCATION */}return xReturn;  // 返回创建结果(pdPASS表示成功,pdFAIL表示失败)
}

pxIdleTaskFunction 是任务函数,具体内容非常简单:

static portTASK_FUNCTION( prvIdleTask, pvParameters )
{/* Stop warnings. */( void ) pvParameters;for( ; configCONTROL_INFINITE_LOOP(); ){/* See if any tasks have deleted themselves - if so then the idle task* is responsible for freeing the deleted task's TCB and stack. */prvCheckTasksWaitingTermination();}
}

简单来说就是循环检查是否需要回收任务空间。


总结

完结撒花!!!

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

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

相关文章

Python编程基础与实践:Python基础运算符与表达式入门

Python运算符与表达式实战 学习目标 通过本课程的学习&#xff0c;学员可以掌握Python中算术运算符、比较运算符、逻辑运算符及赋值运算符的使用方法&#xff0c;并能够构建简单的表达式来解决实际问题。 相关知识点 Python运算符与表达式 学习内容 1 Python运算符与表达式 1.1…

Git下载全攻略(未更新完)

一、在 Windows 上安装 Git​ ​​​ 1.1 下载安装包​ 官方版本可在 Git 官方网站下载,打开Redirecting…,下载会自动开始。此安装包来自名为 Git for Windows 的项目(也称作 msysGit),它与 Git 本身是相互独立的项目,更多相关信息可访问Redirecting Git for Windows…

rocky\centos安装docker镜像的命令

1.安装依赖&#xff1a; sudo yum install -y yum-utils device-mapper-persistent-data lvm22. 选择仓库源&#xff1a; sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo阿里源&#xff1a; sudo yum-config-manager --add-r…

扣子,正式拥抱开源!

资料来源&#xff1a;火山引擎-开发者社区 扣子 是新一代 AI Agent 平台&#xff0c;旗下有四款子产品&#xff1a;「扣子空间」、「扣子开发平台」、「扣子罗盘」 及 Eino 。 我们始终坚信&#xff0c;AI Agent 的未来属于每一位开发者和创造者。为了让前沿的 AI 技术能够更快…

Git 各场景使用方法总结

以下是对 Git 各场景使用方法的全面总结,涵盖 20+ 核心场景和 100+ 命令,包含详细参数、使用示例及原理说明: 一、基础操作场景 1. 仓库初始化 # 本地初始化 git init git init --bare # 创建裸仓库(无工作区) git init -b main # 指…

国际标准组织共聚,智源推动全球AI开源与国际标准双轮驱动人工智能普惠化发展

7 月 26 日&#xff0c;人工智能标准化国际合作论坛在上海召开。该论坛由联合国工业发展组织全球工业人工智能联盟卓越中心主办&#xff0c;中国电子技术标准化研究院、上海人工智能研究院承办&#xff0c;工业和信息化部副部长单忠德、国家市场监督管理总局标准创新管理司司长…

《安富莱嵌入式周报》第356期:H7-TOOL的250M示波器模组批量生产中,自主开发QDD执行器,开源14bit任意波形发生器(2025-07-28)

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版 《安富莱嵌入式周报》第356期&#xff1a;H7-TOOL的250M示波器模组批量生产中&#xff0c;自主开发QDD执行器&a…

大模型学习专栏-导航页

概要 本专栏是小编系统性调研大模型过程中沉淀的知识结晶&#xff0c;涵盖技术原理、实践应用、前沿动态等多维度内容。为助力读者高效学习&#xff0c;特整理此导航页&#xff0c;以清晰脉络串联核心知识点&#xff0c;搭建起系统的大模型学习框架&#xff0c;助您循序渐进掌握…

leetcode热题——组合

组合题目描述给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。你可以按 任何顺序 返回答案。示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4] ]示例 2&#xff1a; 输入&#xff1a;…

暑期算法训练.13

目录 57 力扣14最长公共前缀 57.1 题目解析&#xff1a; 57.2 算法思路 57.3 代码演示&#xff1a; ​编辑 57.4 总结反思&#xff1a; 58 力扣 5最长回文字符串 58.1 题目解析&#xff1a; ​编辑 58.2 算法思路&#xff1a; 58.3 代码演示&#xff1a; ​编辑 …

四、Portainer图形化管理实战与Docker镜像原理

作者&#xff1a;IvanCodes 日期&#xff1a;2025年8月2日 专栏&#xff1a;Docker教程 一、Portainer 安装与基础使用教程 Portainer 是一个轻量级、功能强大的Docker图形化管理界面 (GUI)。它能让你通过简单的Web界面来管理和监控你的Docker容器、镜像、卷、网络等资源&…

网络爬虫(python)入门

一、网络爬虫介绍 网络爬虫&#xff08;Web Crawler&#xff09;是一种自动抓取互联网信息的程序&#xff0c;它能够高效地从海量网页中提取有价值的数据。作为数据采集的利器&#xff0c;爬虫技术在数据分析、搜索引擎、价格监控等领域有着广泛应用。本文将带你全面了解Pytho…

如何解决pip安装报错ModuleNotFoundError: No module named ‘plotnine’问题

【Python系列Bug修复PyCharm控制台pip install报错】如何解决pip安装报错ModuleNotFoundError: No module named ‘plotnine’问题 一、摘要 在使用 PyCharm 进行 Python 开发时&#xff0c;常常需要通过 pip install 安装第三方包。某天&#xff0c;你在终端或 PyCharm 控制…

语校网收录东京语言学校150所:数据结构建模与工程实现全解

语校网收录东京语言学校150所&#xff1a;数据结构建模与工程实现全解 一、为什么语言学校的信息抓取如此困难&#xff1f; 在日语教育领域&#xff0c;“语言学校”是一类极度碎片化的机构体系&#xff0c;尤其在东京地区&#xff0c;2025年时点上已合法设立的语言学校已超1…

【按下电源键后,电脑里发生了什么?——BIOS:启动世界的“第一把钥匙”】

当你按下电源键的瞬间&#xff0c;电脑从一片死寂中“苏醒”。但你是否想过&#xff1a;是什么让屏幕亮起、风扇转动、硬件逐一激活&#xff1f; 这背后&#xff0c;有一个隐藏在主板上的“小程序”在默默掌控全局——它就是 BIOS&#xff08;Basic Input/Output System&#x…

局域网五子棋工具 多人对战无限制

软件介绍 今天推荐一款经典的PC端五子棋游戏——GoBang&#xff0c;绿色免安装版本&#xff0c;完全免费&#xff0c;即开即用&#xff0c;轻松享受对弈乐趣。 游戏模式 软件提供三种对战模式&#xff1a;人人对战、人机对抗以及局域网联机游戏&#xff0c;满足不同玩家的社…

分布式弹幕系统设计

需求:分布式弹幕广播分布式方案1:适用redis 发布订阅来进行不同ws服务器之间的通信优点:适用小系统方案2:对ws服务器进行一致性hash获取ws服务的接入点优点:大型系统缺点:视频连接不均匀挑战点:广播速度聚合广播和线程池来进行优化

梦幻花瓣雨

1. 花瓣设计四种花瓣类型&#xff1a;创建了四种不同形状和颜色的花瓣&#xff08;粉红、淡紫、浅粉和蓝绿色&#xff09;自然形态&#xff1a;使用CSS渐变和复杂边框半径模拟真实花瓣的不规则形状柔和阴影&#xff1a;为花瓣添加微妙的阴影增强立体感2. 动画效果物理模拟&…

React 闭包陷阱及解决方案与 React 16/17/18 版本区别

一、React 闭包陷阱详解1. 什么是闭包陷阱React 闭包陷阱是指在函数组件中使用 Hook&#xff08;特别是 useEffect 和 useCallback&#xff09;时&#xff0c;由于闭包特性导致访问到旧的 state 或 props 值&#xff0c;而非最新值的现象。2. 典型场景示例function Counter() {…

[BJDCTF2020]EasySearch

首先尝试了一下sql注入&#xff0c;但是没有找到不同回显。直接用sqlmap扫描一下&#xff0c;因为这边用的是POST请求&#xff0c;所以需要抓包将请求复制到txt文件中然后使用命令sqlmap -p bp.txt。也没有发现注入漏洞。 再进行目录扫描试试&#xff1a; [02:33:43] 403 - …