From 09bf68e8ade48f1a1f7267f51ccce55485f374a1 Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Tue, 12 Jun 2018 11:18:30 +0200 Subject: [PATCH] Cached_fs_rom: serve static ROM sessions from a cache This component is contrasted with the fs_rom server that serves independent dataspaces to each client. Using a cache was not possible until the region map session supported the creation of read-only attachments. Test at run/read_only_rom. Ref #1633 Fix #2760 --- repos/os/recipes/src/cached_fs_rom/content.mk | 6 + repos/os/recipes/src/cached_fs_rom/hash | 1 + repos/os/recipes/src/cached_fs_rom/used_apis | 3 + repos/os/run/read_only_rom.run | 58 ++ repos/os/src/server/cached_fs_rom/main.cc | 524 ++++++++++++++++++ .../server/cached_fs_rom/session_requests.h | 120 ++++ repos/os/src/server/cached_fs_rom/target.mk | 3 + repos/os/src/test/immutable_rom/component.cc | 29 + repos/os/src/test/immutable_rom/target.mk | 3 + 9 files changed, 747 insertions(+) create mode 100644 repos/os/recipes/src/cached_fs_rom/content.mk create mode 100644 repos/os/recipes/src/cached_fs_rom/hash create mode 100644 repos/os/recipes/src/cached_fs_rom/used_apis create mode 100644 repos/os/run/read_only_rom.run create mode 100755 repos/os/src/server/cached_fs_rom/main.cc create mode 100644 repos/os/src/server/cached_fs_rom/session_requests.h create mode 100755 repos/os/src/server/cached_fs_rom/target.mk create mode 100644 repos/os/src/test/immutable_rom/component.cc create mode 100644 repos/os/src/test/immutable_rom/target.mk diff --git a/repos/os/recipes/src/cached_fs_rom/content.mk b/repos/os/recipes/src/cached_fs_rom/content.mk new file mode 100644 index 0000000000..f78a981ae2 --- /dev/null +++ b/repos/os/recipes/src/cached_fs_rom/content.mk @@ -0,0 +1,6 @@ +SRC_DIR = src/server/cached_fs_rom +include $(GENODE_DIR)/repos/base/recipes/src/content.inc + +content: $(GENODE_DIR)/repos/os/include/file_system + mkdir include + cp -r $< include/file_system diff --git a/repos/os/recipes/src/cached_fs_rom/hash b/repos/os/recipes/src/cached_fs_rom/hash new file mode 100644 index 0000000000..ff1addf6bd --- /dev/null +++ b/repos/os/recipes/src/cached_fs_rom/hash @@ -0,0 +1 @@ +2018-06-12 f3adf98ff3ad5da5d5dc9565431b844423fc4eb3 diff --git a/repos/os/recipes/src/cached_fs_rom/used_apis b/repos/os/recipes/src/cached_fs_rom/used_apis new file mode 100644 index 0000000000..b892d66b55 --- /dev/null +++ b/repos/os/recipes/src/cached_fs_rom/used_apis @@ -0,0 +1,3 @@ +base +file_system_session +os diff --git a/repos/os/run/read_only_rom.run b/repos/os/run/read_only_rom.run new file mode 100644 index 0000000000..964df7c8a5 --- /dev/null +++ b/repos/os/run/read_only_rom.run @@ -0,0 +1,58 @@ +build { + core init + server/cached_fs_rom + server/vfs + test/immutable_rom +} + +create_boot_directory + +install_config { + + + + + + + + + + + + + + + + + + DONT TOUCH + + + + + + + + + + + + + + + + + + +} + +build_boot_image { + core ld.lib.so init + cached_fs_rom + test-immutable_rom + vfs vfs.lib.so +} + +append qemu_args "-nographic " + +run_genode_until {.*pd='init -> test-immutable_rom'.*} 30 diff --git a/repos/os/src/server/cached_fs_rom/main.cc b/repos/os/src/server/cached_fs_rom/main.cc new file mode 100755 index 0000000000..55149f248f --- /dev/null +++ b/repos/os/src/server/cached_fs_rom/main.cc @@ -0,0 +1,524 @@ +/* + * \brief Component that caches files to be served as ROMs + * \author Emery Hemingway + * \date 2018-04-12 + */ + +/* + * Copyright (C) 2018 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 +#include + +/* local session-requests utility */ +#include "session_requests.h" + +/***************** + ** ROM service ** + *****************/ + +namespace Cached_fs_rom { + + using namespace Genode; + typedef Genode::Path Path; + typedef File_system::Session_client::Tx::Source Tx_source; + + class Rom_file; + typedef Genode::Id_space Rom_files; + + class Session_component; + typedef Genode::Id_space Sessions; + + struct Packet_handler; + struct Main; + +} + + +class Cached_fs_rom::Rom_file final +{ + private: + + Env &_env; + Rm_connection &_rm_connection; + File_system::Session &_fs; + + Rom_files &_roms; + Constructible _roms_elem { }; + + /** + * Name of requested file, interpreted at path into the file system + */ + Path const _file_path; + + /** + * Handle of associated file opened during read loop + */ + Constructible _file_handle { }; + + /** + * Size of file + */ + File_system::file_size_t _file_size = 0; + + /** + * Read offset of file handle + */ + File_system::seek_off_t _file_seek = 0; + + /** + * Dataspace to read into + */ + Attached_ram_dataspace _file_ds { _env.pd(), _env.rm(), (size_t)_file_size }; + + /** + * Read-only region map exposed as ROM module to the client + */ + Region_map_client _rm { _rm_connection.create(_file_ds.size()) }; + Region_map::Local_addr _rm_attachment { }; + Dataspace_capability _rm_ds { }; + + /** + * Packet space used by this session + */ + File_system::Packet_descriptor _raw_pkt { }; + + /** + * Reference count of ROM file, initialize to one and + * decrement after read completes. + */ + int _ref_count = 1; + + void _submit_next_packet() + { + using namespace File_system; + + Tx_source &source = *_fs.tx(); + + while (!source.ready_to_submit()) { + warning("FS packet queue congestion"); + _env.ep().wait_and_dispatch_one_io_signal(); + } + + File_system::Packet_descriptor + packet(_raw_pkt, *_file_handle, + File_system::Packet_descriptor::READ, + _raw_pkt.size(), _file_seek); + + source.submit_packet(packet); + } + + void _initiate_read() + { + using namespace File_system; + + Tx_source &source = *_fs.tx(); + + size_t chunk_size = min(_file_size, source.bulk_buffer_size()/2); + + /* loop until a packet space is allocated */ + while (true) { + try { _raw_pkt = source.alloc_packet(chunk_size); break; } + catch (File_system::Session::Tx::Source::Packet_alloc_failed) { + chunk_size = min(_file_size, source.bulk_buffer_size()/4); + _env.ep().wait_and_dispatch_one_io_signal(); + } + } + + _submit_next_packet(); + } + + public: + + /** + * Constructor + * + * \param fs file-system session to read the file from + * \param filename requested file name + * \param sig_rec signal receiver used to get notified about changes + * within the compound directory (in the case when + * the requested file could not be found at session- + * creation time) + */ + Rom_file(Env &env, + Rm_connection &rm, + File_system::Session &fs, + File_system::File_handle handle, + size_t size, + Rom_files &cache, + Path const &file_path) + : + _env(env), _rm_connection(rm), _fs(fs), _roms(cache), + _file_path(file_path), _file_size(size) + { + /* + * invert the file handle id to push the element to + * the opposite end of the space + */ + _roms_elem.construct(*this, _roms, Rom_files::Id{~handle.value}); + _file_handle.construct(handle); + + _initiate_read(); + } + + /** + * Destructor + */ + ~Rom_file() + { + if (_file_handle.constructed()) + _fs.close(*_file_handle); + _fs.tx()->release_packet(_raw_pkt); + + if (_rm_attachment) + _rm.detach(_rm_attachment); + } + + Path const &path() const { return _file_path; } + + bool completed() const { return _rm_ds.valid(); } + + void inc_ref() { ++_ref_count; } + void dec_ref() { --_ref_count; } + + bool unused() const { return (_ref_count < 1); } + + bool matches(File_system::Node_handle h) const { + return _file_handle.constructed() ? (*_file_handle == h) : false; } + + /** + * Return dataspace with content of file + */ + Rom_dataspace_capability dataspace() const { + return static_cap_cast(_rm_ds); } + + /** + * Called from the signal handler. + */ + void process_packet(File_system::Packet_descriptor const packet) + { + if (!(packet.handle() == *_file_handle)) { + error("packet and handle mismatch"); + throw ~0; + } + + if (packet.position() > _file_seek || _file_seek >= _file_size) { + error("bad packet seek position"); + _file_ds.realloc(&_env.ram(), 0); + _file_seek = 0; + _file_size = 0; + } else { + size_t const n = min(packet.length(), _file_size - _file_seek); + memcpy(_file_ds.local_addr()+_file_seek, + _fs.tx()->packet_content(packet), n); + _file_seek += n; + } + + if (_file_seek >= _file_size) { + _fs.tx()->release_packet(_raw_pkt); + _fs.close(*_file_handle); + + _file_handle.destruct(); + _roms_elem.destruct(); + + _roms_elem.construct(*this, _roms); + --_ref_count; + + /* attach dataspace read-only into region map */ + enum { OFFSET = 0, LOCAL_ADDR = false, EXEC = true, WRITE = false }; + _rm_attachment = _rm.attach( + _file_ds.cap(), _file_size, OFFSET, + LOCAL_ADDR, (addr_t)~0, EXEC, WRITE); + _rm_ds = _rm.dataspace(); + } else { + _submit_next_packet(); + } + } +}; + + +class Cached_fs_rom::Session_component final : public Rpc_object +{ + private: + + Rom_file &_rom_file; + + Sessions::Element _sessions_elem; + + public: + + Session_component(Rom_file &rom_file, + Sessions &sessions, + Sessions::Id id) + : + _rom_file(rom_file), + _sessions_elem(*this, sessions, id) + { + _rom_file.inc_ref(); + } + + ~Session_component() + { + _rom_file.dec_ref(); + } + + /*************************** + ** ROM session interface ** + ***************************/ + + Rom_dataspace_capability dataspace() override { + return _rom_file.dataspace(); } + + void sigh(Signal_context_capability) override { } + bool update() override { return false; } +}; + + +struct Cached_fs_rom::Main final : Genode::Session_request_handler +{ + Genode::Env &env; + + Rm_connection rm { env }; + + Rom_files rom_cache { }; + Sessions rom_sessions { }; + + /* Heap for local allocation */ + Heap heap { env.pd(), env.rm() }; + + /* allocate sessions on a simple heap */ + Sliced_heap sliced_heap { env.pd(), env.rm() }; + + Allocator_avl fs_tx_block_alloc { &heap }; + File_system::Connection fs { env, fs_tx_block_alloc }; + + Session_requests_rom session_requests { env, *this }; + + Io_signal_handler
packet_handler { + env.ep(), *this, &Main::handle_packets }; + + /** + * Signal handler to disable blocking behavior in 'Expanding_parent_client' + */ + Signal_handler
resource_handler { + env.ep(), *this, &Main::handle_resources }; + + /** + * Process requests again if parent gives us an upgrade + */ + void handle_resources() { + session_requests.process(); } + + /** + * Return true when a cache element is freed + */ + bool cache_evict() + { + Rom_file *discard = nullptr; + + rom_cache.for_each([&] (Rom_file &rf) { + if (discard) return; + if (rf.unused()) discard = &rf; + }); + + if (discard) { + destroy(heap, discard); + return true; + } + return false; + } + + /** + * Open a file handle + */ + File_system::File_handle open(Path const &file_path) + { + using namespace File_system; + + Path dir_path(file_path); + dir_path.strip_last_element(); + Path file_name(file_path); + file_name.keep_only_last_element(); + + Dir_handle parent_handle = fs.dir(dir_path.base(), false); + Handle_guard parent_guard(fs, parent_handle); + + return fs.file( + parent_handle, file_name.base() + 1, + File_system::READ_ONLY, false); + } + + /** + * Open a file with some exception management + */ + File_system::File_handle try_open(Path const &file_path) + { + using namespace File_system; + try { return open(file_path); } + catch (Lookup_failed) { error(file_path, " not found"); } + catch (Invalid_handle) { error(file_path, ": invalid handle"); } + catch (Invalid_name) { error(file_path, ": invalid nme"); } + catch (Permission_denied) { error(file_path, ": permission denied"); } + catch (...) { error(file_path, ": unhandled error"); } + throw Service_denied(); + } + + /** + * Create new sessions + */ + void handle_session_create(Session_state::Name const &name, + Parent::Server::Id pid, + Session_state::Args const &args) override + { + if (name != "ROM") throw Service_denied(); + + + /************************************************ + ** Enforce sufficient donation for RPC object ** + ************************************************/ + + size_t ram_quota = + Arg_string::find_arg(args.string(), "ram_quota").ulong_value(0); + size_t session_size = + max((size_t)4096, sizeof(Session_component)); + + if (ram_quota < session_size) + throw Insufficient_ram_quota(); + + + /*********************** + ** Find ROM in cache ** + ***********************/ + + Session_label const label = label_from_args(args.string()); + Path const path(label.last_element().string()); + Sessions::Id const id { pid.value }; + + Rom_file *rom_file = nullptr; + + /* lookup the Rom_file in the cache */ + rom_cache.for_each([&] (Rom_file &rf) { + if (!rom_file && rf.path() == path) + rom_file = &rf; + }); + + if (!rom_file) { + File_system::File_handle handle = try_open(path); + size_t file_size = fs.status(handle).size; + + /* alloc-or-evict loop */ + do { + try { + new (heap) + Rom_file(env, rm, fs, handle, file_size, rom_cache, path); + /* session is ready when read completes */ + return; + } + /* + * There is an assumption that failure to allocate + * will implicitly trigger a resource request to the + * parent. If this behavior changes in the base library + * then this local mechanism cannot be expected to work. + */ + catch (Quota_guard::Limit_exceeded) { } + catch (Quota_guard::Limit_exceeded) { } + } while (cache_evict()); + /* eviction failed */ + warning("insufficient resources for '", label, "'" + ", stalling for upgrade"); + return; + } + + if (!rom_file->completed()) + return; + + + /*************************** + ** Create new RPC object ** + ***************************/ + + try { + Session_component *session = new (sliced_heap) + Session_component(*rom_file, rom_sessions, id); + env.parent().deliver_session_cap(pid, env.ep().manage(*session)); + } + + catch (Sessions::Conflicting_id) { + Genode::warning("session request handled twice, ", args); + } + } + + void handle_session_close(Parent::Server::Id pid) override + { + Sessions::Id id { pid.value }; + rom_sessions.apply( + id, [&] (Session_component &session) + { + env.ep().dissolve(session); + destroy(sliced_heap, &session); + env.parent().session_response(pid, Parent::SESSION_CLOSED); + }); + } + + void handle_packets() + { + Tx_source &source = *fs.tx(); + + while (source.ack_avail()) { + File_system::Packet_descriptor pkt = source.get_acked_packet(); + if (pkt.operation() != File_system::Packet_descriptor::READ) continue; + + bool stray_pkt = true; + + /* find the appropriate session */ + rom_cache.apply( + Rom_files::Id{~(pkt.handle().value)}, [&] (Rom_file &rom) + { + rom.process_packet(pkt); + if (rom.completed()) + session_requests.process(); + stray_pkt = false; + }); + + if (stray_pkt) + source.release_packet(pkt); + } + } + + Main(Genode::Env &env) : env(env) + { + env.parent().resource_avail_sigh(resource_handler); + + fs.sigh_ack_avail(packet_handler); + + /* process any requests that have already queued */ + session_requests.process(); + } +}; + + +void Component::construct(Genode::Env &env) +{ + static Cached_fs_rom::Main inst(env); + env.parent().announce("ROM"); + + + /** + * XXX This workaround can be removed with the eager creation of the + * the env log session. + */ + Genode::log("--- cached_fs_rom ready ---"); +} diff --git a/repos/os/src/server/cached_fs_rom/session_requests.h b/repos/os/src/server/cached_fs_rom/session_requests.h new file mode 100644 index 0000000000..3e59dcf9be --- /dev/null +++ b/repos/os/src/server/cached_fs_rom/session_requests.h @@ -0,0 +1,120 @@ +/* + * \brief Utilities for handling the 'session_requests' ROM + * \author Emery Hemingway + * \date 2018-04-08 + */ + +/* + * Copyright (C) 2018 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 __SESSION_REQUESTS_H_ +#define __SESSION_REQUESTS_H_ + +#include +#include +#include + +namespace Genode { + struct Session_request_handler; + class Session_requests_rom; +}; + + +struct Genode::Session_request_handler : Interface +{ + virtual void handle_session_create(Session_state::Name const &, + Parent::Server::Id, + Session_state::Args const &) = 0; + virtual void handle_session_upgrade(Parent::Server::Id, + Session_state::Args const &) { } + virtual void handle_session_close(Parent::Server::Id) = 0; +}; + + +class Genode::Session_requests_rom +{ + private: + + Parent &_parent; + Session_request_handler &_requests_handler; + + Attached_rom_dataspace _parent_rom; + + Signal_handler _handler; + + public: + + Session_requests_rom(Genode::Env &env, + Session_request_handler &requests_handler) + : _parent(env.parent()), + _requests_handler(requests_handler), + _parent_rom(env, "session_requests"), + _handler(env.ep(), *this, &Session_requests_rom::process) + { + _parent_rom.sigh(_handler); + } + + void process() + { + _parent_rom.update(); + Xml_node requests = _parent_rom.xml(); + + auto const create_fn = [&] (Xml_node request) + { + Parent::Server::Id const id { + request.attribute_value("id", ~0UL) }; + + typedef Session_state::Name Name; + Name const name = request.attribute_value("service", Name()); + + typedef Session_state::Args Args; + Args const args = request.sub_node("args").decoded_content(); + + try { _requests_handler.handle_session_create(name, id, args); } + catch (Service_denied) { + _parent.session_response(id, Parent::SERVICE_DENIED); } + catch (Insufficient_ram_quota) { + _parent.session_response(id, Parent::INSUFFICIENT_RAM_QUOTA); } + catch (Insufficient_cap_quota) { + _parent.session_response(id, Parent::INSUFFICIENT_CAP_QUOTA); } + catch (...) { + error("unhandled exception while creating session"); + _parent.session_response(id, Parent::SERVICE_DENIED); + throw; + } + }; + + auto const upgrade_fn = [&] (Xml_node request) + { + Parent::Server::Id const id { + request.attribute_value("id", ~0UL) }; + + typedef Session_state::Args Args; + Args const args = request.sub_node("args").decoded_content(); + + _requests_handler.handle_session_upgrade(id, args); + }; + + auto const close_fn = [&] (Xml_node request) + { + Parent::Server::Id const id { + request.attribute_value("id", ~0UL) }; + _requests_handler.handle_session_close(id); + }; + + /* close sessions to free resources */ + requests.for_each_sub_node("close", close_fn); + + /* service existing sessions */ + requests.for_each_sub_node("upgrade", upgrade_fn); + + /* create new sessions */ + requests.for_each_sub_node("create", create_fn); + } +}; + +#endif diff --git a/repos/os/src/server/cached_fs_rom/target.mk b/repos/os/src/server/cached_fs_rom/target.mk new file mode 100755 index 0000000000..ecc12be1a4 --- /dev/null +++ b/repos/os/src/server/cached_fs_rom/target.mk @@ -0,0 +1,3 @@ +TARGET = cached_fs_rom +SRC_CC = main.cc +LIBS = base diff --git a/repos/os/src/test/immutable_rom/component.cc b/repos/os/src/test/immutable_rom/component.cc new file mode 100644 index 0000000000..1bdb8d1ef5 --- /dev/null +++ b/repos/os/src/test/immutable_rom/component.cc @@ -0,0 +1,29 @@ +/* + * \brief Test of read-only ROM services + * \author Emery Hemingway + * \date 2018-05-15 + */ + +/* + * Copyright (C) 2018 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 + +void Component::construct(Genode::Env &env) +{ + using namespace Genode; + + Attached_rom_dataspace rom(env, "test"); + log("--- writing to ROM dataspace ---"); + for (size_t i = 0; i < rom.size(); ++i) { + rom.local_addr()[i] = i; + log("--- ROM dataspace modified at ", (Hex)i, "! ---"); + } + env.parent().exit(0); +} diff --git a/repos/os/src/test/immutable_rom/target.mk b/repos/os/src/test/immutable_rom/target.mk new file mode 100644 index 0000000000..62f87b2988 --- /dev/null +++ b/repos/os/src/test/immutable_rom/target.mk @@ -0,0 +1,3 @@ +TARGET = test-immutable_rom +SRC_CC = component.cc +LIBS = base