Skip to content
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

[BUG] Compile error building on Cygwin/POSIX platform #1252

Open
msatti-metrol opened this issue Feb 28, 2025 · 9 comments
Open

[BUG] Compile error building on Cygwin/POSIX platform #1252

msatti-metrol opened this issue Feb 28, 2025 · 9 comments
Labels
bug Something isn't working

Comments

@msatti-metrol
Copy link

Describe the bug
Trying to build the POSIX port, targeting Cygwin (v3.5.7) on Windows 11, there is a build error related to pthreads:

[  7%] Building C object _deps/freertos_kernel-build/portable/CMakeFiles/freertos_kernel_port.dir/ThirdParty/GCC/Posix/port.c.obj
In file included from C:/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:59:
C:/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c: In function ‘xPortStartScheduler’:
C:/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:267:27: error: expected expression before ‘{’ token
  267 |         hSigSetupThread = PTHREAD_ONCE_INIT;
      |                           ^~~~~~~~~~~~~~~~~
make[2]: *** [_deps/freertos_kernel-build/portable/CMakeFiles/freertos_kernel_port.dir/build.make:80: _deps/freertos_kernel-build/portable/CMakeFiles/freertos_kernel_port.dir/ThirdParty/GCC/Posix/port.c.obj] Error 1
make[1]: *** [CMakeFiles/Makefile2:1150: _deps/freertos_kernel-build/portable/CMakeFiles/freertos_kernel_port.dir/all] Error 2
make: *** [Makefile:101: all] Error 2

Caused by PTHREAD_ONCE_INIT not being a valid expression for assigning a value, which is defined as:

#define PTHREAD_ONCE_INIT { PTHREAD_MUTEX_INITIALIZER, 0 }

Note: glibc defines it as the following instead, which is a valid expression:

#define PTHREAD_ONCE_INIT (struct __pthread_once) { __PTHREAD_ONCE_INIT }

Target

  • Toolchain and version: [e.g. x86-64-pc-cygwin (pthread.h from package cygwin-v3.5.7) / gcc-14.2]

Host

  • Host OS: Windows 11

To Reproduce

  • Compile the POSIX port via CMake for Cygwin.

Expected behavior
Build succeeds

Workaround by defining the following in FreeRTOSConfig.h:

#undef PTHREAD_ONCE_INIT 
#define PTHREAD_ONCE_INIT (pthread_once_t) { PTHREAD_MUTEX_INITIALIZER, 0 }

Which gets included by port.c via the CMake config interface library.

Potential fix by something like (?):

pthread_once_t hSigSetupThread2 = PTHREAD_ONCE_INIT;
hSigSetupThread = hSigSetupThread2;

(Not familiar with usage of pthreads, is calling pthread_once() applicable?)

Thanks.

@msatti-metrol msatti-metrol added the bug Something isn't working label Feb 28, 2025
@msatti-metrol msatti-metrol changed the title [BUG] compile error building on Cygwin/POSIX platform [BUG] Compile error building on Cygwin/POSIX platform Feb 28, 2025
@xuelix
Copy link
Member

xuelix commented Mar 1, 2025

Checked with the team, the POSIX port is not meant to run in Cygwin environment.

The pthread_once_t is defined a struct in cygwin pthread.h, which is not what this port would consume.

@msatti-metrol
Copy link
Author

With the above workaround, it does appear to build at least1, although running doesn't seem to be behaving properly2.

Is this something you would be interested in? Might be able to dedicate time and submit a PR.

According to POSIX (I assume this says the same thing as the IEEE published version):

All of the types shall be defined as arithmetic types of an appropriate length, with the following exceptions:
...
pthread_once_t

Therefore pthread_once_t being a struct is valid, although I understand this port wasn't originally designed around that.


1 Also required to build:

target_compile_definitions(freertos_config INTERFACE _GNU_SOURCE)

2 Tick behaviour either seems to not work or be incredibly slow, but I might have set something up wrong.

@aggarg
Copy link
Member

aggarg commented Mar 3, 2025

@msatti-metrol
Copy link
Author

Sorry you're right - was using the version v11.1.0. I think I tried main before but also ran into issues, must have forgotten to switch back.

Without any workarounds, this is the build log on main (3fd7f17):

$ cmake --build build --parallel
[  7%] Building C object _deps/freertos_kernel-build/portable/CMakeFiles/freertos_kernel_port.dir/ThirdParty/GCC/Posix/port.c.o
[ 15%] Building C object _deps/freertos_kernel-build/portable/CMakeFiles/freertos_kernel_port.dir/ThirdParty/GCC/Posix/utils/wait_for_event.c.o
In file included from <command-line>:
/cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c: In function ‘event_create’:
/cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c:53:13: error: expected expression before ‘=’ token
   53 |             pthread_mutexattr_setrobust( &ev->mutexattr, PTHREAD_MUTEX_ROBUST );
      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c: In function ‘event_wait’:
/cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c:78:13: warning: implicit declaration of function ‘pthread_mutex_consistent’; did you mean ‘pthread_mutex_init’? [-Wimplicit-function-declaration]
   78 |             pthread_mutex_consistent( &ev->mutex );
      |             ^~~~~~~~~~~~~~~~~~~~~~~~
      |             pthread_mutex_init
/cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c In function ‘prvPortSetCurrentThreadName’:
/cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:201:9: warning: implicit declaration of function ‘pthread_setname_np’ [-Wimplicit-function-declaration]
  201 |         pthread_setname_np( pthread_self(), pxThreadName );
      |         ^~~~~~~~~~~~~~~~~~
make[2]: *** [_deps/freertos_kernel-build/portable/CMakeFiles/freertos_kernel_port.dir/build.make:90: _deps/freertos_kernel-build/portable/CMakeFiles/freertos_kernel_port.dir/ThirdParty/GCC/Posix/utils/wait_for_event.c.o] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [CMakeFiles/Makefile2:949: _deps/freertos_kernel-build/portable/CMakeFiles/freertos_kernel_port.dir/all] Error 2
make: *** [Makefile:101: all] Error 2

Workarounds:

  • pthread_mutexattr_setrobust() and pthread_mutex_consistent() do not exist on the latest Cygwin, so for this I used the workaround:
# CMakeLists.txt

target_compile_options(freertos_config INTERFACE "-include;FreeRTOS_cygwin_prelude.h")
// FreeRTOS_cygwin_prelude.h

#pragma once

#define pthread_mutexattr_setrobust(...) (void)0
#define pthread_mutex_consistent(...) (void)0
  • __linux__ is not defined on Cygwin, so _GNU_SOURCE also never gets defined, and so pthread_setname_np is undefined - using the original workaround still works however.

It builds ok after using these workarounds.

@msatti-metrol
Copy link
Author

msatti-metrol commented Mar 3, 2025

Additionally, I think there is a race condition bug to do with signals, but need some guidance. Tested against main (3fd7f17), but was also happening on v11.1.0.

Bug description

Application code:

namespace GaugeSimulator::Task
{
    constexpr const char* Name = "GaugeSimulator";
    constexpr UBaseType_t Priority = 4;
    constexpr std::size_t StackWords = configMINIMAL_STACK_SIZE;

    StaticTask_t Tcb;
    StackType_t Stack[StackWords];
    
    void Main([[maybe_unused]] void* parameters)
    {
        for (std::size_t i = 0; i < 5; i++)
        {
            std::cout << "Hello world enter " << i << std::endl;
            vTaskDelay(configTICK_RATE_HZ);
            std::cout << "Hello world exit " << i << std::endl;
        }
        
        while (true)
            vTaskEndScheduler();
    }
}

int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
{
    xTaskCreateStatic(
        GaugeSimulator::Task::Main, 
        GaugeSimulator::Task::Name, 
        GaugeSimulator::Task::StackWords,
        nullptr, 
        GaugeSimulator::Task::Priority, 
        GaugeSimulator::Task::Stack, 
        &GaugeSimulator::Task::Tcb);

    vTaskStartScheduler();
    return 0;
}

The GaugeSimulator task never completes the above vTaskDelay() call, thus the task code never progresses.

Bug analysis

It appears the Tmr Svc task/thread is being suspended while SIGALRM is pending, causing ticks to not occur (although I think this can happen for any task & signal).

Note I have added some additional debugging code into event_wait().

bool event_wait( struct event * ev )
{
    if( pthread_mutex_lock( &ev->mutex ) == EOWNERDEAD )
    {
        #ifndef __APPLE__
            /* If the thread owning the mutex died, make the mutex consistent. */
            pthread_mutex_consistent( &ev->mutex );
        #endif
    }

    while( ev->event_triggered == false )
    {
        char name[128] = {0};

        pthread_t self = pthread_self();
        pthread_getname_np(self, name, sizeof(name));
        
        if (strcmp(name, "Tmr Svc") == 0)
        {
            sigset_t oset;
    
            if (pthread_sigmask(0, NULL, &oset) != 0)
                assert("failed to call sigmask()" && 0);

            if (sigismember(&oset, SIGALRM) > 0)
            {
                if (sigpending(&oset) != 0)
                    assert("failed to call sigpending()" && 0);

                printf("[Tmr Svc] sigismember(SIGALRM) = %d\n", sigismember(&oset, SIGALRM));
                
                assert(sigismember(&oset, SIGALRM) == 0); // <----------------- This assert is triggering!
            }
        }

        pthread_cond_wait( &ev->cond, &ev->mutex );
    }

    ev->event_triggered = false;
    pthread_mutex_unlock( &ev->mutex );
    return true;
}
Raised SIGALRM for RTOS task Tmr Svc, pthread ID = 0xa00035d60
...
[Tmr Svc] sigismember(SIGALRM) = 1
assertion "sigismember(&oset, SIGALRM) == 0" failed: file "/cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c", line 106, function: event_wait

Thread 10 "Tmr Svc" received signal SIGABRT, Aborted.
[Switching to Thread 41164.0x6b28]
0x00007ffcc1ea0344 in ntdll!ZwWaitForMultipleObjects () from /cygdrive/c/WINDOWS/SYSTEM32/ntdll.dll
(gdb) info stack
... (multiple assert stack frame descriptions here) ... 
#8  0x00007ffc429a3977 in cygwin1!.assert_func () from /usr/bin/cygwin1.dll
#9  0x0000000100408068 in event_wait (ev=0xa00035c00)
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c:106
#10 0x0000000100407cb3 in prvSuspendSelf (thread=0x100414478 <uxTimerTaskStack.0+4056>)
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:679
#11 0x0000000100407c80 in prvSwitchThread (pxThreadToResume=0x100412098 <GaugeSimulator::Task::Stack+4056>,
    pxThreadToSuspend=0x100414478 <uxTimerTaskStack.0+4056>)
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:657
#12 0x00000001004073d0 in prvPortYieldFromISR ()
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:385
#13 0x0000000100407400 in vPortYield ()
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:397
#14 0x0000000100405ee2 in prvProcessTimerOrBlockTask (xNextExpireTime=0, xListWasEmpty=1)
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/timers.c:824
#15 0x0000000100405e26 in prvTimerTask (pvParameters=0x0)
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/timers.c:770
#16 0x0000000100407c1f in prvWaitForStart (pvParams=0x100414478 <uxTimerTaskStack.0+4056>)
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:621
#17 0x00007ffc42a01fad in cygwin1!.getreent () from /usr/bin/cygwin1.dll
#18 0x00007ffc429a47d1 in cygwin1!.assert () from /usr/bin/cygwin1.dll
#19 0x0000000000000000 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb)
  • In Tmr Svc, vPortYield() is called, which enters the critical section, masking the signals from being processed.
  • Before the task switch occurs through vTaskSwitchContext(), the tick thread (in prvTimerTickHandler()) raises the SIGALRM signal which gets queued for the Tmr Svc thread (unclear if this is a Cygwin specific behaviour? Does Linux/glibc/etc ignore threads that have the signal masked perhaps?). As the signal is masked, it's never processed.
  • Tmr Svc is suspended and never wakes up, thus never processes the SIGALRM signal. I haven't dived into how this works.
  • If the assert is disabled and the application is allowed to continue, the tick thread raises SIGALRM for the IDLE task/thread (the just switched in active task) as per normal but it is never processed (even though IDLE is still running normally, outside a critical section). I haven't looked into why it's not processed, but I assume it has to do with SIGALRM already being pending.

Does this appear to be a bug to you?

@aggarg
Copy link
Member

aggarg commented Mar 4, 2025

It builds ok after using these workarounds.

These seem good. Thank you for sharing!

Does this appear to be a bug to you?

Can you test with a C application instead of C++? Just want to isolate the issue.

@msatti-metrol
Copy link
Author

Can you test with a C application instead of C++? Just want to isolate the issue.

Sure, same issue:

StackType_t GaugeSimulator_Stack[configMINIMAL_STACK_SIZE];
StaticTask_t GaugeSimulator_Tcb;

void GaugeSimulator_Main(void* parameters)
{
    for (size_t i = 0; i < 5; i++)
    {
        printf("Hello world enter %u\n", i);
        vTaskDelay(configTICK_RATE_HZ);
        printf("Hello world exit %u\n", i);
    }
    
    while (1)
        vTaskEndScheduler();
}

int main(int argc, char* argv[])
{
    printf("Starting\n");

    xTaskCreateStatic(
        GaugeSimulator_Main, 
        "GaugeSimulator", 
        configMINIMAL_STACK_SIZE,
        NULL, 
        4, 
        GaugeSimulator_Stack, 
        &GaugeSimulator_Tcb);

    vTaskStartScheduler();

    printf("Exiting\n");

    return 0;
}
$ cmake --build build --parallel
[  7%] Building C object _deps/freertos_kernel-build/portable/CMakeFiles/freertos_kernel_port.dir/ThirdParty/GCC/Posix/port.c.o
[ 15%] Built target freertos_kernel_port
[ 23%] Building C object _deps/freertos_kernel-build/CMakeFiles/freertos_kernel.dir/croutine.c.o
...
[ 84%] Linking C static library libfreertos_kernel.a
[ 84%] Built target freertos_kernel
[ 92%] Building C object gauge_simulator/CMakeFiles/gauge_simulator.dir/source/src/main.c.o
[100%] Linking C executable gauge_simulator.exe
[100%] Built target gauge_simulator
$ gdb ./build/gauge_simulator/gauge_simulator
...
(gdb) r
Starting program: /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/gauge_simulator/gauge_simulator
[New Thread 8664.0x7d24]
[New Thread 8664.0x7250]
[New Thread 8664.0x9aec]
[New Thread 8664.0x78a0]
[New Thread 8664.0x71cc]
Starting
[New Thread 8664.0x7550]
[New Thread 8664.0x7970]
[New Thread 8664.0x7c4c]
[New Thread 8664.0x4ec8]
Hello world enter 0
[Tmr Svc] sigismember(SIGALRM) = 1
assertion "sigismember(&oset, SIGALRM) == 0" failed: file "/cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c", line 106, function: event_wait

Thread 9 "Tmr Svc" received signal SIGABRT, Aborted.
[Switching to Thread 8664.0x7c4c]
0x00007ffb93300344 in ntdll!ZwWaitForMultipleObjects () from /cygdrive/c/WINDOWS/SYSTEM32/ntdll.dll
(gdb) info stack
...
#8  0x00007ffb15ef3977 in cygwin1!.assert_func () from /usr/bin/cygwin1.dll
#9  0x0000000100407858 in event_wait (ev=0xa00022bb0)
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c:106
#10 0x00000001004074a2 in prvSuspendSelf (thread=0x100413458 <uxTimerTaskStack.0+4056>)
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:612
#11 0x000000010040746f in prvSwitchThread (pxThreadToResume=0x100410ff8 <GaugeSimulator_Stack+4056>,
    pxThreadToSuspend=0x100413458 <uxTimerTaskStack.0+4056>)
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:590
#12 0x00000001004070a0 in prvPortYieldFromISR ()
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:381
#13 0x00000001004070d0 in vPortYield ()
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:393
#14 0x0000000100405bb2 in prvProcessTimerOrBlockTask (xNextExpireTime=0, xListWasEmpty=1)
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/timers.c:824
#15 0x0000000100405af6 in prvTimerTask (pvParameters=0x0)
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/timers.c:770
--Type <RET> for more, q to quit, c to continue without paging--
#16 0x000000010040740e in prvWaitForStart (pvParams=0x100413458 <uxTimerTaskStack.0+4056>)
    at /cygdrive/c/Users/msatti/Development/GaugeSimulator/build/_deps/freertos_kernel-src/portable/ThirdParty/GCC/Posix/port.c:554
#17 0x00007ffb15f51fad in cygwin1!.getreent () from /usr/bin/cygwin1.dll
#18 0x00007ffb15ef47d1 in cygwin1!.assert () from /usr/bin/cygwin1.dll
#19 0x0000000000000000 in ?? ()

@msatti-metrol
Copy link
Author

msatti-metrol commented Mar 4, 2025

Additionally, a proper Linux environment (via Ubuntu 24.10 WSL) appears to behave differently.

I think I can replicate the same bug conditions as Cygwin with these code changes:

bool event_wait( struct event * ev )
{
    if( pthread_mutex_lock( &ev->mutex ) == EOWNERDEAD )
    {
        #ifndef __APPLE__
            /* If the thread owning the mutex died, make the mutex consistent. */
            pthread_mutex_consistent( &ev->mutex );
        #endif
    }

    while( ev->event_triggered == false )
    {
        char pthread_name_buf[128] = {0};

        pthread_t self = pthread_self();
        pthread_getname_np(self, pthread_name_buf, sizeof(pthread_name_buf));
        
        if (strcmp(pthread_name_buf, "Tmr Svc") == 0)
        {
            sigset_t blocked_set;
    
            if (pthread_sigmask(0, NULL, &blocked_set) != 0)
                assert("failed to call sigmask()" && 0);

            if (sigismember(&blocked_set, SIGALRM) > 0)
            {
                if (sigpending(&blocked_set) != 0)
                    assert("failed to call sigpending()" && 0);

                printf("[Tmr Svc] sigismember(SIGALRM) = %d\n", sigismember(&blocked_set, SIGALRM));
                // assert(sigismember(&blocked_set, SIGALRM) == 0);
            }
        }

        pthread_cond_wait( &ev->cond, &ev->mutex );
    }

    ev->event_triggered = false;
    pthread_mutex_unlock( &ev->mutex );
    return true;
}

void vPortDisableInterrupts( void )
{
    if( prvIsFreeRTOSThread() == pdTRUE )
    {
        pthread_sigmask( SIG_BLOCK, &xAllSignals, NULL );

        char pthread_name_buf[128] = {0};
        pthread_t self = pthread_self();
        pthread_getname_np(self, pthread_name_buf, sizeof(pthread_name_buf));
        
        if (strcmp(pthread_name_buf, "Tmr Svc") == 0)
        {
            static int tmr_svc_di_count;
            tmr_svc_di_count += 1;

            if (tmr_svc_di_count == 5)
            {
                printf("Sleeping Tmr Svc\n");
                // Note: this is to force the tick to happen while the signal mask is applied but before the task is switched out.
                usleep(5000000);
            }
        }
    }
}

static void * prvTimerTickHandler( void * arg )
{
    ( void ) arg;

    prvMarkAsFreeRTOSThread();

    prvPortSetCurrentThreadName( "Scheduler timer" );

    while( xTimerTickThreadShouldRun )
    {
        /*
         * signal to the active task to cause tick handling or
         * preemption (if enabled)
         */
        Thread_t * thread = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() );
        pthread_kill( thread->pthread, SIGALRM );
        
        char pthread_name_buf[128] = {0};
        pthread_getname_np(thread->pthread, pthread_name_buf, sizeof(pthread_name_buf));
        printf("Sent SIGALRM to pthread %s,%p\n", pthread_name_buf, (void *)thread->pthread);

        usleep( portTICK_RATE_MICROSECONDS );
    }

    return NULL;
}

static void vPortSystemTickHandler( int sig )
{
    if( prvIsFreeRTOSThread() == pdTRUE )
    {
        char pthread_name_buf[128] = {0};
        pthread_t self = pthread_self();
        pthread_getname_np(self, pthread_name_buf, sizeof(pthread_name_buf));
        printf("Handling SIGALRM from pthread %s,%p\n", pthread_name_buf, (void *)self);

        Thread_t * pxThreadToSuspend;
        Thread_t * pxThreadToResume;

        ( void ) sig;

        uxCriticalNesting++; /* Signals are blocked in this signal handler. */

        pxThreadToSuspend = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() );

        if( xTaskIncrementTick() != pdFALSE )
        {
            /* Select Next Task. */
            vTaskSwitchContext();

            pxThreadToResume = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() );

            prvSwitchThread( pxThreadToResume, pxThreadToSuspend );
        }

        uxCriticalNesting--;
    }
    else
    {
        fprintf( stderr, "vPortSystemTickHandler called from non-FreeRTOS thread\n" );
    }
}

However, the bug manifests differently, and the program appears to behave ok:

Starting
Sent SIGALRM to pthread gauge_simulator,0x7ffff69f36c0
Handling SIGALRM from pthread gauge_simulator,0x7ffff69f36c0
Sleeping Tmr Svc
Sent SIGALRM to pthread Tmr Svc,0x7ffff69f36c0
Sent SIGALRM to pthread Tmr Svc,0x7ffff69f36c0
Sent SIGALRM to pthread Tmr Svc,0x7ffff69f36c0
Sent SIGALRM to pthread Tmr Svc,0x7ffff69f36c0
[Tmr Svc] sigismember(SIGALRM) = 1
Hello world enter 0
Sent SIGALRM to pthread IDLE,0x7ffff71f46c0
Handling SIGALRM from pthread IDLE,0x7ffff71f46c0
Hello world exit 0
Hello world enter 1
Sent SIGALRM to pthread IDLE,0x7ffff71f46c0
Handling SIGALRM from pthread IDLE,0x7ffff71f46c0
Hello world exit 1
Hello world enter 2
Sent SIGALRM to pthread IDLE,0x7ffff71f46c0
Handling SIGALRM from pthread IDLE,0x7ffff71f46c0
Hello world exit 2
Hello world enter 3
Sent SIGALRM to pthread IDLE,0x7ffff71f46c0
Handling SIGALRM from pthread IDLE,0x7ffff71f46c0
Hello world exit 3
Hello world enter 4
Sent SIGALRM to pthread IDLE,0x7ffff71f46c0
Handling SIGALRM from pthread IDLE,0x7ffff71f46c0
Hello world exit 4
Exiting
  • SIGALRM is still pending on the Tmr Svc task when it becomes suspended.
  • On subsequent ticks, SIGALRM is raised on the IDLE task which successfully handles them.
    • Not clear if SIGALRM is still pending on Tmr Svc as there is no pthread API to check this (pthread_sigmask() works on the current thread only).
  • Program behaves "good enough" / almost as expected (one tick is "lost" to Tmr Svc).

@kar-rahul-aws
Copy link
Member

kar-rahul-aws commented Apr 9, 2025

Hi @msatti-metrol

In Cygwin, when the Tmr Svc thread calls vPortYield(), it enters a critical section that blocks SIGALRM. Before the context switch completes, the Tick thread raises SIGALRM, which gets queued specifically to the Tmr Svc thread because Cygwin delivers signals to the targeted thread regardless of its mask status. After the context switch, even though the IDLE thread is running and has SIGALRM unblocked, the signal remains pending on Tmr Svc.

When Tmr Svc runs again, it still has SIGALRM blocked and cannot process the signal, triggering the assert in event_wait().

In Linux, when a signal like SIGALRM is sent to a process, it is delivered to any thread within the process that has the signal unblocked. In Cygwin, however, signals are delivered strictly to the thread that was targeted (using pthread_kill with thread-specific handling), even if that thread has the signal blocked.
This causes SIGALRM to remain pending on the Tmr Svc thread in Cygwin, whereas in Linux, it would have been delivered to another unblocked thread like IDLE.

This fundamental difference in signal delivery behavior between Cygwin and Linux is the root cause of the issue.

Since this behavior is very Cygwin-specific, we would not suggest a change in the FreeRTOS Posix port.

But to avoid hitting the Tmr Svc assert in event_wait(), you can explicitly unblock SIGALRM in prvWaitForStart() - right after thread creation using:

sigset_t unblock;
sigemptyset(&unblock);
sigaddset(&unblock, SIGALRM);
pthread_sigmask(SIG_UNBLOCK, &unblock, NULL);

This ensures every FreeRTOS thread starts with SIGALRM unmasked, regardless of what was inherited from main() or elsewhere. It is safe because signal masking is handled properly later in vPortEnableInterrupts() when exiting critical sections.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants