How to work with errno

I have a somewhat general question regarding the usage of errno and thread safety.

Many C functions, such as strtol or similar, set the errno to indicate errors. You can also set it manually to detect if such a function set an error value.

My questions:

  1. Is the usage of errno thread-safe, or should I disable scheduling and interrupts while using it?
  2. How does the errno defined in newlib(-nano) play together with the errno macro defined in nx_bsd.h? Are there issues when nx_bsd is included? Does the include order matter?
  • Newlib reentrancy is discussed here :-

    and some info regarding errno is at the start of section 12.1 here:-

    There is also the issue that the use of malloc in newlib is not thread safe without locks being implemented (malloc can be called internally by some of the library functions :-

    NetX BSD uses bsd_errno in the Thread control block of the currently executing thread :-

     

    which is defined in the properties for the ThreadX source :-

     

    So NetX BSD and newlib use totally different errno implementations.

  • In reply to Jeremy:

    Hi Jeremy,

    thanks for the information!

    I knew about malloc, which I fixed by linking in wrapper functions for memory allocations that use a mutex.

    I suppose that ThreadX doesn't modify the global variable _impure_ptr to point to a thread specific _reent struct? Is there any way to make it do this as a user? Otherwise I'll have to go look for the functions that I use and replace them with the reentrant ones. I think this would make a great extension to ThreadX.

    As far as I understand it, I'm meant to use the __errno_r(ptr) macro and pass the _reent struct to get the thread-safe error after using one of the _r functions.

    One more thing, do you know if there are similar reentrancy problems with C++?

    So far I read that compiling with -pthread will produce reentrant C++ code, but pthread doesn't seem to be supported in newlib as far as I can tell (and it would likely need ThreadX-specific code to make it work).

    It's clear that custom callbacks, constructors and the like will have to be reentrant and thread safe, but are there any unsafe classes or functions in the C++ standard library that are best avoided?

  • In reply to ChrisS:

    Newlib declares one _reent structure and aims _impure_ptr at it during initialisation, so everything starts out correctly for situations where only one thread of execution is in the library at a time.

    To facilitate multiple contexts, you must provide one _reent structure for each execution thread, and you must move _impure_ptr between these structures during context switches.

    To allocate a __reent structure per thread, we need to have the ThreadX source added to the project, in the properties, change TX_THREAD_EXTENSTION_2 to

    struct _reent newlib_errno;

    and add the TX_THREAD_CREATE_EXTENTION :-

    _REENT_INIT_PTR_ZEROED(&thread_ptr->newlib_errno);

    Then in the C complier preprocessor definitions add :-

    bsd_errno=newlib_errno._errno

    and

    TX_ENABLE_EXECUTION_CHANGE_NOTIFY

     

    The define bsd_errno=newlib_errno._errno will mean that the NetX BSD functions set_errno() and get_errno() will use the same errno as newlib :-

     

    and adding TX_ENABLE_EXECUTION_CHANGE_NOTIFY means we can get the PendSV handler to call a function we have control over :-

     

     

    and in that function set the newlib __impure_ptr to point to the currently executing thread __reent structure :-

     

    void _tx_execution_thread_enter(void)
    {
        /* called from the PendSV handler, interrupts are disabled at this point */
        _impure_ptr = &_tx_thread_current_ptr->newlib_errno;
    }

     

    If you want newlib and NetX BSD to still use separate implementations of errno, then this will have to be tweaked slightly.

     

    SSP_1_7_8_S5D9_PK_NetX_BSD_reent_per_thread.zip

  • In reply to Jeremy:

    Thanks, that looks great! I was thinking along these lines but didn't know how to execute code on context switches.
    I will implement this and see how it works. Only possible problem I see might be memory usage increase per thread, about 1 kB on newlib, but I think I can handle that.

    Any idea about C++ (see edit above)?
  • In reply to ChrisS:

    The only reentrancy issue I am aware of with C++ and newlib is if the malloc locks are not implemented.
  • In reply to Jeremy:

    I have implemented your solution now and it appears to work. I haven't done any concurrency tests but I think the associated problems would have appeared rarely in my code before as I didn't see a problem in practice yet, so I don't really have a test case.

    Two hints for others looking to use this solution:
    1) The example project includes two files, tx_execution_profile.c and .h that need to be added with these filenames, implementing the above mentioned function void _tx_execution_thread_enter(void) manually isn't sufficient.
    2) The usually used "int bsd_errno;" thread extension should be removed as far as I can tell.
  • In reply to Jeremy:

    I have found one more possible function that might need to be implemented, any comments about that one?
    stackoverflow.com/.../what-can-i-do-about-gnu-ld-legacy-sync-synchronize-warning
    Here is some more info about related functions: gcc.gnu.org/.../_005f_005fsync-Builtins.html

    Are these implemented in the arm toolchain somewhere?
    Same thing goes for gcc.gnu.org/.../_005f_005fatomic-Builtins.html but it seems these might be implemented, given that they are meant to work better on different platforms and that gcc recommends toolchains to provide own versions.

    What kind of barrier should one use for __sync_synchronize()? __DSB() ? Should one also disable interrupts and context switching? If so, how to reenable them? This function doesn't appear to be meant for that.
  • In reply to ChrisS:

    What makes you think you need to implement __sync_synchronize()? Are you seeing a compiler/linker error or warning?

    From the GCC documentation for Legacy __sync Built-in Functions :-

    "Not all operations are supported by all target processors. If a particular operation cannot be implemented on the target processor, a warning is generated and a call to an external function is generated. "