Some questions on reentrancy, __DYNAMIC_REENT__ and _impure_ptr

Nick cl26@nicolachel.net
Fri Apr 2 03:27:21 GMT 2021


Thanks Jeff!

Have some follow up questions, esp. about _REENT as it seems to take on 
different expansion results depending on whether I try to print it 
during build or not, details below.

2021-04-01 12:26 に Jeff Johnston さんは書きました:
> On Thu, Apr 1, 2021 at 12:58 AM Nick <cl26@nicolachel.net> wrote:
> 
>> Hi,
>> 
>> I've been trying to enable reentrancy of newlib on a home brew
>> kernel
>> for the x86 platform and have some questions on how various pieces
>> all
>> fits together.
>> 
>> Implemented __getreent () to return a private copy of struct reent,
>> and
>> also hard coded __DYNAMIC_REENT__ and GETREENT_PROVIDED in
>> sys/config.h
>> to rule out any issue of passing in via build CFLAGS or the CFLAGS
>> in
>> configure.host. Things including errno seem to work but not totally
>> making sense.
>> 
>> As many library functions are still accessing the reent structure
>> using
>> _impure_ptr instead of calling my __getreent () function, for
>> example,
>> the CHECK_INIT (_REENT, fp) at the beginning of __swsetup_r (struct
>> _reent *ptr, register FILE * fp).
>> 
>> Questions:
>> 
>> 1. Are the library functions expected to still use _impure_ptr
>> instead
>> of calling __getreent () when both __DYNAMIC_REENT__ and
>> GETREENT_PROVIDED are hard coded in sys/config.h?
> 
> No, the dynamic reent system redefines the _REENT macro in sys/reent.h
> to call
> __getreent().  Something is going wrong in your build.  You can try
> compiling a file with -dD to see what is defined by the preprocessor
> to see
> what went wrong.  If you have specified --enable-newlib-multithread=no
> or
> --disable-newlib-multithread, then the dynamic reent system will not
> be used.


You are right! The build has somehow gone wrong. I added #error in the 
_impure_ptr branch in sys/reent.h like below:

#if defined(__DYNAMIC_REENT__) && !defined(__SINGLE_THREAD__)
#ifndef __getreent
   struct _reent * __getreent (void);
#endif
# define _REENT (__getreent())
#else /* __SINGLE_THREAD__ || !__DYNAMIC_REENT__ */

#error __getreent() is not being used!

# define _REENT _impure_ptr
#endif /* __SINGLE_THREAD__ || !__DYNAMIC_REENT__ */

I also broke the stub __getreent () implementation in getreent.c just in 
case it was unexpectedly pulled in instead of my implementation.

However, _REENT would still expand into to _impure_ptr. as observed by 
objdump the resultant binaries, without triggering either of the above.

What's more, if I add a #pragma message right above the _REENT inside 
library source files to print out what it would expand into, the message 
say that it has expanded to __getreent () as expected, and it really 
does, thus making that specific reference to _REENT working as expected. 
But all other _REENTs scattered throughout the library source still 
expended to _impure_ptr and fails at run time. This is despite the 
"#error __getreent() is not being used!" directive inside sys/reent.h 
never catching anything.

Now I'm quite confused and hopefully it wasn't due to today's date :(

Also --enable-newlib-multithread=no wasn't specified, and specifying 
--enable-newlib-multithread=yes to the configure script does not change 
the behavior either.

Any suggestions on what might be the issue here or where to look for it?

>> If so, how do they provide reentrancy? Since _impure_ptr is a global
>> 
>> pointer visible to all threads and threads can easily step on each
>> other's toes trying to change fields in the reent structure pointed
>> to
>> by _impure_ptr concurrently.
> 
>> If not, what other MACROs or changes should I make so that all the
>> library functions all use __getreent () instead of _impure_ptr? Is
>> it
>> okay to set _impure_ptr to a bad value such as NULL in this case, in
>> 
>> order to catch any unintended access?
> 
> Note there is a global impure ptr reference used for std I/O which is
> shared
> between threads.
> 
>> 2. in the documentation on https://sourceware.org/newlib/, the
>> following
>> is mentioned as needed for syscalls stubs to return errno:
>> 
>> #include <errno.h>
>> #undef errno
>> extern int errno;
>> 
>> If I do include this part, all the syscalls stubs seem to do when
>> they
>> assign values to errno is setting the global int errno; inside
>> reent.c.
>> As user code built against the library don’t read out that integer
>> but
>> instead calls __(), errno set by syscall stubs can't be read out by
>> user
>> code.
>> 
>> If on the other hand I don’t include this part before my syscall
>> stubs,
>> the errno set by them do seem to work as they also set the copy in
>> reent
>> structures. What might I have missed here?
> 
> Look at lib/include/reent.h for explanation of reentrancy and
> syscalls.  You need to specify true
> 
> _r versions of the syscalls that place the errno result into the
> reentrancy structure.  Otherwise,
> you can have a collision in writing to the global errno value before
> it gets transferred unless
> you use a lock mechanism to ensure syscalls don't happen at the same
> time.

I thought if I use errno inside syscall stubs, it is a MACRO rather than 
a global and expands to (*__errno()) which is already thread safe, and 
also the same value/address user code on the same thread can read out 
later?

"specify true _r versions" meaning provide syscall stubs that ends with 
_r instead?

>> 3. There were some old discussions about manually changing
>> _impure_ptr
>> at each context switch. But I’m wondering about the validity of
>> such a
>> method since it seems like a really clumsy maneuver for kernel code
>> at
>> CPL0 to reach into user space belonging to different binaries to
>> change
>> a global pointer. What's more, if manually changing _impure_ptr at
>> each
>> context switch is needed, then what would be the purpose of
>> __DYNAMIC_REENT__, GETREENT_PROVIDED and implementing a __getreent
>> () to
>> get a thread local version?
> 
> See Dave's response on that.
> 
>> 4. Is _global_impure_ptr thread safe? It is a bit concerning as it
>> seems
>> to be pointing to the same copy of impure_data that some libraries
>> calls
>> would access, and even if I try to change _impure_ptr at each
>> context
>> switch, some threads might still be accessing _global_impure_ptr
>> concurrently?
> 
> There are locks that are performed for various I/O functions.  You
> will need to provide
> the low-level lock support ... i.e. provide a lock.c implementation
> (see misc/lock.c, libc/include/sys/lock.h).
> 
>> 5. There were also old discussions about having to provide mutex for
>> 
>> malloc, is this still the case for newer versions of newlib like
>> 4.10?
> 
> There is a mallock lock system which again uses the low-level lock.c
> implementation which you need to provide
> as part of 4.

Makes sense, I'll add a lock.

>> Thanks!
>> Nick
> 
> -- Jeff J.

Thanks again,
Nick



More information about the Newlib mailing list