/* * \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 #include #include #include #include #include #include #include #include /* libc includes */ #include #include #include /* libc-internal includes */ #include #include #include #include "vfs_plugin.h" #include "libc_init.h" #include "task.h" extern char **environ; namespace Libc { class Env_implementation; class Cloned_malloc_heap_range; class Malloc_ram_allocator; class Kernel; class Pthreads; class Timer; class Timer_accessor; class Timeout; class Timeout_handler; using Genode::Microseconds; } class Libc::Env_implementation : public Libc::Env, public Config_accessor { private: Genode::Env &_env; Genode::Xml_node const _config; Genode::Xml_node _vfs_config() { try { return _config.sub_node("vfs"); } catch (Genode::Xml_node::Nonexistent_sub_node) { } try { Genode::Xml_node node = _config.sub_node("libc").sub_node("vfs"); Genode::warning("' ' is deprecated, " "please move to ' '"); return node; } catch (Genode::Xml_node::Nonexistent_sub_node) { } return Genode::Xml_node(""); } Genode::Xml_node _libc_config() { try { return _config.sub_node("libc"); } catch (Genode::Xml_node::Nonexistent_sub_node) { } return Genode::Xml_node(""); } Vfs::Simple_env _vfs_env; Genode::Xml_node _config_xml() const override { return _config; }; public: Env_implementation(Genode::Env &env, Genode::Allocator &alloc, Genode::Xml_node config) : _env(env), _config(config), _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(); } /************************************* ** Libc::Config_accessor interface ** *************************************/ Xml_node config() const override { return _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 &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 &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; 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; 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(); struct Libc::Cloned_malloc_heap_range { Genode::Ram_allocator &ram; Genode::Region_map &rm; Genode::Ram_dataspace_capability ds; size_t const size; addr_t const local_addr; Cloned_malloc_heap_range(Genode::Ram_allocator &ram, Genode::Region_map &rm, void *start, size_t size) try : ram(ram), rm(rm), ds(ram.alloc(size)), size(size), local_addr(rm.attach_at(ds, (addr_t)start)) { } catch (Region_map::Region_conflict) { error("could not clone heap region ", Hex_range(local_addr, size)); throw; } void import_content(Clone_connection &clone_connection) { clone_connection.memory_content((void *)local_addr, size); } virtual ~Cloned_malloc_heap_range() { rm.detach(local_addr); ram.free(ds); } }; /* * Utility for tracking the allocation of dataspaces by the malloc heap */ struct Libc::Malloc_ram_allocator : Genode::Ram_allocator { Genode::Allocator &_md_alloc; Genode::Ram_allocator &_ram; struct Dataspace { Genode::Ram_dataspace_capability cap; Dataspace(Genode::Ram_dataspace_capability cap) : cap(cap) { } virtual ~Dataspace() { } }; Genode::Registry > _dataspaces { }; void _release(Genode::Registered &ds) { _ram.free(ds.cap); destroy(_md_alloc, &ds); } Malloc_ram_allocator(Allocator &md_alloc, Ram_allocator &ram) : _md_alloc(md_alloc), _ram(ram) { } ~Malloc_ram_allocator() { _dataspaces.for_each([&] (Registered &ds) { _release(ds); }); } Ram_dataspace_capability alloc(size_t size, Cache_attribute cached) override { Ram_dataspace_capability cap = _ram.alloc(size, cached); new (_md_alloc) Registered(_dataspaces, cap); return cap; } void free(Ram_dataspace_capability ds_cap) override { _dataspaces.for_each([&] (Registered &ds) { if (ds_cap == ds.cap) _release(ds); }); } size_t dataspace_size(Ram_dataspace_capability ds_cap) const override { return _ram.dataspace_size(ds_cap); } }; /** * 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, Reset_malloc_heap { private: Genode::Env &_env; /** * Allocator for libc-internal data * * Not mirrored to forked processes. Preserved across 'execve' calls. */ Genode::Allocator &_heap; /** * Allocator for application-owned data * * Mirrored to forked processes. Not preserved across 'execve' calls. */ Genode::Reconstructible _malloc_ram { _heap, _env.ram() }; Genode::Constructible _malloc_heap { }; Genode::Registry > _cloned_heap_ranges { }; /** * Reset_malloc_heap interface used by execve */ void reset_malloc_heap() override; Genode::Attached_rom_dataspace _config_ds { _env, "config" }; Xml_node const _config = _config_ds.xml(); bool const _cloned = _config.has_sub_node("libc") && _config.sub_node("libc").attribute_value("cloned", false); void _init_malloc_heap() { if (_cloned) return; _malloc_heap.construct(*_malloc_ram, _env.rm()); init_malloc(*_malloc_heap); } bool const _malloc_heap_initialized = (_init_malloc_heap(), true); Env_implementation _libc_env { _env, _heap, _config }; pid_t const _pid = _libc_env.libc_config().attribute_value("pid", 0U); Vfs_plugin _vfs { _libc_env, _heap, *this }; Genode::Reconstructible> _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; size_t _user_stack_size() { size_t size = Component::stack_size(); if (!_cloned) return size; _libc_env.libc_config().with_sub_node("stack", [&] (Xml_node stack) { size = stack.attribute_value("size", 0UL); }); return size; } void *_user_stack = { _myself.alloc_secondary_stack(_myself.name().string(), _user_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; Kernel_routine *_kernel_routine = 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_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; 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 { 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() && !_kernel_routine) 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; } void _init_file_descriptors(); void _clone_state_from_parent(); public: Kernel(Genode::Env &env, Genode::Allocator &heap) : _env(env), _heap(heap) { _env.ep().register_io_progress_handler(*this); if (_cloned) _clone_state_from_parent(); Libc::init_fork(_env, _libc_env, _heap, *_malloc_heap, _pid); Libc::init_execve(_env, _heap, _user_stack, *this); _init_file_descriptors(); } ~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 */ if (_cloned) { _switch_to_user(); } else { _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 (_kernel_routine) { Kernel_routine &routine = *_kernel_routine; /* the 'kernel_routine' may install another kernel routine */ _kernel_routine = nullptr; routine.execute_in_kernel(); if (!_kernel_routine) _switch_to_user(); } 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 (!_kernel_routine && _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_cap = _env.ep().rpc_ep().manage(&resumer); resumer_cap.call(); _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(); } } /** * Alloc new watch handler for given path */ Vfs::Vfs_watch_handle *alloc_watch_handle(char const *path) { Vfs::Vfs_watch_handle *watch_handle { nullptr }; typedef Vfs::Directory_service::Watch_result Result; return _libc_env.vfs().watch(path, &watch_handle, _heap) == Result::WATCH_OK ? watch_handle : nullptr; } /**************************************** ** 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(); } } void register_kernel_routine(Kernel_routine &kernel_routine) { _kernel_routine = &kernel_routine; } }; void Libc::Kernel::reset_malloc_heap() { _malloc_ram.construct(_heap, _env.ram()); _cloned_heap_ranges.for_each([&] (Registered &r) { destroy(_heap, &r); }); Heap &raw_malloc_heap = *_malloc_heap; Genode::construct_at(&raw_malloc_heap, *_malloc_ram, _env.rm()); reinit_malloc(raw_malloc_heap); } void Libc::Kernel::_init_file_descriptors() { auto init_fd = [&] (Genode::Xml_node const &node, char const *attr, int libc_fd, unsigned flags) { if (!node.has_attribute(attr)) return; typedef Genode::String Path; Path const path = node.attribute_value(attr, Path()); struct stat out_stat { }; if (stat(path.string(), &out_stat) != 0) return; Libc::File_descriptor *fd = _vfs.open(path.string(), flags, libc_fd); if (fd->libc_fd != libc_fd) { Genode::error("could not allocate fd ",libc_fd," for ",path,", " "got fd ",fd->libc_fd); _vfs.close(fd); return; } /* * We need to manually register the path. Normally this is done * by '_open'. But we call the local 'open' function directly * because we want to explicitly specify the libc fd ID. */ if (fd->fd_path) Genode::warning("may leak former FD path memory"); { char *dst = (char *)_heap.alloc(path.length()); Genode::strncpy(dst, path.string(), path.length()); fd->fd_path = dst; } ::off_t const seek = node.attribute_value("seek", 0ULL); if (seek) _vfs.lseek(fd, seek, SEEK_SET); }; if (_vfs.root_dir_has_dirents()) { Xml_node const node = _libc_env.libc_config(); typedef Genode::String Path; if (node.has_attribute("cwd")) chdir(node.attribute_value("cwd", Path()).string()); init_fd(node, "stdin", 0, O_RDONLY); init_fd(node, "stdout", 1, O_WRONLY); init_fd(node, "stderr", 2, O_WRONLY); node.for_each_sub_node("fd", [&] (Xml_node fd) { unsigned const id = fd.attribute_value("id", 0U); bool const rd = fd.attribute_value("readable", false); bool const wr = fd.attribute_value("writeable", false); unsigned const flags = rd ? (wr ? O_RDWR : O_RDONLY) : (wr ? O_WRONLY : 0); if (!fd.has_attribute("path")) warning("Invalid node, 'path' attribute is missing"); init_fd(fd, "path", id, flags); }); /* prevent use of IDs of stdin, stdout, and stderr for other files */ for (unsigned fd = 0; fd <= 2; fd++) Libc::file_descriptor_allocator()->preserve(fd); } } void Libc::Kernel::_clone_state_from_parent() { struct Range { void *at; size_t size; }; auto range_attr = [&] (Xml_node node) { return Range { .at = (void *)node.attribute_value("at", 0UL), .size = node.attribute_value("size", 0UL) }; }; /* * Allocate local memory for the backing store of the application heap, * mirrored from the parent. * * This step must precede the creation of the 'Clone_connection' because * the shared-memory buffer of the clone session may otherwise potentially * interfere with such a heap region. */ _libc_env.libc_config().for_each_sub_node("heap", [&] (Xml_node node) { Range const range = range_attr(node); new (_heap) Registered(_cloned_heap_ranges, _env.ram(), _env.rm(), range.at, range.size); }); Clone_connection clone_connection(_env); /* fetch heap content */ _cloned_heap_ranges.for_each([&] (Cloned_malloc_heap_range &heap_range) { heap_range.import_content(clone_connection); }); /* fetch user contex of the parent's application */ clone_connection.memory_content(&_user_context, sizeof(_user_context)); _valid_user_context = true; _libc_env.libc_config().for_each_sub_node([&] (Xml_node node) { auto copy_from_parent = [&] (Range range) { clone_connection.memory_content(range.at, range.size); }; /* clone application stack */ if (node.type() == "stack") copy_from_parent(range_attr(node)); /* clone RW segment of a shared library or the binary */ if (node.type() == "rw") { typedef String<64> Name; Name const name = node.attribute_value("name", Name()); /* * The blacklisted segments are initialized via the * regular startup of the child. */ bool const blacklisted = (name == "ld.lib.so") || (name == "libc.lib.so") || (name == "libm.lib.so") || (name == "posix.lib.so") || (strcmp(name.string(), "vfs", 3) == 0); if (!blacklisted) copy_from_parent(range_attr(node)); } }); /* import application-heap state from parent */ clone_connection.object_content(_malloc_heap); init_malloc_cloned(clone_connection); } /** * 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(); } Vfs::Vfs_watch_handle *Libc::watch(char const *path) { return kernel->alloc_watch_handle(path); } 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; } void Libc::register_kernel_routine(Kernel_routine &kernel_routine) { kernel->register_kernel_routine(kernel_routine); } 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(env.ram(), env.rm()); /* pass Genode::Env to libc subsystems that depend on it */ Libc::init_fd_alloc(heap); Libc::init_mem_alloc(env); Libc::init_dl(env); Libc::sysctl_init(env); Libc::init_pthread_support(env); kernel = unmanaged_singleton(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); }