*
* Memory management implementation
*
- * (c) 2011-2012 Björn Döbel <doebel@os.inf.tu-dresden.de>,
+ * (c) 2011-2013 Björn Döbel <doebel@os.inf.tu-dresden.de>,
* economic rights: Technische Universität Dresden (Germany)
* This file is part of TUD:OS and distributed under the terms of the
* GNU General Public License 2.
void Romain::Region_ops::free(Region_handler const *h, l4_addr_t start, unsigned long size)
{
- (void)h;
- (void)start;
- (void)size;
- enter_kdebug("ops::free");
+ if ((h->flags() & L4Re::Rm::Reserved))
+ return;
+
+ if (h->flags() & L4Re::Rm::Pager)
+ return;
+
+ L4::Cap<L4Re::Dataspace> ds(h->client_cap_idx());
+ ds->clear(h->offset() + start, size);
+
+ //enter_kdebug("ops::free");
}
Romain::Region_map::Region_map()
: Base(0,0), _active_instance(Invalid_inst)
{
+ pthread_mutex_init(&_mtx, 0);
+
l4_kernel_info_t *kip = l4re_kip();
MSG() << "kip found at 0x" << std::hex << kip
<< " having " << L4::Kip::Mem_desc::count(kip)
}
+/*
+ * Attach a replica region locally in the master AS.
+ *
+ * The Romain::Master manages replicas' region map. As a part of this, the master
+ * attaches a copy of every region attached to one of the replicas to its own
+ * address space. This local region is then used to service page faults caused by
+ * the replicas.
+ *
+ * For read-only replica dataspaces, the master creates a writable copy of the
+ * original region in a separate dataspace. This allows us to modify their content
+ * if necessary (e.g., setting debug breakpoints on read-only code).
+ *
+ * For writable dataspaces, the master simply attaches the same region that has
+ * already been attached to the replica AS into its own address space and maintains
+ * a copy for every replica.
+ *
+ * The replica copies lead to another problem: to reduce replication overhead, we
+ * would like to handle page faults using as few memory mappings as possible. To
+ * be able to do so, we however need to make sure that all replica copies are
+ * aligned to the same start address. The Romain::Master does this by ensuring
+ * that the replica address as well as the master-local addresses of the copies
+ * are aligned to the same offset within a minimally aligned region. The minimum
+ * alignment is defined by the Region_handler()'s MIN_ALIGNMENT member (XXX and
+ * should at some point become a configurable feature).
+ *
+ * The layout looks like this:
+ *
+ * REPLICA:
+ *
+ * (1) (2) (3)
+ * +- - - - +---------------------------+
+ * | | |
+ * +- - - - +---------------------------+
+ *
+ * MASTER (per copy):
+ *
+ * (A) (B) (C)
+ * +- - - - +---------------------------+
+ * | | |
+ * +- - - - +---------------------------+
+ *
+ * (1) correct minimum alignment
+ * (2) region.start()
+ * (3) region.end()
+ *
+ * (A) address aligned similar to (1)
+ * (B) local_region[inst]->start()
+ * (C) local_region[inst]->end()
+ *
+ * MIN_ALIGNMENT_MASK := (1 << MIN_ALIGNMENT) - 1
+ * (1) := (2) - [(2) & MIN_ALIGNMENT_MASK]
+ * (A) := (B) - [(B) & MIN_ALIGNMENT_MASK]
+ *
+ * Attaching then works by first reserving an area with the RM
+ * that has the size (C) - (A). Then we attach the dataspace to
+ * (B) and free the reserved area so that the remaining memory
+ * can again be freely used.
+ */
void *Romain::Region_map::attach_locally(void* addr, unsigned long size,
Romain::Region_handler *hdlr,
unsigned flags, unsigned char align)
{
- long r; (void)align; (void)addr;
- Romain::Region_handler::Dataspace const *ds = &hdlr->memory(0);
+ long r; (void)addr;
+ Romain::Region_handler::Dataspace const *ds = &hdlr->local_memory(0);
L4Re::Dataspace::Stats dsstat;
(*ds)->info(&dsstat);
-#if 0
- MSG() << "attaching DS " << std::hex << ds->cap()
+#if 1
+ MSG() << PURPLE
+ << "attaching DS " << std::hex << ds->cap()
<< ", addr = " << addr
<< ", flags = " << flags << " (" << dsstat.flags << ")"
- << ", size = " << size;
+ << ", size = " << size
+ << ", align = " << (unsigned int)align
+ << NOCOLOR;
#endif
if (!ds->is_valid()) {
*/
if (!(dsstat.flags & L4Re::Dataspace::Map_rw)) {
//MSG() << "Copying to rw dataspace.";
- L4::Cap<L4Re::Dataspace> mem = L4Re::Util::cap_alloc.alloc<L4Re::Dataspace>();
- _check(!mem.is_valid(), "could not alloc ds cap");
- //MSG() << " mem_alloc(" << eff_size << ", " << std::hex << mem.cap() << ")";
- r = L4Re::Env::env()->mem_alloc()->alloc(eff_size, mem);
- _check(r != 0, "memory allocation failed");
+ L4::Cap<L4Re::Dataspace> mem;
+
+ Romain::Region_map::allocate_ds(&mem, eff_size);
mem->copy_in(0, *ds, page_base, hdlr->offset() + size);
ds = &mem; // taking pointer to stack?? XXX
page_base = 0;
/*
* XXX: If we copied the dataspace above, what happens to the
- * original DS that was passed in? Shoudln't it be released?
+ * original DS that was passed in? Shouldn't it be released?
*
* No. It might still be attached elsewhere. Perhaps decrement
* some refcnt? XXX check
*/
flags &= ~L4Re::Rm::In_area;
- l4_addr_t a = 0;
- r = L4Re::Env::env()->rm()->attach(&a, eff_size,
- L4Re::Rm::Search_addr | flags,
- *ds, page_base);
- _check(r != 0, "attach error");
+ l4_addr_t a = Romain::Region_map::attach_aligned(ds, eff_size,
+ page_base, flags,
+ hdlr->alignment(),
+ hdlr->align_diff());
Romain::Region reg(a, a+eff_size - 1);
reg.touch_rw();
+ MSG() << "set_local_region(" << _active_instance << ", "
+ << std::hex << reg.start() << ")";
hdlr->set_local_region(_active_instance, reg);
return (void*)(a + offset_in_page);
}
unsigned flags, unsigned char align, bool shared)
{
void *ret = 0;
+
+ MSG() << std::hex << addr << " " << size << " " << (int)align << " " << flags;
+ MSG() << "cap " << std::hex << hdlr.client_cap_idx();
+
+ /*
+ * XXX: the only reason for this Region_handler to exist is to copy
+ * the shared parameter into the handler. Shouldn't the caller
+ * do this???
+ */
Romain::Region_handler _handler(hdlr);
+ _handler.shared(shared);
+
+ /*
+ * First, do the attach() in the remote address space. Thereby we get
+ * the remote address and can then during local attaching make sure
+ * that the memory alignment of the local mapping matches the remote
+ * alignment. This allows us to play mapping tricks later.
+ *
+ * Note, we can only do this if the Search_addr flag is set, otherwise
+ * we might hurt the client's expectations about the mem region.
+ */
+ if ((flags & L4Re::Rm::Search_addr) and
+ !(flags & L4Re::Rm::In_area) and
+ (size > L4_SUPERPAGESIZE) and
+ (align < L4_LOG2_SUPERPAGESIZE)) {
+ align = L4_LOG2_SUPERPAGESIZE;
+ size = l4_round_size(size, L4_SUPERPAGESHIFT);
+ }
+
- if (shared) {
- //DEBUG() << "======> SHARED <======";
- _handler.shared(true);
+ ret = Base::attach(addr, size, _handler, flags, align);
+ MSG() << "Base::attach = " << std::hex << ret << " hdlr.offs: " << _handler.offset();
+ if (ret == (void*)~0UL) {
+ INFO() << std::hex << addr << " " << size << " " << (int)align << " " << flags;
+ enter_kdebug("Attach error");
}
+ /*
+ * The node is now present in the region map. This means we now need
+ * to continue working with the node's handler member instead of our
+ * local copy (because the attach() call has made a copy of it).
+ */
+ Romain::Region_map::Base::Node n = find((l4_addr_t)ret);
+ // and the region handler is const by default ... *grrrml*
+ Romain::Region_handler* theHandler = const_cast<Romain::Region_handler*>(&(n->second));
+
+ /*
+ * Figure out the best possible alignment we can have between the local
+ * and the remote region. If the alignments match, we can later map more
+ * than one page at a time.
+ */
+ theHandler->alignToAddressAndSize(reinterpret_cast<l4_addr_t>(ret), size);
+
/* Only attach locally, if this hasn't been done beforehand yet. */
- if (!_handler.local_region(_active_instance).start()) {
- void* a = attach_locally(addr, size, &_handler, flags, align);
+ if (!n->second.local_region(_active_instance).start()) {
+ void* a = attach_locally(addr, size, theHandler,
+ flags, theHandler->alignment());
_check(a == 0, "Error in local attach");
}
- ret = Base::attach(addr, size, _handler, flags, align);
MSG() << "new mapping (" << _active_instance << ") "
- << "[" << std::hex << _handler.local_region(_active_instance).start()
- << " - " << _handler.local_region(_active_instance).end()
+ << "[" << std::hex << n->second.local_region(_active_instance).start()
+ << " - " << n->second.local_region(_active_instance).end()
<< "] -> " << ret;
//enter_kdebug("after attach");
-#if 0
-int Romain::Region_map::detach(void* addr, unsigned long size, unsigned flags,
- L4Re::Util::Region* reg, Romain::Region_handler* h)
+int Romain::Region_map::detach(void* /* IN */ addr, unsigned long /* IN */ size,
+ unsigned /* IN */ flags,
+ L4Re::Util::Region* /* OUT */ reg,
+ Romain::Region_handler* /* OUT */ h)
{
- MSG() << "addr " << std::hex << l4_addr_t(addr) << " " << size
- << " " << flags;
- MSG() << "active instance: " << _active_instance;
- enter_kdebug("detach");
- return -1;
+ /* First, do a lookup for the handler, because Base::Detach() will not always
+ * return the handler node, but we need it for detach later.
+ */
+ l4_addr_t srch = (l4_addr_t)addr;
+ Romain::Region_handler hd = Base::find(Region(srch, srch + size - 1))->second;
+ MSG() << "Client cap: " << std::hex << hd.client_cap_idx();
+ if (hd.client_cap_idx() == L4_INVALID_CAP) {
+ enter_kdebug("invalid cap!");
+ }
+
+ MSG() << std::hex << "Base::detach(" << (l4_addr_t)addr << " " << size << " " << flags << ")";
+ MSG() << std::hex << find((l4_addr_t)addr)->second.client_cap_idx();
+ int ret = Base::detach(addr, size, flags, reg, h);
+ MSG() << std::hex << "Base::detach(): " << ret << " r.start: " << reg->start() << " " << reg->size();
+
+ /*
+ * Iterate over replicas and detach() the replicas' mappings within
+ * the master AS.
+ */
+ for (unsigned idx = 0; idx < hd.local_region_count(); ++idx) {
+ if (hd.local_region(idx).start() == 0) {
+ continue;
+ }
+
+ MSG() << "[" << std::hex
+ << hd.local_region(idx).start() << " - "
+ << hd.local_region(idx).end() << "] ==> "
+ << "[" << reg->start() << " - "
+ << reg->end() << "]";
+
+ L4::Cap<L4Re::Dataspace> memcap;
+ int r = L4Re::Env::env()->rm()->detach(hd.local_region(idx).start(),
+ hd.local_region(idx).size(),
+ &memcap, L4Re::This_task);
+ MSG() << "detached locally: " << r << " cap: " << std::hex << memcap.cap();
+
+ if (!hd.writable()) {
+ /* For read-only regions, we only need to detach once */
+ break;
+ }
+ }
+
+ return ret;
}
-#endif
-bool Romain::Region_map::lazy_map_region(Romain::Region_map::Base::Node &n, unsigned inst)
+
+bool Romain::Region_map::lazy_map_region(Romain::Region_map::Base::Node &n, unsigned inst, bool iswritepf)
{
/* already mapped? */
if (n->second.local_region(inst).start())
return false;
- DEBUGf(Romain::Log::Memory) << "start " << n->second.local_region(inst).start();
- DEBUGf(Romain::Log::Memory) << "replica without yet established mapping.";
- DEBUGf(Romain::Log::Memory) << "ro: " << n->second.is_ro();
- DEBUGf(Romain::Log::Memory) << "shared: " << (n->second.shared() ? "true" : "false");
+ MSG() << "start " << n->second.local_region(inst).start();
+ MSG() << "replica without yet established mapping.";
+ MSG() << "ro: " << n->second.is_ro();
+ MSG() << "shared: " << (n->second.shared() ? "true" : "false");
/*
* As we found a node, we know there exists at least one replica
* Case 1: region is read-only -> we share the mapping from
* the first node, because it was already established.
*/
-
if (n->second.is_ro() or rh->shared()) {
/*
* XXX: Why is setting local_region and memory split up?
*/
rh->set_local_region(inst, n->second.local_region(existing));
- rh->memory(inst, n->second.memory(existing));
+ rh->local_memory(inst, n->second.local_memory(existing));
} else {
- DEBUGf(Romain::Log::Memory) << "Copying existing mapping.";
- bool b = copy_existing_mapping(*rh, existing, inst);
- _check(!b, "error creating rw copy");
+ MSG() << "Copying existing mapping.";
+ bool b = copy_existing_mapping(*rh, existing, inst, iswritepf);
+ _check(!b, "error creating rw copy");
}
return true;
}
+