Kernel: New handle manager

This handle manager more closely mirrors the behaviour of the CTR-OS
one. In addition object ref-counts and support for DuplicateHandle have
been added.

Note that support for DuplicateHandle is still experimental, since parts
of the kernel still use Handles internally, which will likely cause
troubles if two different handles to the same object are used to e.g.
wait on a synchronization primitive.
This commit is contained in:
Yuri Kunde Schlesner 2014-12-21 10:04:08 -02:00
parent 23f2142009
commit 7e2903cb74
13 changed files with 229 additions and 188 deletions

View file

@ -62,7 +62,8 @@ ResultCode ArbitrateAddress(Handle handle, ArbitrationType type, u32 address, s3
/// Create an address arbiter
AddressArbiter* CreateAddressArbiter(Handle& handle, const std::string& name) {
AddressArbiter* address_arbiter = new AddressArbiter;
handle = Kernel::g_handle_table.Create(address_arbiter);
// TOOD(yuriks): Fix error reporting
handle = Kernel::g_handle_table.Create(address_arbiter).ValueOr(INVALID_HANDLE);
address_arbiter->name = name;
return address_arbiter;
}

View file

@ -129,7 +129,8 @@ ResultCode ClearEvent(Handle handle) {
Event* CreateEvent(Handle& handle, const ResetType reset_type, const std::string& name) {
Event* evt = new Event;
handle = Kernel::g_handle_table.Create(evt);
// TOOD(yuriks): Fix error reporting
handle = Kernel::g_handle_table.Create(evt).ValueOr(INVALID_HANDLE);
evt->locked = true;
evt->permanent_locked = false;

View file

@ -17,73 +17,89 @@ HandleTable g_handle_table;
u64 g_program_id = 0;
HandleTable::HandleTable() {
next_id = INITIAL_NEXT_ID;
next_generation = 1;
Clear();
}
Handle HandleTable::Create(Object* obj, int range_bottom, int range_top) {
if (range_top > MAX_COUNT) {
range_top = MAX_COUNT;
ResultVal<Handle> HandleTable::Create(Object* obj) {
_dbg_assert_(Kernel, obj != nullptr);
u16 slot = next_free_slot;
if (slot >= generations.size()) {
LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use.");
return ERR_OUT_OF_HANDLES;
}
if (next_id >= range_bottom && next_id < range_top) {
range_bottom = next_id++;
next_free_slot = generations[slot];
u16 generation = next_generation++;
// Overflow count so it fits in the 15 bits dedicated to the generation in the handle.
// CTR-OS doesn't use generation 0, so skip straight to 1.
if (next_generation >= (1 << 15)) next_generation = 1;
generations[slot] = generation;
intrusive_ptr_add_ref(obj);
objects[slot] = obj;
Handle handle = generation | (slot << 15);
obj->handle = handle;
return MakeResult<Handle>(handle);
}
ResultVal<Handle> HandleTable::Duplicate(Handle handle) {
Object* object = GetGeneric(handle);
if (object == nullptr) {
LOG_ERROR(Kernel, "Tried to duplicate invalid handle: %08X", handle);
return ERR_INVALID_HANDLE;
}
for (int i = range_bottom; i < range_top; i++) {
if (!occupied[i]) {
occupied[i] = true;
pool[i] = obj;
pool[i]->handle = i + HANDLE_OFFSET;
return i + HANDLE_OFFSET;
}
}
LOG_ERROR(Kernel, "Unable to allocate kernel object, too many objects slots in use.");
return 0;
return Create(object);
}
ResultCode HandleTable::Close(Handle handle) {
if (!IsValid(handle))
return ERR_INVALID_HANDLE;
size_t slot = GetSlot(handle);
u16 generation = GetGeneration(handle);
intrusive_ptr_release(objects[slot]);
objects[slot] = nullptr;
generations[generation] = next_free_slot;
next_free_slot = slot;
return RESULT_SUCCESS;
}
bool HandleTable::IsValid(Handle handle) const {
int index = handle - HANDLE_OFFSET;
if (index < 0)
return false;
if (index >= MAX_COUNT)
return false;
size_t slot = GetSlot(handle);
u16 generation = GetGeneration(handle);
return occupied[index];
return slot < MAX_COUNT && objects[slot] != nullptr && generations[slot] == generation;
}
Object* HandleTable::GetGeneric(Handle handle) const {
if (handle == CurrentThread) {
// TODO(yuriks) Directly return the pointer once this is possible.
handle = GetCurrentThreadHandle();
} else if (handle == CurrentProcess) {
LOG_ERROR(Kernel, "Current process (%08X) pseudo-handle not supported", CurrentProcess);
return nullptr;
}
if (!IsValid(handle)) {
return nullptr;
}
return objects[GetSlot(handle)];
}
void HandleTable::Clear() {
for (int i = 0; i < MAX_COUNT; i++) {
//brutally clear everything, no validation
if (occupied[i])
delete pool[i];
occupied[i] = false;
for (size_t i = 0; i < MAX_COUNT; ++i) {
generations[i] = i + 1;
if (objects[i] != nullptr)
intrusive_ptr_release(objects[i]);
objects[i] = nullptr;
}
pool.fill(nullptr);
next_id = INITIAL_NEXT_ID;
}
Object* &HandleTable::operator [](Handle handle)
{
_dbg_assert_msg_(Kernel, IsValid(handle), "GRABBING UNALLOCED KERNEL OBJ");
return pool[handle - HANDLE_OFFSET];
}
void HandleTable::List() {
for (int i = 0; i < MAX_COUNT; i++) {
if (occupied[i]) {
if (pool[i]) {
LOG_DEBUG(Kernel, "KO %i: %s \"%s\"", i + HANDLE_OFFSET, pool[i]->GetTypeName().c_str(),
pool[i]->GetName().c_str());
}
}
}
}
int HandleTable::GetCount() const {
return std::count(occupied.begin(), occupied.end(), true);
}
Object* HandleTable::CreateByIDType(int type) {
LOG_ERROR(Kernel, "Unimplemented: %d.", type);
return nullptr;
next_free_slot = 0;
}
/// Initialize the kernel

View file

@ -12,13 +12,17 @@
typedef u32 Handle;
typedef s32 Result;
const Handle INVALID_HANDLE = 0;
namespace Kernel {
// From kernel.h. Declarations duplicated here to avoid a circular header dependency.
class Thread;
Thread* GetCurrentThread();
// TODO: Verify code
const ResultCode ERR_OUT_OF_HANDLES(ErrorDescription::OutOfMemory, ErrorModule::Kernel,
ErrorSummary::OutOfResource, ErrorLevel::Temporary);
// TOOD: Verify code
const ResultCode ERR_INVALID_HANDLE = InvalidHandle(ErrorModule::Kernel);
enum KernelHandle {
enum KernelHandle : Handle {
CurrentThread = 0xFFFF8000,
CurrentProcess = 0xFFFF8001,
};
@ -61,103 +65,127 @@ public:
LOG_ERROR(Kernel, "(UNIMPLEMENTED)");
return UnimplementedFunction(ErrorModule::Kernel);
}
};
class HandleTable : NonCopyable {
public:
HandleTable();
~HandleTable() {}
// Allocates a handle within the range and inserts the object into the map.
Handle Create(Object* obj, int range_bottom=INITIAL_NEXT_ID, int range_top=0x7FFFFFFF);
static Object* CreateByIDType(int type);
template <class T>
void Destroy(Handle handle) {
if (Get<T>(handle)) {
occupied[handle - HANDLE_OFFSET] = false;
delete pool[handle - HANDLE_OFFSET];
}
}
bool IsValid(Handle handle) const;
template <class T>
T* Get(Handle handle) {
if (handle == CurrentThread) {
return reinterpret_cast<T*>(GetCurrentThread());
}
if (handle < HANDLE_OFFSET || handle >= HANDLE_OFFSET + MAX_COUNT || !occupied[handle - HANDLE_OFFSET]) {
if (handle != 0) {
LOG_ERROR(Kernel, "Bad object handle %08x", handle);
}
return nullptr;
} else {
Object* t = pool[handle - HANDLE_OFFSET];
if (t->GetHandleType() != T::HANDLE_TYPE) {
LOG_ERROR(Kernel, "Wrong object type for %08x", handle);
return nullptr;
}
return static_cast<T*>(t);
}
}
// ONLY use this when you know the handle is valid.
template <class T>
T *GetFast(Handle handle) {
if (handle == CurrentThread) {
return reinterpret_cast<T*>(GetCurrentThread());
}
const Handle realHandle = handle - HANDLE_OFFSET;
_dbg_assert_(Kernel, realHandle >= 0 && realHandle < MAX_COUNT && occupied[realHandle]);
return static_cast<T*>(pool[realHandle]);
}
template <class T, typename ArgT>
void Iterate(bool func(T*, ArgT), ArgT arg) {
int type = T::GetStaticIDType();
for (int i = 0; i < MAX_COUNT; i++)
{
if (!occupied[i])
continue;
T* t = static_cast<T*>(pool[i]);
if (t->GetIDType() == type) {
if (!func(t, arg))
break;
}
}
}
bool GetIDType(Handle handle, HandleType* type) const {
if ((handle < HANDLE_OFFSET) || (handle >= HANDLE_OFFSET + MAX_COUNT) ||
!occupied[handle - HANDLE_OFFSET]) {
LOG_ERROR(Kernel, "Bad object handle %08X", handle);
return false;
}
Object* t = pool[handle - HANDLE_OFFSET];
*type = t->GetHandleType();
return true;
}
Object* &operator [](Handle handle);
void List();
void Clear();
int GetCount() const;
private:
friend void intrusive_ptr_add_ref(Object*);
friend void intrusive_ptr_release(Object*);
enum {
MAX_COUNT = 0x1000,
HANDLE_OFFSET = 0x100,
INITIAL_NEXT_ID = 0x10,
};
unsigned int ref_count = 0;
};
std::array<Object*, MAX_COUNT> pool;
std::array<bool, MAX_COUNT> occupied;
int next_id;
// Special functions that will later be used by boost::instrusive_ptr to do automatic ref-counting
inline void intrusive_ptr_add_ref(Object* object) {
++object->ref_count;
}
inline void intrusive_ptr_release(Object* object) {
if (--object->ref_count == 0) {
delete object;
}
}
/**
* This class allows the creation of Handles, which are references to objects that can be tested
* for validity and looked up. Here they are used to pass references to kernel objects to/from the
* emulated process. it has been designed so that it follows the same handle format and has
* approximately the same restrictions as the handle manager in the CTR-OS.
*
* Handles contain two sub-fields: a slot index (bits 31:15) and a generation value (bits 14:0).
* The slot index is used to index into the arrays in this class to access the data corresponding
* to the Handle.
*
* To prevent accidental use of a freed Handle whose slot has already been reused, a global counter
* is kept and incremented every time a Handle is created. This is the Handle's "generation". The
* value of the counter is stored into the Handle as well as in the handle table (in the
* "generations" array). When looking up a handle, the Handle's generation must match with the
* value stored on the class, otherwise the Handle is considered invalid.
*
* To find free slots when allocating a Handle without needing to scan the entire object array, the
* generations field of unallocated slots is re-purposed as a linked list of indices to free slots.
* When a Handle is created, an index is popped off the list and used for the new Handle. When it
* is destroyed, it is again pushed onto the list to be re-used by the next allocation. It is
* likely that this allocation strategy differs from the one used in CTR-OS, but this hasn't been
* verified and isn't likely to cause any problems.
*/
class HandleTable final : NonCopyable {
public:
HandleTable();
/**
* Allocates a handle for the given object.
* @return The created Handle or one of the following errors:
* - `ERR_OUT_OF_HANDLES`: the maximum number of handles has been exceeded.
*/
ResultVal<Handle> Create(Object* obj);
/**
* Returns a new handle that points to the same object as the passed in handle.
* @return The duplicated Handle or one of the following errors:
* - `ERR_INVALID_HANDLE`: an invalid handle was passed in.
* - Any errors returned by `Create()`.
*/
ResultVal<Handle> Duplicate(Handle handle);
/**
* Closes a handle, removing it from the table and decreasing the object's ref-count.
* @return `RESULT_SUCCESS` or one of the following errors:
* - `ERR_INVALID_HANDLE`: an invalid handle was passed in.
*/
ResultCode Close(Handle handle);
/// Checks if a handle is valid and points to an existing object.
bool IsValid(Handle handle) const;
/**
* Looks up a handle.
* @returns Pointer to the looked-up object, or `nullptr` if the handle is not valid.
*/
Object* GetGeneric(Handle handle) const;
/**
* Looks up a handle while verifying its type.
* @returns Pointer to the looked-up object, or `nullptr` if the handle is not valid or its
* type differs from the handle type `T::HANDLE_TYPE`.
*/
template <class T>
T* Get(Handle handle) const {
Object* object = GetGeneric(handle);
if (object != nullptr && object->GetHandleType() == T::HANDLE_TYPE) {
return static_cast<T*>(object);
}
return nullptr;
}
/// Closes all handles held in this table.
void Clear();
private:
/**
* This is the maximum limit of handles allowed per process in CTR-OS. It can be further
* reduced by ExHeader values, but this is not emulated here.
*/
static const size_t MAX_COUNT = 4096;
static size_t GetSlot(Handle handle) { return handle >> 15; }
static u16 GetGeneration(Handle handle) { return handle & 0x7FFF; }
/// Stores the Object referenced by the handle or null if the slot is empty.
std::array<Object*, MAX_COUNT> objects;
/**
* The value of `next_generation` when the handle was created, used to check for validity. For
* empty slots, contains the index of the next free slot in the list.
*/
std::array<u16, MAX_COUNT> generations;
/**
* Global counter of the number of created handles. Stored in `generations` when a handle is
* created, and wraps around to 1 when it hits 0x8000.
*/
u16 next_generation;
/// Head of the free slots linked list.
u16 next_free_slot;
};
extern HandleTable g_handle_table;

View file

@ -87,7 +87,7 @@ void ReleaseThreadMutexes(Handle thread) {
// Release every mutex that the thread holds, and resume execution on the waiting threads
for (MutexMap::iterator iter = locked.first; iter != locked.second; ++iter) {
Mutex* mutex = g_handle_table.GetFast<Mutex>(iter->second);
Mutex* mutex = g_handle_table.Get<Mutex>(iter->second);
ResumeWaitingThread(mutex);
}
@ -136,7 +136,8 @@ ResultCode ReleaseMutex(Handle handle) {
*/
Mutex* CreateMutex(Handle& handle, bool initial_locked, const std::string& name) {
Mutex* mutex = new Mutex;
handle = Kernel::g_handle_table.Create(mutex);
// TODO(yuriks): Fix error reporting
handle = Kernel::g_handle_table.Create(mutex).ValueOr(INVALID_HANDLE);
mutex->locked = mutex->initial_locked = initial_locked;
mutex->name = name;

View file

@ -57,7 +57,8 @@ ResultCode CreateSemaphore(Handle* handle, s32 initial_count,
ErrorSummary::WrongArgument, ErrorLevel::Permanent);
Semaphore* semaphore = new Semaphore;
*handle = g_handle_table.Create(semaphore);
// TOOD(yuriks): Fix error reporting
*handle = g_handle_table.Create(semaphore).ValueOr(INVALID_HANDLE);
// When the semaphore is created, some slots are reserved for other threads,
// and the rest is reserved for the caller thread

View file

@ -32,7 +32,8 @@ public:
*/
SharedMemory* CreateSharedMemory(Handle& handle, const std::string& name) {
SharedMemory* shared_memory = new SharedMemory;
handle = Kernel::g_handle_table.Create(shared_memory);
// TOOD(yuriks): Fix error reporting
handle = Kernel::g_handle_table.Create(shared_memory).ValueOr(INVALID_HANDLE);
shared_memory->name = name;
return shared_memory;
}

View file

@ -341,7 +341,8 @@ Thread* CreateThread(Handle& handle, const char* name, u32 entry_point, s32 prio
Thread* thread = new Thread;
handle = Kernel::g_handle_table.Create(thread);
// TOOD(yuriks): Fix error reporting
handle = Kernel::g_handle_table.Create(thread).ValueOr(INVALID_HANDLE);
thread_queue.push_back(handle);
thread_ready_queue.prepare(priority);

View file

@ -77,9 +77,6 @@ Handle ArbitrateHighestPriorityThread(u32 arbiter, u32 address);
/// Arbitrate all threads currently waiting...
void ArbitrateAllThreads(u32 arbiter, u32 address);
/// Gets the current thread
Thread* GetCurrentThread();
/// Gets the current thread handle
Handle GetCurrentThreadHandle();

View file

@ -133,7 +133,7 @@ public:
case FileCommand::Close:
{
LOG_TRACE(Service_FS, "Close %s %s", GetTypeName().c_str(), GetName().c_str());
Kernel::g_handle_table.Destroy<File>(GetHandle());
backend->Close();
break;
}
@ -189,7 +189,7 @@ public:
case DirectoryCommand::Close:
{
LOG_TRACE(Service_FS, "Close %s %s", GetTypeName().c_str(), GetName().c_str());
Kernel::g_handle_table.Destroy<Directory>(GetHandle());
backend->Close();
break;
}
@ -283,7 +283,8 @@ ResultVal<Handle> OpenFileFromArchive(ArchiveHandle archive_handle, const FileSy
}
auto file = Common::make_unique<File>(std::move(backend), path);
Handle handle = Kernel::g_handle_table.Create(file.release());
// TOOD(yuriks): Fix error reporting
Handle handle = Kernel::g_handle_table.Create(file.release()).ValueOr(INVALID_HANDLE);
return MakeResult<Handle>(handle);
}
@ -388,7 +389,8 @@ ResultVal<Handle> OpenDirectoryFromArchive(ArchiveHandle archive_handle, const F
}
auto directory = Common::make_unique<Directory>(std::move(backend), path);
Handle handle = Kernel::g_handle_table.Create(directory.release());
// TOOD(yuriks): Fix error reporting
Handle handle = Kernel::g_handle_table.Create(directory.release()).ValueOr(INVALID_HANDLE);
return MakeResult<Handle>(handle);
}

View file

@ -56,7 +56,8 @@ Manager::~Manager() {
/// Add a service to the manager (does not create it though)
void Manager::AddService(Interface* service) {
m_port_map[service->GetPortName()] = Kernel::g_handle_table.Create(service);
// TOOD(yuriks): Fix error reporting
m_port_map[service->GetPortName()] = Kernel::g_handle_table.Create(service).ValueOr(INVALID_HANDLE);
m_services.push_back(service);
}

View file

@ -54,7 +54,8 @@ public:
/// Allocates a new handle for the service
Handle CreateHandle(Kernel::Object *obj) {
Handle handle = Kernel::g_handle_table.Create(obj);
// TODO(yuriks): Fix error reporting
Handle handle = Kernel::g_handle_table.Create(obj).ValueOr(INVALID_HANDLE);
m_handles.push_back(handle);
return handle;
}
@ -62,7 +63,7 @@ public:
/// Frees a handle from the service
template <class T>
void DeleteHandle(const Handle handle) {
Kernel::g_handle_table.Destroy<T>(handle);
Kernel::g_handle_table.Close(handle);
m_handles.erase(std::remove(m_handles.begin(), m_handles.end(), handle), m_handles.end());
}

View file

@ -119,11 +119,9 @@ static Result WaitSynchronization1(Handle handle, s64 nano_seconds) {
// TODO(bunnei): Do something with nano_seconds, currently ignoring this
bool wait_infinite = (nano_seconds == -1); // Used to wait until a thread has terminated
if (!Kernel::g_handle_table.IsValid(handle)) {
Kernel::Object* object = Kernel::g_handle_table.GetGeneric(handle);
if (object == nullptr)
return InvalidHandle(ErrorModule::Kernel).raw;
}
Kernel::Object* object = Kernel::g_handle_table.GetFast<Kernel::Object>(handle);
_dbg_assert_(Kernel, object != nullptr);
LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle, object->GetTypeName().c_str(),
object->GetName().c_str(), nano_seconds);
@ -150,10 +148,9 @@ static Result WaitSynchronizationN(s32* out, Handle* handles, s32 handle_count,
// Iterate through each handle, synchronize kernel object
for (s32 i = 0; i < handle_count; i++) {
if (!Kernel::g_handle_table.IsValid(handles[i])) {
Kernel::Object* object = Kernel::g_handle_table.GetGeneric(handles[i]);
if (object == nullptr)
return InvalidHandle(ErrorModule::Kernel).raw;
}
Kernel::Object* object = Kernel::g_handle_table.GetFast<Kernel::Object>(handles[i]);
LOG_TRACE(Kernel_SVC, "\thandle[%d] = 0x%08X(%s:%s)", i, handles[i], object->GetTypeName().c_str(),
object->GetName().c_str());
@ -321,19 +318,12 @@ static Result CreateEvent(Handle* evt, u32 reset_type) {
/// Duplicates a kernel handle
static Result DuplicateHandle(Handle* out, Handle handle) {
LOG_WARNING(Kernel_SVC, "(STUBBED) called handle=0x%08X", handle);
// Translate kernel handles -> real handles
if (handle == Kernel::CurrentThread) {
handle = Kernel::GetCurrentThreadHandle();
ResultVal<Handle> out_h = Kernel::g_handle_table.Duplicate(handle);
if (out_h.Succeeded()) {
*out = *out_h;
LOG_TRACE(Kernel_SVC, "duplicated 0x%08X to 0x%08X", handle, *out);
}
_assert_msg_(KERNEL, (handle != Kernel::CurrentProcess),
"(UNIMPLEMENTED) process handle duplication!");
// TODO(bunnei): FixMe - This is a hack to return the handle that we were asked to duplicate.
*out = handle;
return 0;
return out_h.Code().raw;
}
/// Signals an event