/* * \brief Libc kernel for main and pthreads user contexts * \author Christian Helmuth * \author Emery Hemingway * \author Norman Feske * \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. */ #ifndef _LIBC__INTERNAL__KERNEL_H_ #define _LIBC__INTERNAL__KERNEL_H_ /* base-internal includes */ #include /* libc includes */ #include /* libc-internal includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Libc { class Kernel; } /** * 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, Entrypoint::Io_progress_handler, Reset_malloc_heap, Resume, Suspend, Select, Kernel_routine_scheduler, Current_time { private: /** * Pointer to singleton instance */ static Kernel *_kernel_ptr; Genode::Env &_env; /** * Allocator for libc-internal data * * Not mirrored to forked processes. Preserved across 'execve' calls. */ Allocator &_heap; /** * Allocator for application-owned data * * Mirrored to forked processes. Not preserved across 'execve' calls. */ Reconstructible _malloc_ram { _heap, _env.ram() }; Constructible _malloc_heap { }; Registry > _cloned_heap_ranges { }; /** * Reset_malloc_heap interface used by execve */ void reset_malloc_heap() override; Env_implementation _libc_env { _env, _heap }; Vfs_plugin _vfs { _libc_env, _heap, *this }; bool const _cloned = _libc_env.libc_config().attribute_value("cloned", false); pid_t const _pid = _libc_env.libc_config().attribute_value("pid", 0U); typedef String Config_attr; Config_attr const _rtc_path = _libc_env.libc_config().attribute_value("rtc", Config_attr()); 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 }; Thread &_myself { *Thread::myself() }; addr_t _kernel_stack = Thread::mystack().top; size_t _user_stack_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; } Kernel_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(uint64_t timeout_ms) { _construct_timeout_once(); _timeout->start(timeout_ms); } 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 }; Pthread_pool _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 == 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) error("switching to invalid user context"); _resume_main_once = false; _state = USER; _longjmp(_user_context, 1); } uint64_t _suspend_main(Suspend_functor &check, 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, Allocator &heap); ~Kernel() { 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) { 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 interface */ void resume_all() override { if (_app_returned) { if (_scheduled_select_handler) _scheduled_select_handler->dispatch_select(); } else { if (_main_context()) _resume_main(); else Signal_transmitter(*_resume_main_handler).submit(); } _pthreads.resume_all(); } /** * Suspend interface */ uint64_t suspend(Suspend_functor &check, uint64_t timeout_ms) override { if (timeout_ms > 0 && timeout_ms > _timer_accessor.timer().max_timeout()) { 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; } } /** * Current_time interface */ Duration current_time() override { return _timer_accessor.timer().curr_time(); } /** * Called from the main context (by fork) */ void schedule_suspend(void(*original_suspended_callback) ()); /** * Select interface */ void schedule_select(Select_handler_base &h) override { _scheduled_select_handler = &h; } /** * Select interface */ void deschedule_select() override { _scheduled_select_handler = nullptr; } /** * 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; /** * Kernel_routine_scheduler interface */ void register_kernel_routine(Kernel_routine &kernel_routine) override { _kernel_routine = &kernel_routine; } /******************************** ** Access to kernel singleton ** ********************************/ struct Kernel_called_prior_initialization : Exception { }; static Kernel &kernel() { if (!_kernel_ptr) throw Kernel_called_prior_initialization(); return *_kernel_ptr; } }; #endif /* _LIBC__INTERNAL__KERNEL_H_ */