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
英文地址,請注明出處:http://en.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 - …