-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/smp granular locks v4 #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…herit() xTaskPriorityInherit() is called inside a critical section from queue.c. This commit moves the critical section into xTaskPriorityInherit(). Co-authored-by: Sudeep Mohanty <[email protected]>
Changed xPreemptionDisable to be a count rather than a pdTRUE/pdFALSE. This allows nested calls to vTaskPreemptionEnable(), where a yield only occurs when xPreemptionDisable is 0. Co-authored-by: Sudeep Mohanty <[email protected]>
Adds the required checks for granular locking port macros.
Port Config:
- portUSING_GRANULAR_LOCKS to enable granular locks
- portCRITICAL_NESTING_IN_TCB should be disabled
Granular Locking Port Macros:
- Spinlocks
- portSPINLOCK_TYPE
- portINIT_SPINLOCK( pxSpinlock )
- portINIT_SPINLOCK_STATIC
- Locking
- portGET_SPINLOCK()
- portRELEASE_SPINLOCK()
Co-authored-by: Sudeep Mohanty <[email protected]>
- Updated prvCheckForRunStateChange() for granular locks
- Updated vTaskSuspendAll() and xTaskResumeAll()
- Now holds the xTaskSpinlock during kernel suspension
- Increments/decrements xPreemptionDisable. Only yields when 0, thus allowing
for nested suspensions across different data groups
Co-authored-by: Sudeep Mohanty <[email protected]>
Updated critical section macros with granular locks. Some tasks.c API relied on their callers to enter critical sections. This assumption no longer works under granular locking. Critical sections added to the following functions: - `vTaskInternalSetTimeOutState()` - `xTaskIncrementTick()` - `vTaskSwitchContext()` - `xTaskRemoveFromEventList()` - `vTaskInternalSetTimeOutState()` - `eTaskConfirmSleepModeStatus()` - `xTaskPriorityDisinherit()` - `pvTaskIncrementMutexHeldCount()` Added missing suspensions to the following functions: - `vTaskPlaceOnEventList()` - `vTaskPlaceOnUnorderedEventList()` - `vTaskPlaceOnEventListRestricted()` Fixed the locking in vTaskSwitchContext() vTaskSwitchContext() must aquire both kernel locks, viz., task lock and ISR lock. This is because, vTaskSwitchContext() can be called from either task context or ISR context. Also, vTaskSwitchContext() must not alter the interrupt state prematurely. Co-authored-by: Sudeep Mohanty <[email protected]>
Updated queue.c to use granular locking - Added xTaskSpinlock and xISRSpinlock - Replaced critical section macros with data group critical section macros such as taskENTER/EXIT_CRITICAL/_FROM_ISR() with queueENTER/EXIT_CRITICAL_FROM_ISR(). - Added vQueueEnterCritical/FromISR() and vQueueExitCritical/FromISR() which map to the data group critical section macros. - Added prvLockQueueForTasks() and prvUnlockQueueForTasks() as the granular locking equivalents to prvLockQueue() and prvUnlockQueue() respectively Co-authored-by: Sudeep Mohanty <[email protected]>
Updated event_groups.c to use granular locking - Added xTaskSpinlock and xISRSpinlock - Replaced critical section macros with data group critical section macros such as taskENTER/EXIT_CRITICAL/_FROM_ISR() with event_groupsENTER/EXIT_CRITICAL/_FROM_ISR(). - Added vEventGroupsEnterCritical/FromISR() and vEventGroupsExitCriti/FromISR() functions that map to the data group critical section macros. - Added prvLockEventGroupForTasks() and prvUnlockEventGroupForTasks() to suspend the event group when executing non-deterministic code. - xEventGroupSetBits() and vEventGroupDelete() accesses the kernel data group directly. Thus, added vTaskSuspendAll()/xTaskResumeAll() to these fucntions. Co-authored-by: Sudeep Mohanty <[email protected]>
Updated stream_buffer.c to use granular locking - Added xTaskSpinlock and xISRSpinlock - Replaced critical section macros with data group critical section macros such as taskENTER/EXIT_CRITICAL/_FROM_ISR() with sbENTER/EXIT_CRITICAL_FROM_ISR(). - Added vStreambuffersEnterCritical/FromISR() and vStreambuffersExitCritical/FromISR() to map to the data group critical section macros. - Added prvLockStreamBufferForTasks() and prvUnlockStreamBufferForTasks() to suspend the stream buffer when executing non-deterministic code. Co-authored-by: Sudeep Mohanty <[email protected]>
Updated timers.c to use granular locking - Added xTaskSpinlock and xISRSpinlock - Replaced critical section macros with data group critical section macros such as taskENTER/EXIT_CRITICAL() with tmrENTER/EXIT_CRITICAL(). - Added vTimerEnterCritical() and vTimerExitCritical() to map to the data group critical section macros. Co-authored-by: Sudeep Mohanty <[email protected]>
Co-authored-by: Sudeep Mohanty <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Granular Locking Implementation Review
Summary
This PR introduces a significant architectural enhancement to the FreeRTOS SMP kernel by implementing granular locking through the "Dual Spinlock With Data Group Locking" approach. The implementation organizes kernel data into separate data groups (Kernel, Queue, Event Group, Stream Buffer, and Timer data groups), each protected by their own pair of spinlocks (xTaskSpinlock and xISRSpinlock).
Key Architectural Changes
✅ Strengths:
- Well-designed hierarchical data group organization prevents deadlocks
- Maintains backward compatibility with existing applications
- Comprehensive documentation in
granular_locks_v4.md - Proper conditional compilation to maintain single-core compatibility
- Thread safety analysis appears thorough
Issues Requiring Attention
MISRA C 2012 Violations (Critical)
The static analysis identified several MISRA C 2012 violations that must be addressed:
- Rule 20.7: Macro parameters need parentheses protection
- Rule 20.9: Preprocessor identifiers should be tested for definition before use
Code Quality Issues (High Priority)
- Macro Definition Inconsistencies: Non-granular lock macros have trailing semicolons causing style inconsistency
- Return Value Mismatch: Some unlock macros don't preserve expected return values
- Potential Structure Issue: Duplicate conditional compilation blocks in static structure definitions
Implementation Concerns (Medium Priority)
- Performance Impact: While the goal is to reduce lock contention, the implementation adds overhead in lock/unlock operations for each data group
- Complexity: The nested locking approach, while safe, significantly increases code complexity
- Testing Coverage: No evidence of comprehensive testing on actual SMP hardware configurations
Recommendations
Immediate Actions Required:
- Fix all MISRA violations identified in the review comments
- Standardize macro definitions to remove inconsistencies
- Verify data structure layout to ensure static allocation compatibility
Before Merging:
- Performance benchmarking on actual SMP hardware to validate lock contention improvements
- Comprehensive testing with stress scenarios and edge cases
- Memory overhead analysis to quantify the additional spinlock storage requirements
Code Review Confidence Level: Medium-High
The implementation follows good architectural principles and maintains safety through proper critical section management. However, the MISRA violations and inconsistencies need to be resolved before this can be considered production-ready.
The granular locking approach is theoretically sound and should provide performance benefits in high-contention SMP scenarios, but empirical validation is essential given the complexity introduced.
| * \ingroup GranularLocks | ||
| */ | ||
| #if ( portUSING_GRANULAR_LOCKS == 1 ) | ||
| #define taskDATA_GROUP_ENTER_CRITICAL( pxTaskSpinlock, pxISRSpinlock ) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MISRA C 2012 Rule 20.7 violation: Macro parameters should be enclosed in parentheses.
The macro parameter pxTaskSpinlock and pxISRSpinlock should be enclosed in parentheses to prevent unintended operator precedence issues when the macro is expanded.
Fix:
#define taskDATA_GROUP_ENTER_CRITICAL( pxTaskSpinlock, pxISRSpinlock ) \
do { \
/* Disable preemption to avoid task state changes during the critical section. */ \
vTaskPreemptionDisable( NULL ); \
{ \
const BaseType_t xCoreID = ( BaseType_t ) portGET_CORE_ID(); \
/* Task spinlock is always taken first */ \
portGET_SPINLOCK( xCoreID, ( portSPINLOCK_TYPE * ) (pxTaskSpinlock) ); \
/* Disable interrupts */ \
portDISABLE_INTERRUPTS(); \
/* Take the ISR spinlock next */ \
portGET_SPINLOCK( xCoreID, ( portSPINLOCK_TYPE * ) (pxISRSpinlock) ); \
/* Increment the critical nesting count */ \
portINCREMENT_CRITICAL_NESTING_COUNT( xCoreID ); \
} \
} while( 0 )| * \ingroup GranularLocks | ||
| */ | ||
| #if ( portUSING_GRANULAR_LOCKS == 1 ) | ||
| #define taskDATA_GROUP_EXIT_CRITICAL( pxTaskSpinlock, pxISRSpinlock ) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MISRA C 2012 Rule 20.7 violation: Macro parameters should be enclosed in parentheses.
The macro parameters pxTaskSpinlock and pxISRSpinlock should be enclosed in parentheses.
Fix:
#define taskDATA_GROUP_EXIT_CRITICAL( pxTaskSpinlock, pxISRSpinlock ) \
do { \
const BaseType_t xCoreID = ( BaseType_t ) portGET_CORE_ID(); \
configASSERT( portGET_CRITICAL_NESTING_COUNT( xCoreID ) > 0U ); \
/* Release the ISR spinlock */ \
portRELEASE_SPINLOCK( xCoreID, ( portSPINLOCK_TYPE * ) (pxISRSpinlock) ); \
/* Release the task spinlock */ \
portRELEASE_SPINLOCK( xCoreID, ( portSPINLOCK_TYPE * ) (pxTaskSpinlock) ); \
/* Decrement the critical nesting count */ \
portDECREMENT_CRITICAL_NESTING_COUNT( xCoreID ); \
/* Enable interrupts only if the critical nesting count is 0 */ \
if( portGET_CRITICAL_NESTING_COUNT( xCoreID ) == 0 ) \
{ \
portENABLE_INTERRUPTS(); \
} \
else \
{ \
mtCOVERAGE_TEST_MARKER(); \
} \
/* Re-enable preemption */ \
vTaskPreemptionEnable( NULL ); \
} while( 0 )| * \defgroup taskDATA_GROUP_ENTER_CRITICAL taskDATA_GROUP_ENTER_CRITICAL | ||
| * \ingroup GranularLocks | ||
| */ | ||
| #if ( portUSING_GRANULAR_LOCKS == 1 ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MISRA C 2012 Rule 20.9 violation: All identifiers used in the controlling expression of #if or #elif preprocessing directives shall be #define'd before evaluation.
The preprocessor identifier portUSING_GRANULAR_LOCKS should be tested for definition before being used in conditional compilation.
Fix:
#if ( defined( portUSING_GRANULAR_LOCKS ) && ( portUSING_GRANULAR_LOCKS == 1 ) )
#define taskDATA_GROUP_ENTER_CRITICAL( pxTaskSpinlock, pxISRSpinlock ) \
do { \
/* ... macro body ... */ \
} while( 0 )
#endif /* #if ( defined( portUSING_GRANULAR_LOCKS ) && ( portUSING_GRANULAR_LOCKS == 1 ) ) */Or ensure portUSING_GRANULAR_LOCKS is always defined in FreeRTOS.h before this point (which appears to be done, but the static analyzer may be checking individual files).
| * Macros to mark the start and end of a critical code region. | ||
| */ | ||
| #if ( portUSING_GRANULAR_LOCKS == 1 ) | ||
| #define queueENTER_CRITICAL( pxQueue ) taskDATA_GROUP_ENTER_CRITICAL( &pxQueue->xTaskSpinlock, &pxQueue->xISRSpinlock ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MISRA C 2012 Rule 20.7 violation: Macro parameters should be enclosed in parentheses.
The macro parameter pxQueue should be enclosed in parentheses to prevent unintended operator precedence issues.
Fix:
#define queueENTER_CRITICAL( pxQueue ) taskDATA_GROUP_ENTER_CRITICAL( &(pxQueue)->xTaskSpinlock, &(pxQueue)->xISRSpinlock )
#define queueENTER_CRITICAL_FROM_ISR( pxQueue, puxSavedInterruptStatus ) taskDATA_GROUP_ENTER_CRITICAL_FROM_ISR( &(pxQueue)->xISRSpinlock, puxSavedInterruptStatus )
#define queueEXIT_CRITICAL( pxQueue ) taskDATA_GROUP_EXIT_CRITICAL( &(pxQueue)->xTaskSpinlock, &(pxQueue)->xISRSpinlock )
#define queueEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus, pxQueue ) taskDATA_GROUP_EXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus, &(pxQueue)->xISRSpinlock )| uint8_t ucQueueType; | ||
| #endif | ||
|
|
||
| #if ( ( portUSING_GRANULAR_LOCKS == 1 ) && ( configNUMBER_OF_CORES > 1 ) ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MISRA C 2012 Rule 20.9 violation: All identifiers used in the controlling expression of #if or #elif preprocessing directives shall be #define'd before evaluation.
Similar to the previous violation with portUSING_GRANULAR_LOCKS and configNUMBER_OF_CORES. Ensure all preprocessor identifiers are properly defined before use.
Fix:
#if ( defined( portUSING_GRANULAR_LOCKS ) && ( portUSING_GRANULAR_LOCKS == 1 ) && defined( configNUMBER_OF_CORES ) && ( configNUMBER_OF_CORES > 1 ) )
portSPINLOCK_TYPE xTaskSpinlock;
portSPINLOCK_TYPE xISRSpinlock;
#endif /* #if ( defined( portUSING_GRANULAR_LOCKS ) && ( portUSING_GRANULAR_LOCKS == 1 ) && defined( configNUMBER_OF_CORES ) && ( configNUMBER_OF_CORES > 1 ) ) */| #define event_groupsEXIT_CRITICAL( pxEventBits ) taskDATA_GROUP_EXIT_CRITICAL( &pxEventBits->xTaskSpinlock, &pxEventBits->xISRSpinlock ) | ||
| #define event_groupsEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus, pxEventBits ) taskDATA_GROUP_EXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus, &pxEventBits->xISRSpinlock ) | ||
| #else /* #if ( portUSING_GRANULAR_LOCKS == 1 ) */ | ||
| #define event_groupsENTER_CRITICAL( pxEventBits ) taskENTER_CRITICAL(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Style Issue: Inconsistent macro definition style.
The non-granular lock versions of these macros have semicolons, which is inconsistent with FreeRTOS style and can cause issues in certain contexts.
Fix:
#else /* #if ( portUSING_GRANULAR_LOCKS == 1 ) */
#define event_groupsENTER_CRITICAL( pxEventBits ) taskENTER_CRITICAL()
#define event_groupsENTER_CRITICAL_FROM_ISR( pxEventBits, puxSavedInterruptStatus ) do { *( puxSavedInterruptStatus ) = taskENTER_CRITICAL_FROM_ISR(); } while( 0 )
#define event_groupsEXIT_CRITICAL( pxEventBits ) taskEXIT_CRITICAL()
#define event_groupsEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus, pxEventBits ) taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus )
#endif /* #if ( portUSING_GRANULAR_LOCKS == 1 ) */Remove the trailing semicolons to maintain consistency with FreeRTOS coding standards.
| #if ( ( portUSING_GRANULAR_LOCKS == 1 ) && ( configNUMBER_OF_CORES > 1 ) ) | ||
| #define event_groupsLOCK( pxEventBits ) prvLockEventGroupForTasks( pxEventBits ) | ||
| #define event_groupsUNLOCK( pxEventBits ) prvUnlockEventGroupForTasks( pxEventBits ); | ||
| #else /* #if ( ( portUSING_GRANULAR_LOCKS == 1 ) && ( configNUMBER_OF_CORES > 1 ) ) */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Macro Issue: Missing return value in unlock macro.
The event_groupsUNLOCK macro should return a value consistent with the xTaskResumeAll() function it replaces.
Fix:
#if ( ( portUSING_GRANULAR_LOCKS == 1 ) && ( configNUMBER_OF_CORES > 1 ) )
#define event_groupsLOCK( pxEventBits ) prvLockEventGroupForTasks( pxEventBits )
#define event_groupsUNLOCK( pxEventBits ) prvUnlockEventGroupForTasks( pxEventBits )
#else /* #if ( ( portUSING_GRANULAR_LOCKS == 1 ) && ( configNUMBER_OF_CORES > 1 ) ) */
#define event_groupsLOCK( pxEventBits ) vTaskSuspendAll()
#define event_groupsUNLOCK( pxEventBits ) xTaskResumeAll()
#endif /* #if ( ( portUSING_GRANULAR_LOCKS == 1 ) && ( configNUMBER_OF_CORES > 1 ) ) */Note the removal of the semicolon in the granular lock version to match the non-granular version's return behavior.
| UBaseType_t xDummy25; | ||
| #endif | ||
| #if ( configUSE_TASK_PREEMPTION_DISABLE == 1 ) | ||
| BaseType_t xDummy26; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential Issue: Duplicate conditional compilation blocks.
There appear to be two separate #if ( configUSE_TASK_PREEMPTION_DISABLE == 1 ) blocks on consecutive lines, which may indicate a copy-paste error.
Current code:
#if ( configUSE_TASK_PREEMPTION_DISABLE == 1 )
UBaseType_t xDummy25;
#endif
#if ( configUSE_TASK_PREEMPTION_DISABLE == 1 )
BaseType_t xDummy26;
#endifPlease verify: Is this intentional or should these be combined into a single block?
Suggested fix if combining:
#if ( configUSE_TASK_PREEMPTION_DISABLE == 1 )
UBaseType_t xDummy25;
BaseType_t xDummy26;
#endif
This PR adds support for granular locking to the FreeRTOS kernel.
Description
Granular locking introduces the concept of having localized locks per kernel data group for SMP configuration. This method is an optional replacement of the existing kernel locks and is controlled by a new port layer configuration, viz., portUSING_GRANULAR_LOCKS. More details about the approach can be found here.
Test Steps
The implementation has been tested on Espressif SoC targets, viz., the ESP32 using the ESP-IDF framework.
To test the implementation of the granular locking scheme on esp32, setup the ESP-IDF environment on your local machine. The steps to follow are listed in the Getting Started Guide.
Instead of the main ESP-IDF repository, the granular locks implementation resides in this froked repository - https://github.com/sudeep-mohanty/esp-idf.
Once you have cloned the forked repository and setup the ESP-IDF environment, change your branch to feat/granular_locks_tests.
To run the FreeRTOS unit tests, change the directory to components/freertos/test_apps/freertos where all the test cases are located.
Performance tests are located in the performance subfolder at the same location.
Setup the target device with the command idf.py seet-target esp32.
Select the Amazon FreeRTOS SMP kernel using the menuconfig options. To do this you must enter the command -idf.py menuconfig -> Component config -> FreeRTOS -> Kernel -> Run the Amazon SMP FreeRTOS kernel instead (FEATURE UNDER DEVELOPMENT). Save the configuration and exit the menuconfig.
Now, build and flash the unit test application with the command idf.py build flash monitor.
Once the app is up, you can enter the number of the test case and run the unity test case.
TODO
Test setup for other targets (Raspberry Pi Pico)
Generic target tests to be uploaded to the FreeRTOS repository.
Checklist:
I have tested my changes. No regression in existing tests.
I have modified and/or added unit-tests to cover the code changes in this Pull Request.
Related Issue
Closes FreeRTOS#905
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.