Hello Steve,
On Sun, Dec 17, 2017 at 05:38:59PM -0600, Steven Harp wrote:
Release 17.02 introduced a new execution model of the C runtime which was, I believe, intended to make it possible to write components that combine regular Genode signal handlers and RPC with code using libc. This would be very handy for porting network services etc that are greatly simplified by the C runtime.
We introduced the Libc Component and Select_handler API as a first approach to support Genode components that use contrib implementations from the POSIX environment. During the past month we gathered a lot of experience and also identified some flaws of aspects of our first take. For example, we're currently moving with_libc() from the public API to the libc internals as it brought to much hassle. On the other hand, we're quite certain the current execution model proved practical.
When implementing a libc component the design must submit to the execution model, which in particular assigns the initial entrypoint the role of the I/O signal handler. The following aspects may be relevant.
- There are several libc operations that may block the calling thread. Examples are read, select, and nanosleep.
- The initial entrypoint must pass a code section where it dispatches libc-relevant I/O signals, which in turn may result in unblocking of blocking libc operations. Those code sections are the entrypoint RPC-and-signal dispatcher (which is entered on return from an RPC function or a signal handler) and any blocking libc operation. (Searching the sources for calls to wait_and_dispatch_one_io_signal*() may help to get a picture.)
- Note, if the initial entrypoint is blocked in a libc operation (e.g., read(socket)) it only handles I/O signals but does not respond to RPC requests or other signals. Those are dispatched in the entrypoint dispatcher only.
- If the initial entrypoint is blocked by other means (blocking RPC to a service, semaphore, lock) or busy looping it will not dispatch I/O signals, thus not unblock libc operations (other threads in the same component may block in).
Q: Is there a preferred model for the interaction between the libc and non-libc parts of such a component? For example, suppose we want to make the libc code block on a Genode signal (asynchronous event)?
Not yet. You may find different approaches throughout the sources or in developers repositories. Indeed, we already continued the OpenVPN discussion in the issue tracker [1].
After reading discussion [1] I found that running the libc elements in a new Genode thread, and blocking on a Genode::Semaphore was a workable option, though perhaps not very elegant. The naive approach (using the semaphore without the extra thread) seems to lead to a situation where signals are received but never unblock the libc code.
This situation is expected given the execution model described above: If the initial entrypoint blocks without handling I/O signals other threads never leave their blocking state.
Can anyone recommend some examples/tests that illustrate the intended new approach for: (a) libc code blocking on a Genode signal, (b) a component's RPC service invoking some code using libc (c) libc code invoking Genode RPC (d) invoking libc code in Genode callback, e.g. timeout handler
I suggest you have a look into the libc_component test [2]. This test served as debugging environment during development of the libc execution model. If the test leaves some gaps, please feel free to ask.
In addition to the descriptions above I'd like to note that the mix of Genode and libc code bears a high risk of designing complex solutions with unexpected corner cases. Please look into [1] where I sketched an alternative implementation of the OpenVPN tun backend. I'm certain that future integrations of libc and Genode (at least on the client side) will need a twofold approach: a pseudo file system provided by a Genode-facing VFS plugin and an application backend using this file system. The advantage of this approach is that both parts may gain the best out of their environment. For example, the application backend may use select() to monitor a set of file descriptors while the VFS plugin uses Genode APIs and signal handling. Finally, plugin and application are coupled by the libc file API only. Currently, the most complex pseudo file system is the socket_fs [3].
[1] https://github.com/genodelabs/genode/issues/2595 [2] https://github.com/genodelabs/genode/blob/17.11/repos/libports/src/test/libc... [3] https://genode.org/documentation/release-notes/17.02#Linux_TCP_IP_stack_as_V...
Regards