912 lines
21 KiB
C++
912 lines
21 KiB
C++
/*
|
|
* \brief Libc kernel for main and pthreads user contexts
|
|
* \author Christian Helmuth
|
|
* \author Emery Hemingway
|
|
* \date 2016-01-22
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2016-2017 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/file_system_factory.h>
|
|
#include <vfs/dir_file_system.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;
|
|
class Io_response_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::Global_file_system_factory _file_system_factory;
|
|
Vfs::Dir_file_system _vfs;
|
|
|
|
Genode::Xml_node _config_xml() const override {
|
|
return _config.xml(); };
|
|
|
|
public:
|
|
|
|
Env_implementation(Genode::Env &env, Genode::Allocator &alloc,
|
|
Vfs::Io_response_handler &io_response_handler)
|
|
:
|
|
_env(env), _file_system_factory(alloc),
|
|
_vfs(_env, alloc, _vfs_config(), io_response_handler,
|
|
_file_system_factory)
|
|
{ }
|
|
|
|
|
|
/*************************
|
|
** Libc::Env interface **
|
|
*************************/
|
|
|
|
Vfs::File_system &vfs() override {
|
|
return _vfs; }
|
|
|
|
Genode::Xml_node libc_config() override {
|
|
return _libc_config(); }
|
|
|
|
|
|
/***************************
|
|
** Genode::Env interface **
|
|
***************************/
|
|
|
|
Parent &parent() override {
|
|
return _env.parent(); }
|
|
|
|
Ram_session &ram() override {
|
|
return _env.ram(); }
|
|
|
|
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(); }
|
|
|
|
Ram_session_capability ram_session_cap() override {
|
|
return _env.ram_session_cap(); }
|
|
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) { }
|
|
|
|
unsigned long curr_time()
|
|
{
|
|
return _timer.curr_time().trunc_to_plain_us().value/1000;
|
|
}
|
|
|
|
static Microseconds microseconds(unsigned long timeout_ms)
|
|
{
|
|
return Microseconds(1000*timeout_ms);
|
|
}
|
|
|
|
static unsigned long 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;
|
|
unsigned long _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(unsigned long timeout_ms)
|
|
{
|
|
unsigned long const now = _timer_accessor.timer().curr_time();
|
|
|
|
_expired = false;
|
|
_absolute_timeout_ms = now + timeout_ms;
|
|
|
|
_timeout.schedule(_timer_accessor.timer().microseconds(timeout_ms));
|
|
}
|
|
|
|
unsigned long duration_left() const
|
|
{
|
|
unsigned long const now = _timer_accessor.timer().curr_time();
|
|
|
|
if (_expired || _absolute_timeout_ms < now)
|
|
return 0;
|
|
|
|
return _absolute_timeout_ms - now;
|
|
}
|
|
};
|
|
|
|
|
|
struct Libc::Pthreads
|
|
{
|
|
struct Pthread : Timeout_handler
|
|
{
|
|
Genode::Lock lock { Genode::Lock::LOCKED };
|
|
Pthread *next { nullptr };
|
|
|
|
Timeout _timeout;
|
|
|
|
Pthread(Timer_accessor &timer_accessor, unsigned long timeout_ms)
|
|
: _timeout(timer_accessor, *this)
|
|
{
|
|
if (timeout_ms > 0)
|
|
_timeout.start(timeout_ms);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
unsigned long suspend_myself(Suspend_functor & check, unsigned long 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._timeout.duration_left() : 0;
|
|
}
|
|
};
|
|
|
|
|
|
extern void (*libc_select_notify)();
|
|
|
|
struct Libc::Io_response_handler : Vfs::Io_response_handler
|
|
{
|
|
void handle_io_response(Vfs::Vfs_handle::Context *) override
|
|
{
|
|
/* some contexts may have been deblocked from select() */
|
|
if (libc_select_notify)
|
|
libc_select_notify();
|
|
|
|
/* resume all as any context may have been deblocked from blocking I/O */
|
|
Libc::resume_all();
|
|
}
|
|
};
|
|
|
|
|
|
/* 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
|
|
{
|
|
private:
|
|
|
|
Genode::Env &_env;
|
|
Genode::Allocator &_heap;
|
|
Io_response_handler _io_response_handler;
|
|
Env_implementation _libc_env { _env, _heap, _io_response_handler };
|
|
Vfs_plugin _vfs { _libc_env, _heap };
|
|
|
|
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;
|
|
|
|
Genode::Thread &_myself { *Genode::Thread::myself() };
|
|
|
|
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(unsigned long timeout_ms)
|
|
{
|
|
_construct_timeout_once();
|
|
_timeout->start(timeout_ms);
|
|
}
|
|
|
|
unsigned long 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);
|
|
}
|
|
|
|
unsigned long _suspend_main(Suspend_functor &check,
|
|
unsigned long timeout_ms)
|
|
{
|
|
/* check if we're running on the user context */
|
|
if (Thread::myself()->mystack().top != (Genode::addr_t)_user_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 supension 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) { }
|
|
|
|
~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)) {
|
|
_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)
|
|
*/
|
|
unsigned long suspend(Suspend_functor &check, unsigned long 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);
|
|
}
|
|
|
|
unsigned long 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; }
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* 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(); }
|
|
|
|
|
|
unsigned long Libc::suspend(Suspend_functor &s, unsigned long timeout_ms)
|
|
{
|
|
if (!kernel) {
|
|
error("libc kernel not initialized, needed for suspend()");
|
|
exit(1);
|
|
}
|
|
return kernel->suspend(s, timeout_ms);
|
|
}
|
|
|
|
unsigned long 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)
|
|
{
|
|
/*
|
|
* XXX We don't support a second entrypoint - pthreads should work as they
|
|
* don't use this code.
|
|
*/
|
|
|
|
if (!kernel) {
|
|
error("libc kernel not initialized, needed for with_libc()");
|
|
exit(1);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/***************************
|
|
** 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);
|
|
|
|
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); }
|