diff --git a/repos/libports/include/libc-plugin/fd_alloc.h b/repos/libports/include/libc-plugin/fd_alloc.h index c6304c7f5..b768c0666 100644 --- a/repos/libports/include/libc-plugin/fd_alloc.h +++ b/repos/libports/include/libc-plugin/fd_alloc.h @@ -20,6 +20,7 @@ #include #include #include +#include /* libc includes */ #include @@ -115,6 +116,8 @@ namespace Libc { void preserve(int libc_fd); File_descriptor *find_by_libc_fd(int libc_fd); + + void generate_info(Genode::Xml_generator &); }; diff --git a/repos/libports/lib/mk/libc.mk b/repos/libports/lib/mk/libc.mk index 515caaf29..e4a064adb 100644 --- a/repos/libports/lib/mk/libc.mk +++ b/repos/libports/lib/mk/libc.mk @@ -18,7 +18,7 @@ SRC_CC = atexit.cc dummies.cc rlimit.cc sysctl.cc \ pread_pwrite.cc readv_writev.cc poll.cc \ vfs_plugin.cc dynamic_linker.cc signal.cc \ socket_operations.cc task.cc socket_fs_plugin.cc syscall.cc \ - getpwent.cc getrandom.cc + getpwent.cc getrandom.cc fork.cc # # Pthreads diff --git a/repos/libports/src/lib/libc/clone_session.h b/repos/libports/src/lib/libc/clone_session.h new file mode 100644 index 000000000..5b544910d --- /dev/null +++ b/repos/libports/src/lib/libc/clone_session.h @@ -0,0 +1,95 @@ +/* + * \brief Session interface for fetching the content of cloned libc process + * \author Norman Feske + * \date 2019-08-14 + */ + +/* + * Copyright (C) 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 _CLONE_SESSION_H_ +#define _CLONE_SESSION_H_ + +/* Genode includes */ +#include +#include +#include +#include + +/* libc includes */ +#include + +namespace Libc { + struct Clone_session; + struct Clone_connection; +} + + +struct Libc::Clone_session : Genode::Session +{ + static const char *service_name() { return "Clone"; } + + enum { BUFFER_SIZE = 512*1024UL, + RAM_QUOTA = BUFFER_SIZE + 4096, + CAP_QUOTA = 2 }; + + struct Memory_range + { + void *start; + size_t size; + }; + + GENODE_RPC(Rpc_dataspace, Genode::Dataspace_capability, dataspace); + GENODE_RPC(Rpc_memory_content, void, memory_content, Memory_range); + + GENODE_RPC_INTERFACE(Rpc_dataspace, Rpc_memory_content); +}; + + +struct Libc::Clone_connection : Genode::Connection, + Genode::Rpc_client +{ + Genode::Attached_dataspace const _buffer; + + Clone_connection(Genode::Env &env) + : + Genode::Connection(env, + session(env.parent(), + "ram_quota=%ld, cap_quota=%ld", + RAM_QUOTA, CAP_QUOTA)), + Genode::Rpc_client(cap()), + _buffer(env.rm(), call()) + { } + + /** + * Obtain memory content from cloned address space + */ + void memory_content(void *dst, size_t const len) + { + size_t remaining = len; + char *ptr = (char *)dst; + + while (remaining > 0) { + + size_t const chunk_len = Genode::min((size_t)BUFFER_SIZE, remaining); + + /* instruct server to fill shared buffer */ + call(Memory_range{ ptr, chunk_len }); + + /* copy-out data from shared buffer to local address space */ + ::memcpy(ptr, _buffer.local_addr(), chunk_len); + + remaining -= chunk_len; + ptr += chunk_len; + } + } + + template + void object_content(OBJ &obj) { memory_content(&obj, sizeof(obj)); } +}; + +#endif /* _CLONE_SESSION_H_ */ diff --git a/repos/libports/src/lib/libc/dummies.cc b/repos/libports/src/lib/libc/dummies.cc index 089c36196..b63dd3d40 100644 --- a/repos/libports/src/lib/libc/dummies.cc +++ b/repos/libports/src/lib/libc/dummies.cc @@ -110,7 +110,6 @@ DUMMY(int, -1, getifaddrs, (struct ifaddrs **)) DUMMY(void, , freeifaddrs, (struct ifaddrs *ifp)) DUMMY(char *, 0, _getlogin, (void)) DUMMY(int , -1, getnameinfo, (const sockaddr *, socklen_t, char *, size_t, char *, size_t, int)) -DUMMY_SILENT(pid_t , -1, getpid, (void)) DUMMY(struct servent *, 0, getservbyname, (const char *, const char *)) DUMMY(int , -1, getsid, (pid_t)) DUMMY(pid_t , -1, getppid, (void)) @@ -154,7 +153,6 @@ DUMMY(int , 0, utimes, (const char *, const timeval *)) DUMMY(int, -1, semget, (key_t, int, int)) DUMMY(int, -1, semop, (key_t, int, int)) __SYS_DUMMY(int, -1, aio_suspend, (const struct aiocb * const[], int, const struct timespec *)); -__SYS_DUMMY(pid_t , -1, fork, (void)) __SYS_DUMMY(int , -1, getfsstat, (struct statfs *, long, int)) __SYS_DUMMY(int, -1, kevent, (int, const struct kevent*, int, struct kevent *, int, const struct timespec*)); __SYS_DUMMY(void , , map_stacks_exec, (void)); diff --git a/repos/libports/src/lib/libc/fd_alloc.cc b/repos/libports/src/lib/libc/fd_alloc.cc index a24f6288e..480486d35 100644 --- a/repos/libports/src/lib/libc/fd_alloc.cc +++ b/repos/libports/src/lib/libc/fd_alloc.cc @@ -21,6 +21,10 @@ /* libc plugin interface */ #include +/* libc includes */ +#include +#include + using namespace Libc; using namespace Genode; @@ -96,6 +100,37 @@ File_descriptor *File_descriptor_allocator::find_by_libc_fd(int libc_fd) } +void File_descriptor_allocator::generate_info(Xml_generator &xml) +{ + Lock::Guard guard(_lock); + + _id_space.for_each([&] (File_descriptor &fd) { + xml.node("fd", [&] () { + + xml.attribute("id", fd.libc_fd); + + if (fd.fd_path) + xml.attribute("path", fd.fd_path); + + if (fd.cloexec) + xml.attribute("cloexec", "yes"); + + if (((fd.flags & O_ACCMODE) != O_WRONLY)) + xml.attribute("readable", "yes"); + + if (((fd.flags & O_ACCMODE) != O_RDONLY)) + xml.attribute("writeable", "yes"); + + if (fd.plugin) { + ::off_t const seek = fd.plugin->lseek(&fd, 0, SEEK_CUR); + if (seek) + xml.attribute("seek", seek); + } + }); + }); +} + + /******************** ** Libc functions ** ********************/ diff --git a/repos/libports/src/lib/libc/fork.cc b/repos/libports/src/lib/libc/fork.cc new file mode 100644 index 000000000..8eb792111 --- /dev/null +++ b/repos/libports/src/lib/libc/fork.cc @@ -0,0 +1,713 @@ +/* + * \brief Libc fork mechanism + * \author Norman Feske + * \date 2019-08-13 + */ + +/* + * Copyright (C) 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 +#include +#include +#include +#include + +/* libc-internal includes */ +#include +#include +#include + +using namespace Genode; + + +static pid_t fork_result; + +static Env *_env_ptr; +static Allocator *_alloc_ptr; +static Heap *_malloc_heap_ptr; +static void *_user_stack_base_ptr; +static size_t _user_stack_size; +static int _pid; +static int _pid_cnt; + +static Libc::Config_accessor const *_config_accessor_ptr; + + +void Libc::init_fork(Env &env, Config_accessor const &config_accessor, + Allocator &alloc, Heap &malloc_heap, pid_t pid) +{ + _env_ptr = &env; + _alloc_ptr = &alloc; + _malloc_heap_ptr = &malloc_heap; + _config_accessor_ptr = &config_accessor; + _pid = pid; +} + + +namespace Libc { + struct Child_config; + struct Parent_services; + struct Local_rom_service; + struct Local_rom_services; + struct Local_clone_service; + struct Forked_child_policy; + struct Forked_child; + + struct Child_ready : Interface + { + virtual void child_ready() = 0; + }; +} + + +struct Libc::Child_config +{ + Constructible _ds { }; + + Env &_env; + + pid_t const _pid; + + void _generate(Xml_generator &xml, Xml_node config); + + Child_config(Env &env, Config_accessor const &config_accessor, pid_t pid) + : + _env(env), _pid(pid) + { + Xml_node const config = config_accessor.config(); + + size_t buffer_size = 4096; + + retry( + + [&] () { + _ds.construct(env.ram(), env.rm(), buffer_size); + + Xml_generator + xml(_ds->local_addr(), buffer_size, "config", [&] () { + _generate(xml, config); }); + }, + + [&] () { buffer_size += 4096; } + ); + } + + Rom_dataspace_capability ds_cap() const + { + Dataspace_capability cap = _ds->cap(); + return static_cap_cast(cap); + } +}; + + +void Libc::Child_config::_generate(Xml_generator &xml, Xml_node config) +{ + typedef String<30> Addr; + + /* + * Disarm the dynamic linker's sanity check for the + * execution of static constructors for the forked child + * because those constructors were executed by the parent + * already. + */ + xml.attribute("ld_check_ctors", "no"); + + xml.node("libc", [&] () { + + xml.attribute("pid", _pid); + + typedef String Path; + config.with_sub_node("libc", [&] (Xml_node node) { + if (node.has_attribute("rtc")) + xml.attribute("rtc", node.attribute_value("rtc", Path())); }); + + { + char buf[Vfs::MAX_PATH_LEN] { }; + if (getcwd(buf, sizeof(buf))) + xml.attribute("cwd", Path(Cstring(buf))); + } + + file_descriptor_allocator()->generate_info(xml); + + auto gen_range_attr = [&] (auto at, auto size) + { + xml.attribute("at", Addr(at)); + xml.attribute("size", Addr(size)); + }; + + xml.attribute("cloned", "yes"); + xml.node("stack", [&] () { + gen_range_attr(_user_stack_base_ptr, _user_stack_size); }); + + typedef Dynamic_linker::Object_info Info; + Dynamic_linker::for_each_loaded_object(_env, [&] (Info const &info) { + xml.node("rw", [&] () { + xml.attribute("name", info.name); + gen_range_attr(info.rw_start, info.rw_size); }); }); + + _malloc_heap_ptr->for_each_region([&] (void *start, size_t size) { + xml.node("heap", [&] () { + gen_range_attr(start, size); }); }); + }); + + xml.append("\n"); + + /* copy non-libc config as is */ + config.for_each_sub_node([&] (Xml_node node) { + if (node.type() != "libc") { + node.with_raw_node([&] (char const *start, size_t len) { + xml.append("\t"); + xml.append(start, len); + }); + xml.append("\n"); + } + }); +} + + +class Libc::Parent_services : Noncopyable +{ + private: + + Env &_env; + Allocator &_alloc; + + typedef Registered Registered_service; + + Registry _services { }; + + public: + + Parent_services(Env &env, Allocator &alloc) : _env(env), _alloc(alloc) { } + + ~Parent_services() + { + _services.for_each([&] (Registered_service &service) { + destroy(_alloc, &service); }); + } + + /** + * Return parent service matching the specified name + */ + Service &matching_service(Service::Name const &name) + { + Service *service = nullptr; + _services.for_each([&] (Parent_service &s) { + if (!service && name == s.name()) + service = &s; }); + + if (service) + return *service; + + /* expand list of parent services on demand */ + return *new (_alloc) Registered_service(_services, _env, name); + } +}; + + +struct Libc::Local_rom_service : Noncopyable +{ + typedef Session_label Rom_name; + + struct Session : Session_object + { + Rom_dataspace_capability _ds; + + static Session::Resources _resources() + { + return { .ram_quota = { 0 }, + .cap_quota = { Rom_session::CAP_QUOTA } }; + } + + Session(Entrypoint &ep, Rom_name const &name, Rom_dataspace_capability ds) + : + Session_object(ep.rpc_ep(), _resources(), name, Session::Diag()), + _ds(ds) + { } + + Rom_dataspace_capability dataspace() override { return _ds; } + + void sigh(Signal_context_capability) override { } + + } _session; + + typedef Local_service Service; + + Service::Single_session_factory _factory { _session }; + + Service service { _factory }; + + bool matches(Session_label const &label) const + { + return label.last_element() == _session.label(); + } + + Local_rom_service(Entrypoint &ep, Rom_name const &name, + Rom_dataspace_capability ds) + : _session(ep, name, ds) { } + + virtual ~Local_rom_service() { } +}; + + +struct Libc::Local_rom_services : Noncopyable +{ + Allocator &_alloc; + + typedef Registered Registered_service; + + Registry _services { }; + + Local_rom_services(Env &env, Entrypoint &fork_ep, Allocator &alloc) + : + _alloc(alloc) + { + typedef Dynamic_linker::Object_info Info; + Dynamic_linker::for_each_loaded_object(env, [&] (Info const &info) { + new (alloc) + Registered_service(_services, fork_ep, info.name, info.ds_cap); }); + } + + ~Local_rom_services() + { + _services.for_each([&] (Registered_service &service) { + destroy(_alloc, &service); }); + } + + /* + * \throw Service_denied + */ + Service &matching_service(Service::Name const &name, Session_label const &label) + { + if (name != Rom_session::service_name()) + throw Service_denied(); + + Service *service_ptr = nullptr; + + _services.for_each([&] (Local_rom_service &service) { + if (service.matches(label)) + service_ptr = &service.service; }); + + if (!service_ptr) + throw Service_denied(); + + return *service_ptr; + } +}; + + +struct Libc::Local_clone_service : Noncopyable +{ + struct Session : Session_object + { + Attached_ram_dataspace _ds; + + static Session::Resources _resources() + { + return { .ram_quota = { Clone_session::RAM_QUOTA }, + .cap_quota = { Clone_session::CAP_QUOTA } }; + } + + Session(Env &env, Entrypoint &ep) + : + Session_object(ep.rpc_ep(), _resources(), + "cloned", Session::Diag()), + _ds(env.ram(), env.rm(), Clone_session::BUFFER_SIZE) + { } + + Dataspace_capability dataspace() { return _ds.cap(); } + + void memory_content(Memory_range range) + { + ::memcpy(_ds.local_addr(), range.start, range.size); + } + + } _session; + + typedef Local_service Service; + + Child_ready &_child_ready; + + Io_signal_handler _child_ready_handler; + + void _handle_child_ready() + { + _child_ready.child_ready(); + + Libc::resume_all(); + } + + struct Factory : Local_service::Factory + { + Session &_session; + Signal_context_capability _started_sigh; + + Factory(Session &session, Signal_context_capability started_sigh) + : _session(session), _started_sigh(started_sigh) { } + + Session &create(Args const &, Affinity) override { return _session; } + + void upgrade(Session &, Args const &) override { } + + void destroy(Session &) override { Signal_transmitter(_started_sigh).submit(); } + + } _factory; + + Service service { _factory }; + + Local_clone_service(Env &env, Entrypoint &ep, Child_ready &child_ready) + : + _session(env, ep), _child_ready(child_ready), + _child_ready_handler(env.ep(), *this, &Local_clone_service::_handle_child_ready), + _factory(_session, _child_ready_handler) + { } +}; + + +struct Libc::Forked_child : Child_policy, Child_ready +{ + Env &_env; + + pid_t const _pid; + + enum class State { STARTING_UP, RUNNING, EXITED } _state { State::STARTING_UP }; + + int _exit_code = 0; + + Name const _name { _pid }; + + /* + * Signal handler triggered at the main entrypoint, waking up the libc + * suspend mechanism. + */ + Io_signal_handler _exit_handler { + _env.ep(), *this, &Forked_child::_handle_exit }; + + void _handle_exit() { Libc::resume_all(); } + + Libc::Child_config _child_config; + + Parent_services &_parent_services; + Local_rom_services &_local_rom_services; + Local_clone_service _local_clone_service; + Local_rom_service _config_rom_service; + + struct Wait_fork_ready : Libc::Kernel_routine + { + Libc::Forked_child const &child; + + Wait_fork_ready(Libc::Forked_child const &child) : child(child) { } + + void execute_in_kernel() override + { + /* keep executing this kernel routine until child is running */ + if (!child.running()) + register_kernel_routine(*this); + } + } wait_fork_ready { *this }; + + pid_t pid() const { return _pid; } + + bool running() const { return _state == State::RUNNING; } + + bool exited() const { return _state == State::EXITED; } + + int exit_code() const { return _exit_code; } + + + /*************************** + ** Child_ready interface ** + ***************************/ + + void child_ready() override { _state = State::RUNNING; } + + + /**************************** + ** Child_policy interface ** + ****************************/ + + Name name() const override { return _name; } + + Binary_name binary_name() const override { return "binary"; } + + Pd_session &ref_pd() override { return _env.pd(); } + Pd_session_capability ref_pd_cap() const override { return _env.pd_session_cap(); } + + void init(Pd_session &session, Pd_session_capability cap) override + { + session.ref_account(_env.pd_session_cap()); + + _env.pd().transfer_quota(cap, Ram_quota{2*1024*1024}); + _env.pd().transfer_quota(cap, Cap_quota{100}); + } + + Route resolve_session_request(Service::Name const &name, + Session_label const &label) override + { + Service *service_ptr = nullptr; + if (_state == State::STARTING_UP && name == Clone_session::service_name()) + service_ptr = &_local_clone_service.service; + + if (name == Rom_session::service_name()) { + + try { service_ptr = &_local_rom_services.matching_service(name, label); } + catch (...) { } + + if (!service_ptr && label.last_element() == "config") + service_ptr = &_config_rom_service.service; + } + + if (!service_ptr) + service_ptr = &_parent_services.matching_service(name); + + if (service_ptr) + return Route { .service = *service_ptr, + .label = label, + .diag = Session::Diag() }; + + throw Service_denied(); + } + + void resource_request(Parent::Resource_args const &args) override + { + Session::Resources resources = session_resources_from_args(args.string()); + + if (resources.ram_quota.value) + _env.pd().transfer_quota(_child.pd_session_cap(), resources.ram_quota); + + if (resources.cap_quota.value) + _env.pd().transfer_quota(_child.pd_session_cap(), resources.cap_quota); + + _child.notify_resource_avail(); + } + + void exit(int code) override + { + _exit_code = code; + _state = State::EXITED; + + Signal_transmitter(_exit_handler).submit(); + } + + Child _child; + + Forked_child(Env &env, + Entrypoint &fork_ep, + Allocator &alloc, + pid_t pid, + Config_accessor const &config_accessor, + Parent_services &parent_services, + Local_rom_services &local_rom_services) + : + _env(env), _pid(pid), + _child_config(env, config_accessor, pid), + _parent_services(parent_services), + _local_rom_services(local_rom_services), + _local_clone_service(env, fork_ep, *this), + _config_rom_service(fork_ep, "config", _child_config.ds_cap()), + _child(env.rm(), fork_ep.rpc_ep(), *this) + { } + + virtual ~Forked_child() { } +}; + + +/* initialized on first call of 'fork_kernel_routine' */ +typedef Registry > Forked_children; +static Forked_children *_forked_children_ptr; + + +static void fork_kernel_routine() +{ + fork_result = 0; + + if (!_env_ptr || !_alloc_ptr || !_config_accessor_ptr) { + error("missing call of 'init_fork'"); + abort(); + } + + Env &env = *_env_ptr; + Allocator &alloc = *_alloc_ptr; + + pid_t const child_pid = ++_pid_cnt; + + enum { STACK_SIZE = 1024*16 }; + static Entrypoint fork_ep(env, STACK_SIZE, "fork_ep", Affinity::Location()); + + static Libc::Parent_services parent_services(env, alloc); + + static Libc::Local_rom_services local_rom_services(env, fork_ep, alloc); + + static Forked_children forked_children { }; + _forked_children_ptr = &forked_children; + + Registered &child = *new (alloc) + Registered(forked_children, env, fork_ep, alloc, + child_pid, *_config_accessor_ptr, + parent_services, local_rom_services); + + fork_result = child_pid; + + register_kernel_routine(child.wait_fork_ready); +} + + +/************ + ** getpid ** + ************/ + +/* + * We provide weak symbols to allow 'libc_noux' to override them. + */ + +extern "C" pid_t __sys_fork(void) __attribute__((weak)); +extern "C" pid_t __sys_fork(void) +{ + fork_result = -1; + + /* obtain current stack info, which might have changed since the startup */ + Thread::Stack_info const mystack = Thread::mystack(); + _user_stack_base_ptr = (void *)mystack.base; + _user_stack_size = mystack.top - mystack.base; + + struct Fork_kernel_routine : Libc::Kernel_routine + { + void execute_in_kernel() override { fork_kernel_routine(); } + + } kernel_routine { }; + + Libc::register_kernel_routine(kernel_routine); + + struct Suspend_functor_impl : Libc::Suspend_functor + { + bool suspend() override { return false; } + + } suspend_functor { }; + + Libc::suspend(suspend_functor, 0); + + return fork_result; +} + +pid_t fork(void) __attribute__((weak, alias("__sys_fork"))); + + +/************ + ** getpid ** + ************/ + +extern "C" pid_t __sys_getpid(void) __attribute__((weak)); +extern "C" pid_t __sys_getpid(void) { return _pid; } + +pid_t getpid(void) __attribute__((weak, alias("__sys_getpid"))); + + +/*********** + ** wait4 ** + ***********/ + +namespace Libc { struct Wait4_suspend_functor; } + +struct Libc::Wait4_suspend_functor : Suspend_functor +{ + Forked_children &_children; + + pid_t const _pid; + + Wait4_suspend_functor(pid_t pid, Forked_children &children) + : _children(children), _pid(pid) { } + + template + bool with_exited_child(FN const &fn) + { + Registered *child_ptr = nullptr; + + _children.for_each([&] (Registered &child) { + + if (child_ptr || !child.exited()) + return; + + if (_pid == child.pid() || _pid == -1) + child_ptr = &child; + }); + + if (!child_ptr) + return false; + + fn(*child_ptr); + return true; + } + + bool suspend() override + { + bool const any_child_exited = + with_exited_child([] (Forked_child const &) { }); + + return !any_child_exited; + } +}; + + +extern "C" pid_t __sys_wait4(pid_t, int *, int, rusage *) __attribute__((weak)); +extern "C" pid_t __sys_wait4(pid_t pid, int *status, int options, rusage *rusage) +{ + pid_t result = -1; + int exit_code = 0; /* code and status */ + + using namespace Genode; + using namespace Libc; + + if (!_forked_children_ptr) { + errno = ECHILD; + return -1; + } + + Wait4_suspend_functor suspend_functor { pid, *_forked_children_ptr }; + + for (;;) { + + suspend_functor.with_exited_child([&] (Registered &child) { + result = child.pid(); + exit_code = child.exit_code(); + destroy(*_alloc_ptr, &child); + }); + + if (result >= 0 || (options & WNOHANG)) + break; + + Libc::suspend(suspend_functor, 0); + } + + /* + * The libc expects status information in bits 0..6 and the exit value + * in bits 8..15 (according to 'wait.h'). + * + * The status bits carry the information about the terminating signal. + */ + if (status) + *status = ((exit_code >> 8) & 0x7f) | ((exit_code & 0xff) << 8); + + return result; +} + +extern "C" pid_t wait4(pid_t, int *, int, rusage *) __attribute__((weak, alias("__sys_wait4"))); + diff --git a/repos/libports/src/lib/libc/libc_init.h b/repos/libports/src/lib/libc/libc_init.h index c0dfa1fd7..e0fe830dc 100644 --- a/repos/libports/src/lib/libc/libc_init.h +++ b/repos/libports/src/lib/libc/libc_init.h @@ -16,9 +16,12 @@ /* Genode includes */ #include -#include +#include #include +/* libc includes */ +#include /* for 'jmp_buf' type */ + namespace Libc { /** @@ -41,16 +44,30 @@ namespace Libc { */ void libc_config_init(Genode::Xml_node node); + struct Clone_connection; + /** * Malloc allocator */ void init_malloc(Genode::Allocator &heap); + void init_malloc_cloned(Clone_connection &); /** * Allow thread.cc to access the 'Genode::Env' (needed for the * implementation of condition variables with timeout) */ void init_pthread_support(Genode::Env &env); + + struct Config_accessor : Genode::Interface + { + virtual Genode::Xml_node config() const = 0; + }; + + /** + * Fork mechanism + */ + void init_fork(Genode::Env &, Config_accessor const &, + Genode::Allocator &heap, Genode::Heap &malloc_heap, int pid); } #endif /* _LIBC_INIT_H_ */ diff --git a/repos/libports/src/lib/libc/malloc.cc b/repos/libports/src/lib/libc/malloc.cc index 5ad21ed51..864181bc4 100644 --- a/repos/libports/src/lib/libc/malloc.cc +++ b/repos/libports/src/lib/libc/malloc.cc @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include @@ -29,45 +29,51 @@ extern "C" { /* libc-internal includes */ #include "libc_init.h" #include +#include -namespace Genode { +namespace Libc { + class Slab_alloc; + class Malloc; - class Slab_alloc : public Slab - { - private: - - size_t const _object_size; - - size_t _calculate_block_size(size_t object_size) - { - size_t block_size = 16*object_size; - return align_addr(block_size, 12); - } - - public: - - Slab_alloc(size_t object_size, Allocator *backing_store) - : - Slab(object_size, _calculate_block_size(object_size), 0, backing_store), - _object_size(object_size) - { } - - void *alloc() - { - void *result; - return (Slab::alloc(_object_size, &result) ? result : 0); - } - - void free(void *ptr) { Slab::free(ptr, _object_size); } - }; + using namespace Genode; } +class Libc::Slab_alloc : public Slab +{ + private: + + size_t const _object_size; + + size_t _calculate_block_size(size_t object_size) + { + size_t block_size = 16*object_size; + return align_addr(block_size, 12); + } + + public: + + Slab_alloc(size_t object_size, Allocator &backing_store) + : + Slab(object_size, _calculate_block_size(object_size), 0, &backing_store), + _object_size(object_size) + { } + + void *alloc() + { + void *result; + return (Slab::alloc(_object_size, &result) ? result : 0); + } + + void free(void *ptr) { Slab::free(ptr, _object_size); } +}; + + /** * Allocator that uses slabs for small objects sizes */ -class Malloc +class Libc::Malloc { private: @@ -112,9 +118,11 @@ class Malloc */ static constexpr size_t _room() { return sizeof(Metadata) + 15; } - Genode::Allocator &_backing_store; /* back-end allocator */ - Genode::Slab_alloc *_allocator[NUM_SLABS]; /* slab allocators */ - Genode::Lock _lock; + Allocator &_backing_store; /* back-end allocator */ + + Constructible _slabs[NUM_SLABS]; /* slab allocators */ + + Lock _lock; unsigned _slab_log2(size_t size) const { @@ -133,15 +141,13 @@ class Malloc public: - Malloc(Genode::Allocator &backing_store) : _backing_store(backing_store) + Malloc(Allocator &backing_store) : _backing_store(backing_store) { - for (unsigned i = SLAB_START; i <= SLAB_STOP; i++) { - _allocator[i - SLAB_START] = - new (backing_store) Genode::Slab_alloc(1U << i, &backing_store); - } + for (unsigned i = SLAB_START; i <= SLAB_STOP; i++) + _slabs[i - SLAB_START].construct(1U << i, backing_store); } - ~Malloc() { Genode::warning(__func__, " unexpectedly called"); } + ~Malloc() { warning(__func__, " unexpectedly called"); } /** * Allocator interface @@ -149,7 +155,7 @@ class Malloc void * alloc(size_t size) { - Genode::Lock::Guard lock_guard(_lock); + Lock::Guard lock_guard(_lock); size_t const real_size = size + _room(); unsigned const msb = _slab_log2(real_size); @@ -160,7 +166,7 @@ class Malloc if (msb > SLAB_STOP) _backing_store.alloc(real_size, &alloc_addr); else - alloc_addr = _allocator[msb - SLAB_START]->alloc(); + alloc_addr = _slabs[msb - SLAB_START]->alloc(); if (!alloc_addr) return nullptr; @@ -189,7 +195,7 @@ class Malloc if (new_addr) { /* copy content from old block into new block */ - memcpy(new_addr, ptr, old_real_size - _room()); + ::memcpy(new_addr, ptr, old_real_size - _room()); /* free old block */ free(ptr); @@ -200,7 +206,7 @@ class Malloc void free(void *ptr) { - Genode::Lock::Guard lock_guard(_lock); + Lock::Guard lock_guard(_lock); Metadata *md = (Metadata *)ptr - 1; @@ -212,13 +218,13 @@ class Malloc if (msb > SLAB_STOP) { _backing_store.free(alloc_addr, real_size); } else { - _allocator[msb - SLAB_START]->free(alloc_addr); + _slabs[msb - SLAB_START]->free(alloc_addr); } } }; -static Malloc *mallocator; +static Libc::Malloc *mallocator; extern "C" void *malloc(size_t size) @@ -255,7 +261,26 @@ extern "C" void *realloc(void *ptr, size_t size) } +static Genode::Constructible &constructible_malloc() +{ + return *unmanaged_singleton >(); +} + + void Libc::init_malloc(Genode::Allocator &heap) { - mallocator = unmanaged_singleton(heap); + + Genode::Constructible &_malloc = constructible_malloc(); + + _malloc.construct(heap); + + mallocator = _malloc.operator->(); +} + + +void Libc::init_malloc_cloned(Clone_connection &clone_connection) +{ + clone_connection.object_content(constructible_malloc()); + + mallocator = constructible_malloc().operator->(); } diff --git a/repos/libports/src/lib/libc/signal.cc b/repos/libports/src/lib/libc/signal.cc index c8e72f406..3b25fa8a9 100644 --- a/repos/libports/src/lib/libc/signal.cc +++ b/repos/libports/src/lib/libc/signal.cc @@ -63,25 +63,6 @@ extern "C" int __i386_libc_sigprocmask(int how, const sigset_t *set, sigset_t *o } -extern "C" __attribute__((weak)) -pid_t wait4(pid_t, int *, int, struct rusage *) -{ - Genode::warning(__func__, " not implemented"); - errno = ENOSYS; - return -1; -} - - -extern "C" -pid_t __sys_wait4(pid_t wpid, int *status, int options, struct rusage *rusage) { - return wait4(wpid, status, options, rusage); } - - -extern "C" -pid_t _wait4(pid_t wpid, int *status, int options, struct rusage *rusage) { - return wait4(wpid, status, options, rusage); } - - extern "C" pid_t wait(int *istat) { return wait4(WAIT_ANY, istat, 0, NULL); } diff --git a/repos/libports/src/lib/libc/task.cc b/repos/libports/src/lib/libc/task.cc index d366b44ab..b39982f36 100644 --- a/repos/libports/src/lib/libc/task.cc +++ b/repos/libports/src/lib/libc/task.cc @@ -31,6 +31,7 @@ /* libc-internal includes */ #include #include +#include #include "vfs_plugin.h" #include "libc_init.h" #include "task.h" @@ -39,6 +40,7 @@ extern char **environ; namespace Libc { class Env_implementation; + class Cloned_malloc_heap_range; class Kernel; class Pthreads; class Timer; @@ -50,7 +52,7 @@ namespace Libc { } -class Libc::Env_implementation : public Libc::Env +class Libc::Env_implementation : public Libc::Env, public Config_accessor { private: @@ -104,6 +106,13 @@ class Libc::Env_implementation : public Libc::Env return _libc_config(); } + /************************************* + ** Libc::Config_accessor interface ** + *************************************/ + + Xml_node config() const override { return _config.xml(); } + + /*************************** ** Genode::Env interface ** ***************************/ @@ -328,6 +337,40 @@ 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); + } +}; + + /** * Libc "kernel" * @@ -343,12 +386,19 @@ struct Libc::Kernel final : Vfs::Io_response_handler, { private: - Genode::Env &_env; - Genode::Allocator &_heap; + Genode::Env &_env; + Genode::Allocator &_heap; + + Genode::Constructible _malloc_heap { }; + + Genode::Registry > _cloned_heap_ranges { }; 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); + Genode::Reconstructible> _resume_main_handler { _env.ep(), *this, &Kernel::_resume_main }; @@ -364,9 +414,21 @@ struct Libc::Kernel final : Vfs::Io_response_handler, 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(), - Component::stack_size()) }; + _user_stack_size()) }; void (*_original_suspended_callback)() = nullptr; @@ -384,6 +446,8 @@ struct Libc::Kernel final : Vfs::Io_response_handler, 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 @@ -523,7 +587,7 @@ struct Libc::Kernel final : Vfs::Io_response_handler, exit(1); } - if (!check.suspend()) + if (!check.suspend() && !_kernel_routine) return 0; if (timeout_ms > 0) @@ -560,13 +624,26 @@ struct Libc::Kernel final : Vfs::Io_response_handler, void _init_file_descriptors(); + void _clone_state_from_parent(); + public: Kernel(Genode::Env &env, Genode::Allocator &heap) - : _env(env), _heap(heap) + : + _env(env), _heap(heap) { _env.ep().register_io_progress_handler(*this); + if (_cloned) { + _clone_state_from_parent(); + + } else { + _malloc_heap.construct(_env.ram(), _env.rm()); + init_malloc(*_malloc_heap); + } + + Libc::init_fork(_env, _libc_env, _heap, *_malloc_heap, _pid); + _init_file_descriptors(); } @@ -593,8 +670,13 @@ struct Libc::Kernel final : Vfs::Io_response_handler, /* 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); + + if (_cloned) { + _switch_to_user(); + } else { + _state = USER; + call_func(_user_stack, (void *)_user_entry, (void *)this); + } /* never reached */ } @@ -602,6 +684,17 @@ struct Libc::Kernel final : Vfs::Io_response_handler, /* _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()) ; @@ -610,7 +703,7 @@ struct Libc::Kernel final : Vfs::Io_response_handler, _env.ep().wait_and_dispatch_one_io_signal(); } - if (_resume_main_once && !_setjmp(_kernel_context)) + if (!_kernel_routine && _resume_main_once && !_setjmp(_kernel_context)) _switch_to_user(); } @@ -819,6 +912,11 @@ struct Libc::Kernel final : Vfs::Io_response_handler, Kernel::resume_all(); } } + + void register_kernel_routine(Kernel_routine &kernel_routine) + { + _kernel_routine = &kernel_routine; + } }; @@ -897,6 +995,79 @@ void Libc::Kernel::_init_file_descriptors() } +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 * @@ -1015,6 +1186,12 @@ void Libc::execute_in_application_context(Libc::Application_code &app_code) } +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(); @@ -1038,7 +1215,6 @@ void Component::construct(Genode::Env &env) *unmanaged_singleton(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); diff --git a/repos/libports/src/lib/libc/task.h b/repos/libports/src/lib/libc/task.h index c90bb1724..1df9512c3 100644 --- a/repos/libports/src/lib/libc/task.h +++ b/repos/libports/src/lib/libc/task.h @@ -81,6 +81,23 @@ namespace Libc { */ void schedule_select(Select_handler_base *); + struct Kernel_routine : Genode::Interface + { + virtual void execute_in_kernel() = 0; + }; + + /** + * Register routine to be called once on the next libc-kernel activation + * + * The specified routine is executed only once. For a repeated execution, + * the routine must call 'register_kernel_routine' with itself as + * argument. + * + * This mechanism is used by 'fork' to implement the blocking for the + * startup of a new child and for 'wait4'. + */ + void register_kernel_routine(Kernel_routine &); + /** * Access libc configuration Xml_node. */