l4lx: ballooning by using new parent interface

Implement a ballooning mechanism in L4Linux similar to solutions like XEN's
balloon driver. Therefore the new parent interface extensions for requesting
and yielding resources are used. L4Linux registers a yield signal context at
its parent. Whenever the parent triggers a yield, the balloon driver blows up,
which means it requests all pages available, and then frees the corresponding
backend memory.
This commit is contained in:
Stefan Kalkowski 2013-09-10 15:16:35 +02:00 committed by Christian Helmuth
parent 705c73d3f3
commit 59d6157441
10 changed files with 507 additions and 135 deletions

View File

@ -0,0 +1,36 @@
/*
* \brief Genode C API balloon functions
* \author Stefan Kalkowski
* \date 2013-09-19
*/
/*
* 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.
*/
#ifndef _INCLUDE__GENODE__BALLOON_H_
#define _INCLUDE__GENODE__BALLOON_H_
#include <l4/sys/compiler.h>
#include <l4/sys/types.h>
#include <genode/linkage.h>
#ifdef __cplusplus
extern "C" {
#endif
L4_CV l4_cap_idx_t genode_balloon_irq_cap(void);
L4_CV void genode_balloon_free_chunk(unsigned long addr);
L4_CV void genode_balloon_free_done(void);
#ifdef __cplusplus
}
#endif
#endif /* _INCLUDE__GENODE__BALLOON_H_ */

View File

@ -112,7 +112,7 @@ set config {
</route>
</start>
<start name="nic_bridge">
<resource name="RAM" quantum="2M"/>
<resource name="RAM" quantum="4M"/>
<provides><service name="Nic"/></provides>
<route>
<service name="Nic"> <child name="usb_drv"/> </service>
@ -163,64 +163,23 @@ set config {
<start name="cli_monitor">
<resource name="RAM" quantum="2G"/>
<config>
<subsystem name="l4linux" help="L4Linux with 100 MiB RAM">
<binary name="init"/>
<resource name="RAM" quantum="116M"/>
<config>
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="CAP"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
<service name="SIGNAL"/>
<service name="Timer"/>
<service name="Terminal"/>
</parent-provides>
<start name="l4linux">
<resource name="RAM" quantum="1G"/>
<config args="mem=100M console=ttyS0 l4x_rd=initrd.gz l4x_cpus=2 l4x_cpus_map=0,1"/>
<route> <any-service> <parent/> </any-service> </route>
</start>
</config>
</subsystem>
<preservation name="RAM" quantum="16M" />
<subsystem name="l4linux_net" help="L4Linux with network">
<binary name="init"/>
<resource name="RAM" quantum="116M"/>
<config>
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="CAP"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
<service name="SIGNAL"/>
<service name="Timer"/>
<service name="Terminal"/>
<service name="Nic"/>
</parent-provides>
<start name="l4linux">
<resource name="RAM" quantum="1G"/>
<config args="mem=100M console=ttyS0 l4x_rd=initrd.gz l4x_cpus=2 l4x_cpus_map=0,1"/>
<route> <any-service> <parent/> </any-service> </route>
</start>
</config>
<binary name="l4linux"/>
<resource name="RAM" quantum="64M"/>
<config args="mem=2G console=ttyS0 l4x_rd=initrd.gz l4x_cpus=2 l4x_cpus_map=0,1"/>
</subsystem>
<subsystem name="l4linux_emmc" help="L4Linux accessing the second eMMC partition">
<binary name="l4linux"/>
<resource name="RAM" quantum="100M"/>
<config args="mem=64M console=ttyS0 l4x_rd=initrd.gz l4x_cpus=2 l4x_cpus_map=0,1">
<resource name="RAM" quantum="64M"/>
<config args="mem=2G console=ttyS0 l4x_rd=initrd.gz l4x_cpus=2 l4x_cpus_map=0,1">
<block label="sda" />
</config>
</subsystem>
<subsystem name="l4linux_sata" help="L4Linux accessing the second SATA partition">
<binary name="l4linux"/>
<resource name="RAM" quantum="100M"/>
<config args="mem=64M console=ttyS0 l4x_rd=initrd.gz l4x_cpus=2 l4x_cpus_map=0,1">
<resource name="RAM" quantum="64MM"/>
<config args="mem=2G console=ttyS0 l4x_rd=initrd.gz l4x_cpus=2 l4x_cpus_map=0,1">
<block label="sda" />
</config>
</subsystem>

View File

@ -1,4 +1,5 @@
obj-y += genode_serial.o
obj-y += genode_balloon.o
obj-$(CONFIG_RTC_CLASS) += genode_rtc.o
obj-$(CONFIG_FB) += genode_fb.o
obj-$(CONFIG_BLOCK) += genode_block.o

View File

@ -0,0 +1,112 @@
/*
* \brief Balloon driver to use Genode's dynamic memory balancing
* \author Stefan Kalkowski <stefan.kalkowski@genode-labs.com>
* \date 2013-09-19
*/
/*
* 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.
*/
/* Linux includes */
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/platform_device.h>
#include <linux/highmem.h>
#include <linux/sizes.h>
#include <asm/tlb.h>
#include <l4/log/log.h>
#include <genode/balloon.h>
#define GFP_BALLOON \
(__GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM | __GFP_NOWARN | __GFP_NORETRY | __GFP_NOMEMALLOC)
enum { CHUNK_CACHE_SIZE = 16384 };
static void* chunk_cache[CHUNK_CACHE_SIZE];
static void free_avail_pages(unsigned long data)
{
void *pages;
unsigned i = 0;
LOG_printf("free_avail_pages\n");
for (; i < CHUNK_CACHE_SIZE; i++) {
pages = alloc_pages_exact(SZ_1M, GFP_BALLOON);
if (!pages)
break;
chunk_cache[i] = pages;
}
BUG_ON(i == CHUNK_CACHE_SIZE);
/* Ensure that ballooned highmem pages don't have kmaps. */
kmap_flush_unused();
flush_tlb_all();
for (; i > 0;) {
genode_balloon_free_chunk((unsigned long)chunk_cache[--i]);
free_pages_exact(chunk_cache[i], SZ_1M);
}
LOG_printf("free_avail_pages done\n");
genode_balloon_free_done();
}
DECLARE_TASKLET(free_avail, free_avail_pages, 0);
static irqreturn_t event_interrupt(int irq, void *data)
{
tasklet_schedule(&free_avail);
return IRQ_HANDLED;
}
static struct platform_device genode_balloon_device = {
.name = "balloon-genode",
};
static int __init balloon_init(void)
{
int ret = 0;
unsigned irq;
l4_cap_idx_t irq_cap;
/*
* touch the memory eager otherwise we run into trouble
* when memory is empty and we balloon
*/
memset(&chunk_cache, 0, sizeof(chunk_cache));
/**
* Obtain an IRQ for the device.
*/
irq_cap = genode_balloon_irq_cap();
if ((irq = l4x_register_irq(irq_cap)) < 0)
return -ENOMEM;
if ((ret = request_irq(irq, event_interrupt, 0,
"Genode balloon", &genode_balloon_device))) {
printk(KERN_WARNING "%s: request_irq failed: %d\n", __func__, ret);
return ret;
}
ret = platform_device_register(&genode_balloon_device);
return ret;
}
subsys_initcall(balloon_init);
MODULE_LICENSE("GPL");

View File

@ -20,6 +20,7 @@
#include <base/env.h>
#include <util/avl_tree.h>
#include <rm_session/connection.h>
#include <platform_env.h>
namespace Fiasco {
#include <l4/sys/types.h>
@ -56,7 +57,8 @@ namespace L4lx {
Fiasco::l4_cap_idx_t ref() { return _ref; }
virtual Genode::Dataspace_capability cap() = 0;
virtual bool map(Genode::size_t offset) = 0;
virtual void map(Genode::size_t offset, bool greedy = false) = 0;
virtual bool free(Genode::size_t offset) = 0;
/************************
** Avl_node interface **
@ -89,8 +91,9 @@ namespace L4lx {
Genode::cap_idx_alloc()->alloc_range(1)->kcap())
: Dataspace(name, size, ref), _cap(ds) {}
Genode::Dataspace_capability cap() { return _cap; }
bool map(Genode::size_t offset) { return true; }
Genode::Dataspace_capability cap() { return _cap; }
void map(Genode::size_t offset, bool greedy) { }
bool free(Genode::size_t offset) { return false; }
};
@ -98,72 +101,54 @@ namespace L4lx {
{
private:
class Chunk : public Genode::Avl_node<Chunk>
{
private:
Genode::size_t _offset;
Genode::size_t _size;
Genode::Dataspace_capability _cap;
public:
Chunk(Genode::size_t off, Genode::size_t size,
Genode::Dataspace_capability cap)
: _offset(off), _size(size), _cap(cap) {}
Genode::size_t offset() { return _offset; }
Genode::size_t size() { return _size; }
Genode::Dataspace_capability cap() { return _cap; }
bool higher(Chunk *n) { return n->_offset > _offset; }
Chunk *find_by_offset(Genode::size_t off)
{
if (off >= _offset && off < _offset+_size) return this;
Chunk *n = Genode::Avl_node<Chunk>::child(off > _offset);
return n ? n->find_by_offset(off) : 0;
}
};
Genode::Rm_connection _rm;
Genode::Avl_tree<Chunk> _chunks;
Genode::size_t _chunk_size;
Genode::size_t _chunk_size_log2;
Genode::Rm_connection _rm_con;
Genode::Expanding_rm_session_client _rm;
Genode::Ram_dataspace_capability *_chunks;
public:
enum {
CHUNK_SIZE_LOG2 = 20,
CHUNK_SIZE = 1 << CHUNK_SIZE_LOG2,
};
Chunked_dataspace(const char* name,
Genode::size_t size,
Fiasco::l4_cap_idx_t ref,
Genode::size_t chunk_size)
: Dataspace(name, size, ref), _rm(0, size), _chunk_size(chunk_size),
_chunk_size_log2(Genode::log2(_chunk_size)) {}
Genode::Dataspace_capability cap() { return _rm.dataspace(); }
bool map(Genode::size_t off)
Fiasco::l4_cap_idx_t ref)
: Dataspace(name, size, ref), _rm_con(0, size), _rm(_rm_con.cap())
{
off = Genode::align_addr((off-(_chunk_size-1)), _chunk_size_log2);
_chunks = (Genode::Ram_dataspace_capability*) Genode::env()->heap()->alloc(
sizeof(Genode::Ram_dataspace_capability) * (size/CHUNK_SIZE));
}
Chunk* c = _chunks.first() ? _chunks.first()->find_by_offset(off) : 0;
if (c) return true;
Genode::Dataspace_capability cap() { return _rm_con.dataspace(); }
try {
Genode::Dataspace_capability cap =
Genode::env()->ram_session()->alloc(_chunk_size);
_chunks.insert(new (Genode::env()->heap())
Chunk(off, _chunk_size, cap));
_rm.attach(cap, 0, 0, true, off);
return true;
} catch(Genode::Ram_session::Quota_exceeded) {
PWRN("Could not allocate new dataspace chunk");
} catch(Genode::Rm_session::Attach_failed) {
PWRN("Attach of chunk dataspace of size %zx to %p failed",
_chunk_size, (void*) off);
void map(Genode::size_t off, bool greedy)
{
off = Genode::align_addr((off-(CHUNK_SIZE-1)), CHUNK_SIZE_LOG2);
int i = off / CHUNK_SIZE;
if (_chunks[i].valid()) return;
Genode::size_t ram_avail = Genode::env()->ram_session()->avail();
if (greedy && ram_avail < 4*CHUNK_SIZE) {
char buf[128];
Genode::snprintf(buf, sizeof(buf), "ram_quota=%zd",
4*CHUNK_SIZE - ram_avail);
Genode::env()->parent()->resource_request(buf);
}
return false;
_chunks[i] = Genode::env()->ram_session()->alloc(CHUNK_SIZE);
_rm.attach(_chunks[i], 0, 0, true, off, false);
}
bool free(Genode::size_t off)
{
off = Genode::align_addr((off-(CHUNK_SIZE-1)), CHUNK_SIZE_LOG2);
int i = off / CHUNK_SIZE;
if (!_chunks[i].valid()) return false;
Genode::env()->ram_session()->free(_chunks[i]);
_chunks[i] = Genode::Ram_dataspace_capability();
return true;
}
};

View File

@ -0,0 +1,111 @@
/*
* \brief Platform environment of Genode process
* \author Norman Feske
* \author Christian Helmuth
* \date 2006-07-28
*
* This file is a generic variant of the platform environment, which is
* suitable for platforms such as L4ka::Pistachio and L4/Fiasco. On other
* platforms, it may be replaced by a platform-specific version residing
* in the corresponding 'base-<platform>' repository.
*/
/*
* Copyright (C) 2006-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.
*/
#ifndef _PLATFORM_ENV_H_
#define _PLATFORM_ENV_H_
/* Genode includes */
#include <base/printf.h>
#include <base/env.h>
#include <base/heap.h>
#include <rm_session/client.h>
#include <util/arg_string.h>
namespace Genode {
struct Expanding_rm_session_client;
}
/**
* Repeatedly try to execute a function 'func'
*
* If the function 'func' throws an exception of type 'EXC', the 'handler'
* is called and the function call is retried.
*
* \param EXC exception type to handle
* \param func functor to execute
* \param handler exception handler executed if 'func' raised an exception
* of type 'EXC'
* \param attempts number of attempts to execute 'func' before giving up
* and reflecting the exception 'EXC' to the caller. If not
* specified, attempt infinitely.
*/
template <typename EXC, typename FUNC, typename HANDLER>
auto retry(FUNC func, HANDLER handler, unsigned attempts = ~0U) -> decltype(func())
{
for (unsigned i = 0; attempts == ~0U || i < attempts; i++)
try { return func(); }
catch (EXC) { handler(); }
throw EXC();
}
/**
* Client object for a session that may get its session quota upgraded
*/
template <typename CLIENT>
struct Upgradeable_client : CLIENT
{
typedef Genode::Capability<typename CLIENT::Rpc_interface> Capability;
Capability _cap;
Upgradeable_client(Capability cap) : CLIENT(cap), _cap(cap) { }
void upgrade_ram(Genode::size_t quota)
{
PINF("upgrading quota donation for Env::%s (%zd bytes)",
CLIENT::Rpc_interface::service_name(), quota);
char buf[128];
Genode::snprintf(buf, sizeof(buf), "ram_quota=%zd", quota);
Genode::env()->parent()->upgrade(_cap, buf);
}
};
struct Genode::Expanding_rm_session_client : Upgradeable_client<Genode::Rm_session_client>
{
Expanding_rm_session_client(Rm_session_capability cap)
: Upgradeable_client<Genode::Rm_session_client>(cap) { }
Local_addr attach(Dataspace_capability ds, size_t size, off_t offset,
bool use_local_addr, Local_addr local_addr,
bool executable)
{
return retry<Rm_session::Out_of_metadata>(
[&] () {
return Rm_session_client::attach(ds, size, offset,
use_local_addr,
local_addr,
executable); },
[&] () { upgrade_ram(8*1024); });
}
Pager_capability add_client(Thread_capability thread)
{
return retry<Rm_session::Out_of_metadata>(
[&] () { return Rm_session_client::add_client(thread); },
[&] () { upgrade_ram(8*1024); });
}
};
#endif /* _PLATFORM_ENV_H_ */

View File

@ -14,7 +14,6 @@
/* Genode includes */
#include <base/printf.h>
#include <base/capability.h>
#include <os/config.h>
#include <rm_session/connection.h>
/* L4lx includes */
@ -31,36 +30,18 @@ static const bool DEBUG = false;
extern "C" {
static const unsigned long _chunk_size()
{
enum { DEFAULT_CHUNK_SIZE = 16*1024*1024 };
Genode::Number_of_bytes result = DEFAULT_CHUNK_SIZE;
try {
Genode::config()->xml_node().sub_node("ram")
.attribute("chunk_size")
.value(&result);
} catch(...) { }
return result;
}
long l4re_ma_alloc(unsigned long size, l4re_ds_t const mem,
unsigned long flags)
{
static const unsigned long chunk_size = _chunk_size();
using namespace L4lx;
if (DEBUG)
PDBG("size=%lx mem=%lx flags=%lx", size, mem, flags);
Dataspace *ds;
if (size > chunk_size) {
if (Genode::log2(size) >= Chunked_dataspace::CHUNK_SIZE_LOG2) {
ds = new (Genode::env()->heap())
Chunked_dataspace("lx_memory", size, mem, chunk_size);
Chunked_dataspace("lx_memory", size, mem);
} else {
Genode::Dataspace_capability cap =
Genode::env()->ram_session()->alloc(size);

View File

@ -1,13 +1,66 @@
#include <env.h>
#include <rm.h>
#include <vcpu.h>
#include <linux.h>
namespace Fiasco {
#include <genode/net.h>
#include <l4/sys/irq.h>
#include <l4/util/util.h>
#include <l4/sys/linkage.h>
}
extern "C" L4_CV int l4x_forward_pf(Fiasco::l4_umword_t addr,
Fiasco::l4_umword_t pc, int extra_write)
static bool ballooning = false;
static Genode::Lock balloon_lock;
namespace {
class Signal_thread : public Genode::Thread<8192>
{
private:
Fiasco::l4_cap_idx_t _cap;
Genode::Lock *_sync;
protected:
void entry()
{
using namespace Fiasco;
using namespace Genode;
Signal_receiver receiver;
Signal_context rx;
Signal_context_capability cap(receiver.manage(&rx));
Genode::env()->parent()->yield_sigh(cap);
_sync->unlock();
while (true) {
receiver.wait_for_signal();
Genode::env()->parent()->yield_request();
{
Genode::Lock::Guard guard(balloon_lock);
ballooning = true;
if (l4_error(l4_irq_trigger(_cap)) != -1)
PWRN("IRQ net trigger failed\n");
}
}
}
public:
Signal_thread(Fiasco::l4_cap_idx_t cap, Genode::Lock *sync)
: Genode::Thread<8192>("net-signal-thread"), _cap(cap), _sync(sync) {
start(); }
};
}
extern "C" {
L4_CV int l4x_forward_pf(Fiasco::l4_umword_t addr,
Fiasco::l4_umword_t pc, int extra_write)
{
using namespace Fiasco;
@ -15,8 +68,16 @@ extern "C" L4_CV int l4x_forward_pf(Fiasco::l4_umword_t addr,
Genode::addr_t ds_start_addr = addr;
L4lx::Region *r = L4lx::Env::env()->rm()->find_region(&ds_start_addr, &size);
L4lx::Dataspace *ds = r ? r->ds() : 0;
if (ds && !ds->map(addr - r->addr()))
return 0;
while (ds) {
try {
ds->map(addr - r->addr(), !ballooning);
break;
} catch(Genode::Rm_session::Attach_failed) {
PWRN("Attach of chunk dataspace of failed");
return 0;
}
}
if (!extra_write)
l4_touch_ro((void*)l4_trunc_page(addr), L4_LOG2_PAGESIZE);
@ -26,3 +87,36 @@ extern "C" L4_CV int l4x_forward_pf(Fiasco::l4_umword_t addr,
}
Fiasco::l4_cap_idx_t genode_balloon_irq_cap()
{
Linux::Irq_guard guard;
static Genode::Native_capability cap = L4lx::vcpu_connection()->alloc_irq();
static Genode::Lock lock(Genode::Lock::LOCKED);
static Signal_thread th(cap.dst(), &lock);
lock.lock();
return cap.dst();
}
bool genode_balloon_free_chunk(unsigned long addr)
{
Linux::Irq_guard guard;
Genode::addr_t ds_start_addr = addr;
Genode::size_t size = L4_PAGESIZE;
L4lx::Region *r = L4lx::Env::env()->rm()->find_region(&ds_start_addr, &size);
L4lx::Dataspace *ds = r ? r->ds() : 0;
return ds ? ds->free(addr - r->addr()) : false;
}
void genode_balloon_free_done()
{
Linux::Irq_guard ig;
Genode::Lock::Guard guard(balloon_lock);
ballooning = false;
Genode::env()->parent()->yield_response();
}
}

View File

@ -0,0 +1,90 @@
#include <balloon_session/balloon_session.h>
#include <cap_session/connection.h>
#include <timer_session/connection.h>
#include <base/rpc_server.h>
#include <root/component.h>
#include <base/sleep.h>
#include <base/env.h>
using namespace Genode;
class Session_component;
static List<Session_component> session_list;
class Session_component : public Rpc_object<Balloon_session, Session_component>,
public List<Session_component>::Element
{
private:
Signal_context_capability _handler;
public:
Session_component() {
session_list.insert(this); }
~Session_component() {
session_list.remove(this); };
int increase_quota(Ram_session_capability ram_session, size_t amount)
{
PDBG("increase ram_quota of client by %zx", amount);
while (true) ;
return 0;
}
void balloon_handler(Signal_context_capability handler) {
_handler = handler; }
Signal_context_capability handler() { return _handler; }
};
class Root : public Genode::Root_component<Session_component>
{
protected:
Session_component *_create_session(const char *args)
{
size_t ram_quota =
Arg_string::find_arg(args, "ram_quota").ulong_value(0);
if (ram_quota < sizeof(Session_component))
throw Root::Quota_exceeded();
return new (md_alloc()) Session_component();
}
public:
Root(Rpc_entrypoint *session_ep,
Allocator *md_alloc)
: Root_component(session_ep, md_alloc) { }
};
int main() {
enum { STACK_SIZE = 1024*sizeof(Genode::addr_t) };
static Timer::Connection timer;
static Cap_connection cap;
static Rpc_entrypoint ep(&cap, STACK_SIZE, "balloon_ep");
static ::Root root(&ep, env()->heap());
env()->parent()->announce(ep.manage(&root));
while (true) {
Session_component *c = session_list.first();
while (c) {
if (c->handler().valid()) {
PINF("request memory from client!");
Signal_transmitter transmitter(c->handler());
transmitter.submit();
}
c = c->next();
}
timer.msleep(10000);
}
return 0;
}

View File

@ -0,0 +1,3 @@
TARGET = balloon
LIBS = base
SRC_CC = main.cc