/* * \brief Test for yielding resources * \author Norman Feske * \date 2013-10-05 * * This test exercises the protocol between a parent and child, which is used * by the parent to regain resources from a child subsystem. * * The program acts in either one of two roles, the parent or the child. The * role is determined by reading a config argument. * * The child periodically allocates chunks of RAM until its RAM quota is * depleted. Once it observes a yield request from the parent, however, it * cooperatively releases as much resources as requested by the parent. * * The parent wait a while to give the child the chance to allocate RAM. It * then sends a yield request and waits for a response. When getting the * response, it validates whether the child complied to the request or not. */ /* * Copyright (C) 2013 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU General Public License version 2. */ /* Genode includes */ #include #include #include #include #include #include #include /**************** ** Child role ** ****************/ /** * The child eats more and more RAM. However, when receiving a yield request, * it releases the requested amount of resources. */ class Child { private: typedef Genode::size_t size_t; struct Ram_chunk : Genode::List::Element { size_t const size; Genode::Ram_dataspace_capability ds_cap; Ram_chunk(size_t size) : size(size), ds_cap(Genode::env()->ram_session()->alloc(size)) { } ~Ram_chunk() { Genode::env()->ram_session()->free(ds_cap); } }; bool const _expand; Genode::List _ram_chunks; Timer::Connection _timer; Genode::Signal_receiver _sig_rec; Genode::Signal_dispatcher _periodic_timeout_dispatcher; Genode::Signal_dispatcher _yield_dispatcher; unsigned long const _period_ms; void _dispatch_periodic_timeout(unsigned); void _dispatch_yield(unsigned); void _schedule_next_timeout() { _timer.trigger_once(_period_ms*1000); } public: Child(); void main(); }; void Child::_dispatch_periodic_timeout(unsigned) { size_t const chunk_size = 1024*1024; if (Genode::env()->ram_session()->avail() < chunk_size) { if (_expand) { PLOG("quota consumed, request additional resources"); /* * The attempt to allocate RAM will result in a resource request to * the parent. The resource request will block until the parent * responds. */ } else { PLOG("consumed all of our quota, stop allocating"); return; } } /* perform allocation and remember chunk in list */ _ram_chunks.insert(new (Genode::env()->heap()) Ram_chunk(chunk_size)); PLOG("allocated chunk of %zd KiB", chunk_size / 1024); _schedule_next_timeout(); } void Child::_dispatch_yield(unsigned) { using namespace Genode; /* request yield request arguments */ Parent::Resource_args const args = env()->parent()->yield_request(); PLOG("yield request: %s", args.string()); size_t const requested_ram_quota = Arg_string::find_arg(args.string(), "ram_quota").ulong_value(0); /* free chunks of RAM to comply with the request */ size_t released_quota = 0; while (released_quota < requested_ram_quota) { Ram_chunk *chunk = _ram_chunks.first(); if (!chunk) { PWRN("no chunk left to release"); break; } size_t const chunk_size = chunk->size; _ram_chunks.remove(chunk); destroy(env()->heap(), chunk); released_quota += chunk_size; PLOG("released chunk of %zd bytes", chunk_size); } /* acknowledge yield request */ env()->parent()->yield_response(); _schedule_next_timeout(); } static inline unsigned long read_period_ms_from_config() { unsigned long period_ms = 500; if (Genode::config()->xml_node().has_attribute("period_ms")) Genode::config()->xml_node().attribute("period_ms").value(&period_ms); return period_ms; } Child::Child() : _expand(Genode::config()->xml_node().has_attribute("expand") && Genode::config()->xml_node().attribute("expand").has_value("yes")), _periodic_timeout_dispatcher(_sig_rec, *this, &Child::_dispatch_periodic_timeout), _yield_dispatcher(_sig_rec, *this, &Child::_dispatch_yield), _period_ms(read_period_ms_from_config()) { /* register yield signal handler */ Genode::env()->parent()->yield_sigh(_yield_dispatcher); /* register timeout signal handler and schedule periodic timeouts */ _timer.sigh(_periodic_timeout_dispatcher); _schedule_next_timeout(); } void Child::main() { using namespace Genode; for (;;) { Signal sig = _sig_rec.wait_for_signal(); Signal_dispatcher_base *dispatcher = dynamic_cast(sig.context()); if (dispatcher) dispatcher->dispatch(sig.num()); } } /***************** ** Parent role ** *****************/ /** * The parent grants resource requests as long as it has free resources. * Once in a while, it politely requests the child to yield resources. */ class Parent : Genode::Slave_policy { private: /** * Return singleton entrypoint instance * * The entrypoint cannot be a regular member because we need to pass * it into the constructor of the 'Slave_policy' base class. */ static Genode::Rpc_entrypoint &_entrypoint(); typedef Genode::size_t size_t; size_t const slave_quota = 10*1024*1024; Genode::Slave _slave = { _entrypoint(), *this, slave_quota }; Timer::Connection _timer; Genode::Lock _yield_blockade; void _print_status() { PLOG("quota: %zd KiB used: %zd KiB", _slave.ram().quota() / 1024, _slave.ram().used() / 1024); } public: /** * Constructor */ Parent() : Genode::Slave_policy("test-resource_yield", _entrypoint(), Genode::env()->ram_session()) { configure(""); } int main(); /**************************** ** Slave_policy interface ** ****************************/ char const **_permitted_services() const { static char const *services[] = { "RM", "SIGNAL", "LOG", "Timer" }; return services; } void yield_response() { _yield_blockade.unlock(); /* * At this point, the ownership of '_yield_blockade' will be passed * to the main program. By trying to aquire it here, we block until * the main program is ready. * * This way, we ensure that the main program validates the * statistics before the 'yield_response' RPC call returns. * Otherwise, the child might allocate new resources before the * main program is able to see the amount of yielded resources. */ Genode::Lock::Guard guard(_yield_blockade); } }; Genode::Rpc_entrypoint &Parent::_entrypoint() { using namespace Genode; static Cap_connection cap; size_t const stack_size = sizeof(addr_t)*2*1024; static Rpc_entrypoint ep(&cap, stack_size, "ep", false); return ep; } int Parent::main() { using namespace Genode; _entrypoint().activate(); /* perform the test three times */ for (unsigned j = 0; j < 3; j++) { /* wait five seconds and observe growth of resource usage */ for (unsigned i = 0; i < 5; i++) { _timer.msleep(1000); _print_status(); } /* remember quantum of resources used by the child */ size_t const used_prior_yield = _slave.ram().used(); /* issue yield request */ Genode::Parent::Resource_args yield_args("ram_quota=5M"); _slave.yield(yield_args); /* * Synchronously wait for a yield response. Note that a careful parent * would never trust its child to comply to the yield request. */ PLOG("wait for yield response"); _yield_blockade.lock(); _yield_blockade.lock(); PLOG("got yield response"); _print_status(); /* validate that the amount of yielded resources matches the request */ size_t const used_after_yield = _slave.ram().used(); if (used_after_yield + 5*1024*1024 > used_prior_yield) { PERR("child has not yielded enough resources"); return -1; } /* let the 'yield_response' RPC call return */ _yield_blockade.unlock(); } printf("--- test-resource_yield finished ---\n"); return 0; } /****************** ** Main program ** ******************/ int main(int argc, char **argv) { using namespace Genode; /* * Read value '' attribute to decide whether to perform * the child or the parent role. */ bool const is_child = config()->xml_node().has_attribute("child") && config()->xml_node().attribute("child").has_value("yes"); if (is_child) { printf("--- test-resource_yield child role started ---\n"); static ::Child child; child.main(); return -1; /* the child should never reach this point */ } else { printf("--- test-resource_yield parent role started ---\n"); static ::Parent parent; return parent.main(); } }