hw: reference count capabilities in UTCBs

When capabilities are delegated to components, they are added to the UTCB of the
target thread. Before the thread is able to take out the capability id out of
the UTCB and adapt the user-level capability reference counter, it might happen
that another thread of the same component deletes the same capability because
its user-level reference counter reached zero. If the kernel then destroys the
capability, before the same capability id is taken out of all UTCBs, an
inconsitent view in the component is the result.  To keep an consistent view in
the multi-threading scenario, the kernel now counts how often it puts a
capability into a UTCB. The threads on the other hand hint the kernel when they
took capabilities out of the UTCB, so the kernel can decrement the counter
again. Only when the counter is zero, capabilities can get destructed.

Fix #1623
This commit is contained in:
Stefan Kalkowski 2015-12-01 14:50:14 +01:00 committed by Christian Helmuth
parent 41b9f6bd03
commit 60ba210a6b
7 changed files with 38 additions and 11 deletions

View File

@ -161,7 +161,10 @@ class Genode::Native_utcb
void copy_to(Msgbuf_base &o)
{
o._snd_cap_cnt = _cap_cnt;
for (unsigned i = 0; i < _cap_cnt; i++) o._caps[i] = _caps[i];
for (unsigned i = 0; i < _cap_cnt; i++) {
o._caps[i] = _caps[i];
if (o._caps[i].valid()) Kernel::ack_cap(o._caps[i].dst());
}
memcpy(o.buf, _buf, min(_size, o._size));
}

View File

@ -41,7 +41,8 @@ namespace Kernel
constexpr Call_arg call_id_print_char() { return 10; }
constexpr Call_arg call_id_update_data_region() { return 11; }
constexpr Call_arg call_id_update_instr_region() { return 12; }
constexpr Call_arg call_id_delete_cap() { return 13; }
constexpr Call_arg call_id_ack_cap() { return 13; }
constexpr Call_arg call_id_delete_cap() { return 14; }
/*****************************************************************
@ -266,6 +267,16 @@ namespace Kernel
return call(call_id_kill_signal_context(), context);
}
/**
* Acknowledge reception of a capability
*
* \param cap capability id to acknowledge
*/
inline void ack_cap(capid_t const cap)
{
call(call_id_ack_cap(), cap);
}
/**
* Delete a capability id
*

View File

@ -106,6 +106,7 @@ class Kernel::Object_identity_reference
capid_t _capid;
Object_identity *_identity;
Pd &_pd;
unsigned short _in_utcbs;
public:
@ -125,6 +126,10 @@ class Kernel::Object_identity_reference
Pd & pd() { return _pd; }
capid_t capid() { return _capid; }
void add_to_utcb() { _in_utcbs++; }
void remove_from_utcb() { _in_utcbs--; }
bool in_utcb() { return _in_utcbs > 0; }
void invalidate();

View File

@ -244,6 +244,7 @@ class Kernel::Thread
void _call_ack_irq();
void _call_new_obj();
void _call_delete_obj();
void _call_ack_cap();
void _call_delete_cap();
template <typename T, typename... ARGS>

View File

@ -47,13 +47,6 @@ void Ipc_node::copy_msg(Ipc_node * const sender)
continue;
}
/* within the same pd, we can simply copy the id */
if (pd() == sender->pd()) {
_utcb->cap_add(id);
pd()->platform_pd()->capability_slab().free(_obj_id_ref_ptr[i]);
continue;
}
/* lookup the capability id within the caller's cap space */
Reference *oir = (id == cap_id_invalid())
? nullptr : sender->pd()->cap_tree().find(id);
@ -76,6 +69,8 @@ void Ipc_node::copy_msg(Ipc_node * const sender)
} else /* otherwise free the pre-allocation */
pd()->platform_pd()->capability_slab().free(_obj_id_ref_ptr[i]);
if (dst_oir) dst_oir->add_to_utcb();
/* add the translated capability id to the target buffer */
_utcb->cap_add(dst_oir ? dst_oir->capid() : cap_id_invalid());
}

View File

@ -82,7 +82,7 @@ void Object_identity_reference::invalidate() {
Object_identity_reference::Object_identity_reference(Object_identity *oi,
Pd &pd)
: _capid(pd.capid_alloc().alloc()), _identity(oi), _pd(pd)
: _capid(pd.capid_alloc().alloc()), _identity(oi), _pd(pd), _in_utcbs(0)
{
if (_identity) _identity->insert(this);
_pd.cap_tree().insert(this);

View File

@ -585,10 +585,21 @@ void Thread::_call_delete_obj()
}
void Thread::_call_ack_cap()
{
Object_identity_reference * oir = pd()->cap_tree().find(user_arg_1());
if (oir) oir->remove_from_utcb();
}
void Thread::_call_delete_cap()
{
Object_identity_reference * oir = pd()->cap_tree().find(user_arg_1());
if (oir) destroy(pd()->platform_pd()->capability_slab(), oir);
if (!oir) return;
if (oir->in_utcb()) return;
destroy(pd()->platform_pd()->capability_slab(), oir);
}
@ -612,6 +623,7 @@ void Thread::_call()
case call_id_await_signal(): _call_await_signal(); return;
case call_id_ack_signal(): _call_ack_signal(); return;
case call_id_print_char(): _call_print_char(); return;
case call_id_ack_cap(): _call_ack_cap(); return;
case call_id_delete_cap(): _call_delete_cap(); return;
default:
/* check wether this is a core thread */