genode/repos/libports/src/lib/libc/task.cc

985 lines
23 KiB
C++

/*
* \brief Libc kernel for main and pthreads user contexts
* \author Christian Helmuth
* \author Emery Hemingway
* \date 2016-01-22
*/
/*
* Copyright (C) 2016-2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <base/component.h>
#include <base/log.h>
#include <base/thread.h>
#include <base/rpc_server.h>
#include <base/rpc_client.h>
#include <base/heap.h>
#include <base/attached_rom_dataspace.h>
#include <vfs/simple_env.h>
#include <timer_session/connection.h>
/* libc includes */
#include <libc/component.h>
#include <libc/select.h>
#include <libc-plugin/plugin_registry.h>
/* libc-internal includes */
#include <internal/call_func.h>
#include <base/internal/unmanaged_singleton.h>
#include "vfs_plugin.h"
#include "libc_init.h"
#include "task.h"
extern char **environ;
namespace Libc {
class Env_implementation;
class Kernel;
class Pthreads;
class Timer;
class Timer_accessor;
class Timeout;
class Timeout_handler;
using Genode::Microseconds;
}
class Libc::Env_implementation : public Libc::Env
{
private:
Genode::Env &_env;
Genode::Attached_rom_dataspace _config { _env, "config" };
Genode::Xml_node _vfs_config()
{
try { return _config.xml().sub_node("vfs"); }
catch (Genode::Xml_node::Nonexistent_sub_node) { }
try {
Genode::Xml_node node =
_config.xml().sub_node("libc").sub_node("vfs");
Genode::warning("'<config> <libc> <vfs/>' is deprecated, "
"please move to '<config> <vfs/>'");
return node;
}
catch (Genode::Xml_node::Nonexistent_sub_node) { }
return Genode::Xml_node("<vfs/>");
}
Genode::Xml_node _libc_config()
{
try { return _config.xml().sub_node("libc"); }
catch (Genode::Xml_node::Nonexistent_sub_node) { }
return Genode::Xml_node("<libc/>");
}
Vfs::Simple_env _vfs_env;
Genode::Xml_node _config_xml() const override {
return _config.xml(); };
public:
Env_implementation(Genode::Env &env, Genode::Allocator &alloc)
: _env(env), _vfs_env(_env, alloc, _vfs_config()) { }
/*************************
** Libc::Env interface **
*************************/
Vfs::File_system &vfs() override {
return _vfs_env.root_dir(); }
Genode::Xml_node libc_config() override {
return _libc_config(); }
/***************************
** Genode::Env interface **
***************************/
Parent &parent() override { return _env.parent(); }
Cpu_session &cpu() override { return _env.cpu(); }
Region_map &rm() override { return _env.rm(); }
Pd_session &pd() override { return _env.pd(); }
Entrypoint &ep() override { return _env.ep(); }
Cpu_session_capability cpu_session_cap() override {
return _env.cpu_session_cap(); }
Pd_session_capability pd_session_cap() override {
return _env.pd_session_cap(); }
Id_space<Parent::Client> &id_space() override {
return _env.id_space(); }
Session_capability session(Parent::Service_name const &name,
Parent::Client::Id id,
Parent::Session_args const &args,
Affinity const &aff) override {
return _env.session(name, id, args, aff); }
void upgrade(Parent::Client::Id id,
Parent::Upgrade_args const &args) override {
return _env.upgrade(id, args); }
void close(Parent::Client::Id id) override {
return _env.close(id); }
/* already done by the libc */
void exec_static_constructors() override { }
void reinit(Native_capability::Raw raw) override {
_env.reinit(raw); }
void reinit_main_thread(Capability<Region_map> &stack_area_rm) override {
_env.reinit_main_thread(stack_area_rm); }
};
struct Libc::Timer
{
::Timer::Connection _timer;
Timer(Genode::Env &env) : _timer(env) { }
Genode::Duration curr_time()
{
return _timer.curr_time();
}
static Microseconds microseconds(Genode::uint64_t timeout_ms)
{
return Microseconds(1000*timeout_ms);
}
static Genode::uint64_t max_timeout()
{
return ~0UL/1000;
}
};
/**
* Interface for obtaining the libc-global timer instance
*
* The 'Timer' is instantiated on demand whenever the 'Timer_accessor::timer'
* method is first called. This way, libc-using components do not depend of a
* timer connection unless they actually use time-related functionality.
*/
struct Libc::Timer_accessor
{
virtual Timer &timer() = 0;
};
struct Libc::Timeout_handler
{
virtual void handle_timeout() = 0;
};
/*
* TODO curr_time wrapping
*/
struct Libc::Timeout
{
Libc::Timer_accessor &_timer_accessor;
Timeout_handler &_handler;
::Timer::One_shot_timeout<Timeout> _timeout;
bool _expired = true;
Genode::uint64_t _absolute_timeout_ms = 0;
void _handle(Duration now)
{
_expired = true;
_absolute_timeout_ms = 0;
_handler.handle_timeout();
}
Timeout(Timer_accessor &timer_accessor, Timeout_handler &handler)
:
_timer_accessor(timer_accessor),
_handler(handler),
_timeout(_timer_accessor.timer()._timer, *this, &Timeout::_handle)
{ }
void start(Genode::uint64_t timeout_ms)
{
Milliseconds const now = _timer_accessor.timer().curr_time().trunc_to_plain_ms();
_expired = false;
_absolute_timeout_ms = now.value + timeout_ms;
_timeout.schedule(_timer_accessor.timer().microseconds(timeout_ms));
}
Genode::uint64_t duration_left() const
{
Milliseconds const now = _timer_accessor.timer().curr_time().trunc_to_plain_ms();
if (_expired || _absolute_timeout_ms < now.value)
return 0;
return _absolute_timeout_ms - now.value;
}
};
struct Libc::Pthreads
{
struct Pthread : Timeout_handler
{
Genode::Lock lock { Genode::Lock::LOCKED };
Pthread *next { nullptr };
Timer_accessor &_timer_accessor;
Constructible<Timeout> _timeout;
void _construct_timeout_once()
{
if (!_timeout.constructed())
_timeout.construct(_timer_accessor, *this);
}
Pthread(Timer_accessor &timer_accessor, Genode::uint64_t timeout_ms)
: _timer_accessor(timer_accessor)
{
if (timeout_ms > 0) {
_construct_timeout_once();
_timeout->start(timeout_ms);
}
}
Genode::uint64_t duration_left()
{
_construct_timeout_once();
return _timeout->duration_left();
}
void handle_timeout() override
{
lock.unlock();
}
};
Genode::Lock mutex;
Pthread *pthreads = nullptr;
Timer_accessor &timer_accessor;
Pthreads(Timer_accessor &timer_accessor)
: timer_accessor(timer_accessor) { }
void resume_all()
{
Genode::Lock::Guard g(mutex);
for (Pthread *p = pthreads; p; p = p->next)
p->lock.unlock();
}
Genode::uint64_t suspend_myself(Suspend_functor & check, Genode::uint64_t timeout_ms)
{
Pthread myself { timer_accessor, timeout_ms };
{
Genode::Lock::Guard g(mutex);
myself.next = pthreads;
pthreads = &myself;
}
if (check.suspend())
myself.lock.lock();
{
Genode::Lock::Guard g(mutex);
/* address of pointer to next pthread allows to change the head */
for (Pthread **next = &pthreads; *next; next = &(*next)->next) {
if (*next == &myself) {
*next = myself.next;
break;
}
}
}
return timeout_ms > 0 ? myself.duration_left() : 0;
}
};
extern void (*libc_select_notify)();
/* internal utility */
static void resumed_callback();
static void suspended_callback();
/**
* Libc "kernel"
*
* This class represents the "kernel" of the libc-based application
* Blocking and deblocking happens here on libc functions like read() or
* select(). This combines blocking of the VFS backend and other signal sources
* (e.g., timers). The libc task runs on the component thread and allocates a
* secondary stack for the application task. Context switching uses
* setjmp/longjmp.
*/
struct Libc::Kernel final : Vfs::Io_response_handler,
Genode::Entrypoint::Io_progress_handler
{
private:
Genode::Env &_env;
Genode::Allocator &_heap;
Env_implementation _libc_env { _env, _heap };
Vfs_plugin _vfs { _libc_env, _heap, *this };
Genode::Reconstructible<Genode::Io_signal_handler<Kernel>> _resume_main_handler {
_env.ep(), *this, &Kernel::_resume_main };
jmp_buf _kernel_context;
jmp_buf _user_context;
bool _valid_user_context = false;
bool _dispatch_pending_io_signals = false;
/* io_progress_handler marker */
bool _io_ready { false };
Genode::Thread &_myself { *Genode::Thread::myself() };
addr_t _kernel_stack = Thread::mystack().top;
void *_user_stack = {
_myself.alloc_secondary_stack(_myself.name().string(),
Component::stack_size()) };
void (*_original_suspended_callback)() = nullptr;
enum State { KERNEL, USER };
State _state = KERNEL;
Application_code *_nested_app_code = nullptr;
Application_code *_app_code = nullptr;
bool _app_returned = false;
bool _resume_main_once = false;
bool _suspend_scheduled = false;
Select_handler_base *_scheduled_select_handler = nullptr;
void _resume_main() { _resume_main_once = true; }
struct Timer_accessor : Libc::Timer_accessor
{
Genode::Env &_env;
/*
* The '_timer' is constructed by whatever thread (main thread
* of pthread) that uses a time-related function first. Hence,
* the construction must be protected by a lock.
*/
Genode::Lock _lock;
Genode::Constructible<Timer> _timer;
Timer_accessor(Genode::Env &env) : _env(env) { }
Timer &timer() override
{
Lock::Guard guard(_lock);
if (!_timer.constructed())
_timer.construct(_env);
return *_timer;
}
};
Timer_accessor _timer_accessor { _env };
struct Main_timeout : Timeout_handler
{
Timer_accessor &_timer_accessor;
Constructible<Timeout> _timeout;
Kernel &_kernel;
void _construct_timeout_once()
{
if (!_timeout.constructed())
_timeout.construct(_timer_accessor, *this);
}
Main_timeout(Timer_accessor &timer_accessor, Kernel &kernel)
: _timer_accessor(timer_accessor), _kernel(kernel)
{ }
void timeout(Genode::uint64_t timeout_ms)
{
_construct_timeout_once();
_timeout->start(timeout_ms);
}
Genode::uint64_t duration_left()
{
_construct_timeout_once();
return _timeout->duration_left();
}
void handle_timeout() override
{
_kernel._resume_main();
}
};
Main_timeout _main_timeout { _timer_accessor, *this };
Pthreads _pthreads { _timer_accessor };
struct Resumer
{
GENODE_RPC(Rpc_resume, void, resume);
GENODE_RPC_INTERFACE(Rpc_resume);
};
struct Resumer_component : Rpc_object<Resumer, Resumer_component>
{
Kernel &_kernel;
Resumer_component(Kernel &kernel) : _kernel(kernel) { }
void resume() { _kernel.run_after_resume(); }
};
/**
* Trampoline to application (user) code
*
* This function is called by the main thread.
*/
static void _user_entry(Libc::Kernel *kernel)
{
struct Check : Suspend_functor {
bool suspend() override { return true; }
} check;
kernel->_app_code->execute();
kernel->_app_returned = true;
kernel->_suspend_main(check, 0);
}
bool _main_context() const { return &_myself == Genode::Thread::myself(); }
/**
* Utility to switch main context to kernel
*
* User context must be saved explicitly before this function is called
* to enable _switch_to_user() later.
*/
void _switch_to_kernel()
{
_state = KERNEL;
_longjmp(_kernel_context, 1);
}
/**
* Utility to switch main context to user
*
* Kernel context must be saved explicitly before this function is called
* to enable _switch_to_kernel() later.
*/
void _switch_to_user()
{
if (!_valid_user_context)
Genode::error("switching to invalid user context");
_resume_main_once = false;
_state = USER;
_longjmp(_user_context, 1);
}
Genode::uint64_t _suspend_main(Suspend_functor &check,
Genode::uint64_t timeout_ms)
{
/* check that we're not running on libc kernel context */
if (Thread::mystack().top == _kernel_stack) {
error("libc suspend() called from non-user context (",
__builtin_return_address(0), ") - aborting");
exit(1);
}
if (!check.suspend())
return 0;
if (timeout_ms > 0)
_main_timeout.timeout(timeout_ms);
if (!_setjmp(_user_context)) {
_valid_user_context = true;
_switch_to_kernel();
} else {
_valid_user_context = false;
}
/*
* During the suspension of the application code a nested
* Libc::with_libc() call took place, which will be executed
* before returning to the first Libc::with_libc() call.
*/
if (_nested_app_code) {
/*
* We have to explicitly set the user context back to true
* because we are borrowing it to execute our nested application
* code.
*/
_valid_user_context = true;
_nested_app_code->execute();
_nested_app_code = nullptr;
_longjmp(_kernel_context, 1);
}
return timeout_ms > 0 ? _main_timeout.duration_left() : 0;
}
public:
Kernel(Genode::Env &env, Genode::Allocator &heap)
: _env(env), _heap(heap)
{
_env.ep().register_io_progress_handler(*this);
}
~Kernel() { Genode::error(__PRETTY_FUNCTION__, " should not be executed!"); }
Libc::Env & libc_env() { return _libc_env; }
/**
* Setup kernel context and run libc application main context
*
* This function is called by the component thread on with_libc().
*/
void run(Libc::Application_code &app_code)
{
if (!_main_context() || _state != KERNEL) {
Genode::error(__PRETTY_FUNCTION__, " called from non-kernel context");
return;
}
_resume_main_once = false;
_app_returned = false;
_app_code = &app_code;
/* save continuation of libc kernel (incl. current stack) */
if (!_setjmp(_kernel_context)) {
/* _setjmp() returned directly -> switch to user stack and call application code */
_state = USER;
call_func(_user_stack, (void *)_user_entry, (void *)this);
/* never reached */
}
/* _setjmp() returned after _longjmp() - user context suspended */
while ((!_app_returned) && (!_suspend_scheduled)) {
if (_dispatch_pending_io_signals) {
/* dispatch pending signals but don't block */
while (_env.ep().dispatch_pending_io_signal()) ;
} else {
/* block for signals */
_env.ep().wait_and_dispatch_one_io_signal();
}
if (_resume_main_once && !_setjmp(_kernel_context))
_switch_to_user();
}
_suspend_scheduled = false;
}
/*
* Run libc application main context after suspend and resume
*/
void run_after_resume()
{
if (!_setjmp(_kernel_context))
_switch_to_user();
while ((!_app_returned) && (!_suspend_scheduled)) {
_env.ep().wait_and_dispatch_one_io_signal();
if (_resume_main_once && !_setjmp(_kernel_context))
_switch_to_user();
}
_suspend_scheduled = false;
}
/**
* Resume all contexts (main and pthreads)
*/
void resume_all()
{
if (_app_returned) {
if (_scheduled_select_handler)
_scheduled_select_handler->dispatch_select();
} else {
if (_main_context())
_resume_main();
else
Genode::Signal_transmitter(*_resume_main_handler).submit();
}
_pthreads.resume_all();
}
/**
* Suspend this context (main or pthread)
*/
Genode::uint64_t suspend(Suspend_functor &check, Genode::uint64_t timeout_ms)
{
if (timeout_ms > 0
&& timeout_ms > _timer_accessor.timer().max_timeout()) {
Genode::warning("libc: limiting exceeding timeout of ",
timeout_ms, " ms to maximum of ",
_timer_accessor.timer().max_timeout(), " ms");
timeout_ms = min(timeout_ms, _timer_accessor.timer().max_timeout());
}
return _main_context() ? _suspend_main(check, timeout_ms)
: _pthreads.suspend_myself(check, timeout_ms);
}
void dispatch_pending_io_signals()
{
if (!_main_context()) return;
if (!_setjmp(_user_context)) {
_valid_user_context = true;
_dispatch_pending_io_signals = true;
_resume_main_once = true; /* afterwards resume main */
_switch_to_kernel();
} else {
_valid_user_context = false;
_dispatch_pending_io_signals = false;
}
}
Genode::Duration current_time()
{
return _timer_accessor.timer().curr_time();
}
/**
* Called from the main context (by fork)
*/
void schedule_suspend(void(*original_suspended_callback) ())
{
if (_state != USER) {
Genode::error(__PRETTY_FUNCTION__, " called from non-user context");
return;
}
/*
* We hook into suspend-resume callback chain to destruct and
* reconstruct parts of the kernel from the context of the initial
* thread, i.e., without holding any object locks.
*/
_original_suspended_callback = original_suspended_callback;
_env.ep().schedule_suspend(suspended_callback, resumed_callback);
if (!_setjmp(_user_context)) {
_valid_user_context = true;
_suspend_scheduled = true;
_switch_to_kernel();
} else {
_valid_user_context = false;
}
}
void schedule_select(Select_handler_base *h)
{
_scheduled_select_handler = h;
}
/**
* Called from the context of the initial thread (on fork)
*/
void entrypoint_suspended()
{
_resume_main_handler.destruct();
_original_suspended_callback();
}
/**
* Called from the context of the initial thread (after fork)
*/
void entrypoint_resumed()
{
_resume_main_handler.construct(_env.ep(), *this, &Kernel::_resume_main);
Resumer_component resumer { *this };
Capability<Resumer> resumer_cap =
_env.ep().rpc_ep().manage(&resumer);
resumer_cap.call<Resumer::Rpc_resume>();
_env.ep().rpc_ep().dissolve(&resumer);
}
/**
* Return if main is currently suspended
*/
bool main_suspended() { return _state == KERNEL; }
/**
* Public alias for _main_context()
*/
bool main_context() const { return _main_context(); }
/**
* Execute application code while already executing in run()
*/
void nested_execution(Libc::Application_code &app_code)
{
_nested_app_code = &app_code;
if (!_setjmp(_kernel_context)) {
_switch_to_user();
}
}
/****************************************
** Vfs::Io_response_handler interface **
****************************************/
void read_ready_response() override {
_io_ready = true; }
void io_progress_response() override {
_io_ready = true; }
/**********************************************
* Entrypoint::Io_progress_handler interface **
**********************************************/
void handle_io_progress() override
{
/*
* TODO: make VFS I/O completion checks during
* kernel time to avoid flapping between stacks
*/
if (_io_ready) {
_io_ready = false;
/* some contexts may have been deblocked from select() */
if (libc_select_notify)
libc_select_notify();
/*
* resume all as any VFS context may have
* been deblocked from blocking I/O
*/
Kernel::resume_all();
}
}
};
/**
* Libc kernel singleton
*
* The singleton is implemented with the unmanaged-singleton utility
* in Component::construct() to ensure it is never destructed
* like normal static global objects. Otherwise, the task object may be
* destructed in a RPC to Rpc_resume, which would result in a deadlock.
*/
static Libc::Kernel *kernel;
/**
* Main context execution was suspended (on fork)
*
* This function is executed in the context of the initial thread.
*/
static void suspended_callback() { kernel->entrypoint_suspended(); }
/**
* Resume main context execution (after fork)
*
* This function is executed in the context of the initial thread.
*/
static void resumed_callback() { kernel->entrypoint_resumed(); }
/*******************
** Libc task API **
*******************/
void Libc::resume_all() { kernel->resume_all(); }
Genode::uint64_t Libc::suspend(Suspend_functor &s, Genode::uint64_t timeout_ms)
{
if (!kernel) {
error("libc kernel not initialized, needed for suspend()");
exit(1);
}
return kernel->suspend(s, timeout_ms);
}
void Libc::dispatch_pending_io_signals()
{
kernel->dispatch_pending_io_signals();
}
Genode::Duration Libc::current_time()
{
return kernel->current_time();
}
void Libc::schedule_suspend(void (*suspended) ())
{
if (!kernel) {
error("libc kernel not initialized, needed for fork()");
exit(1);
}
kernel->schedule_suspend(suspended);
}
void Libc::schedule_select(Libc::Select_handler_base *h)
{
if (!kernel) {
error("libc kernel not initialized, needed for select()");
exit(1);
}
kernel->schedule_select(h);
}
void Libc::execute_in_application_context(Libc::Application_code &app_code)
{
if (!kernel) {
error("libc kernel not initialized, needed for with_libc()");
exit(1);
}
/*
* The libc execution model builds on the main entrypoint, which handles
* all relevant signals (e.g., timing and VFS). Additional component
* entrypoints or pthreads should never call with_libc() but we catch this
* here and just execute the application code directly.
*/
if (!kernel->main_context()) {
app_code.execute();
return;
}
static bool nested = false;
if (nested) {
if (kernel->main_suspended()) {
kernel->nested_execution(app_code);
} else {
app_code.execute();
}
return;
}
nested = true;
kernel->run(app_code);
nested = false;
}
Genode::Xml_node Libc::libc_config()
{
return kernel->libc_env().libc_config();
}
/***************************
** Component entry point **
***************************/
Genode::size_t Component::stack_size() { return Libc::Component::stack_size(); }
void Component::construct(Genode::Env &env)
{
/* initialize the global pointer to environment variables */
static char *null_env = nullptr;
if (!environ) environ = &null_env;
Genode::Allocator &heap =
*unmanaged_singleton<Genode::Heap>(env.ram(), env.rm());
/* pass Genode::Env to libc subsystems that depend on it */
Libc::init_malloc(heap);
Libc::init_mem_alloc(env);
Libc::init_dl(env);
Libc::sysctl_init(env);
Libc::init_pthread_support(env);
kernel = unmanaged_singleton<Libc::Kernel>(env, heap);
Libc::libc_config_init(kernel->libc_env().libc_config());
/*
* XXX The following two steps leave us with the dilemma that we don't know
* which linked library may depend on the successfull initialization of a
* plugin. For example, some high-level library may try to open a network
* connection in its constructor before the network-stack library is
* initialized. But, we can't initialize plugins before calling static
* constructors as those are needed to know about the libc plugin. The only
* solution is to remove all libc plugins beside the VFS implementation,
* which is our final goal anyway.
*/
/* finish static construction of component and libraries */
Libc::with_libc([&] () { env.exec_static_constructors(); });
/* initialize plugins that require Genode::Env */
auto init_plugin = [&] (Libc::Plugin &plugin) {
plugin.init(env);
};
Libc::plugin_registry()->for_each_plugin(init_plugin);
/* construct libc component on kernel stack */
Libc::Component::construct(kernel->libc_env());
}
/**
* Default stack size for libc-using components
*/
Genode::size_t Libc::Component::stack_size() __attribute__((weak));
Genode::size_t Libc::Component::stack_size() { return 32UL*1024*sizeof(long); }