From: John Hodge Date: Sun, 27 Feb 2011 13:54:09 +0000 (+0800) Subject: Kernel - Implementing select() X-Git-Tag: rel0.10~189 X-Git-Url: https://git.ucc.asn.au/?p=tpg%2Facess2.git;a=commitdiff_plain;h=e573d30162c179edc6de7b7c8364a617d9f25f77 Kernel - Implementing select() - Fully untested, but it does compile - TODO: Complete the helpers (flag setting), and port blocking calls to use select() instead of using their own semaphores. - TODO: Implement an alarm in threads (or timer) to implement the timeout argument to select() --- diff --git a/Design Notes/VFS - Select.txt b/Design Notes/VFS - Select.txt index cceef5ce..70e1993c 100644 --- a/Design Notes/VFS - Select.txt +++ b/Design Notes/VFS - Select.txt @@ -10,3 +10,16 @@ The VFS function select() - Maintains a list of processes on each node (if select has been called on that node) - Each process maybe has a semaphore on it (to use the semaphore code to maintain the process list) > Could maybe use a mutex instead + + +VFS_Select(int, fd_set* read, fd_set* write, fd_set* except) + +read is the set of sockets that we are waiting to read from +write " " " be able to write to +except " " " for possible errors on + + +Hence, each VFS_Node has three listener lists (or just pointers) +- One for when data is avaliable +- One for when space is avaliable for writing +- One for when an error occurs (closed pipe, etc ...) diff --git a/Kernel/Makefile b/Kernel/Makefile index 39e41ddc..72590b8f 100644 --- a/Kernel/Makefile +++ b/Kernel/Makefile @@ -32,7 +32,7 @@ OBJ += messages.o modules.o syscalls.o system.o threads.o OBJ += $(addprefix vfs/fs/, $(addsuffix .o,$(FILESYSTEMS))) OBJ += drv/kb.o drv/vterm.o drv/proc.o drv/fifo.o drv/iocache.o drv/dma.o drv/pci.o drv/vga.o OBJ += binary.o bin/elf.o bin/pe.o -OBJ += vfs/main.o vfs/open.o vfs/acls.o vfs/dir.o vfs/io.o vfs/mount.o vfs/memfile.o vfs/nodecache.o vfs/handle.o +OBJ += vfs/main.o vfs/open.o vfs/acls.o vfs/dir.o vfs/io.o vfs/mount.o vfs/memfile.o vfs/nodecache.o vfs/handle.o vfs/select.o OBJ += vfs/fs/root.o vfs/fs/devfs.o OBJ += $(addprefix drv/, $(addsuffix .o,$(DRIVERS))) OBJ := $(addsuffix .$(ARCH), $(OBJ)) diff --git a/Kernel/include/acess.h b/Kernel/include/acess.h index 4286f5ee..a2d406b5 100644 --- a/Kernel/include/acess.h +++ b/Kernel/include/acess.h @@ -20,6 +20,7 @@ typedef int tTID; typedef Uint tUID; typedef Uint tGID; typedef Sint64 tTimestamp; +typedef Sint64 tTime; typedef struct sShortSpinlock tShortSpinlock; typedef struct sMutex tMutex; diff --git a/Kernel/include/vfs.h b/Kernel/include/vfs.h index 8618a048..e37ebfec 100644 --- a/Kernel/include/vfs.h +++ b/Kernel/include/vfs.h @@ -21,6 +21,11 @@ #include +/** + * \brief Thread list datatype for VFS_Select + */ +typedef struct sVFS_SelectList tVFS_SelectList; + /** * \name tVFS_Node Flags * \brief Flag values for tVFS_Node.Flags @@ -127,7 +132,7 @@ typedef struct sVFS_Node */ /** - * \name Access controll + * \name Access control * \{ */ tUID UID; //!< ID of Owning User @@ -139,6 +144,20 @@ typedef struct sVFS_Node * \} */ + /** + * \name VFS_Select() fields + * \{ + */ + int DataAvaliable; + tVFS_SelectList *ReadThreads; //!< Threads waiting to read + int BufferFull; + tVFS_SelectList *WriteThreads; //!< Threads waiting to write + int ErrorOccurred; + tVFS_SelectList *ErrorThreads; //!< Threads waiting for an error + /** + * \} + */ + /** * \name Common Functions * \brief Functions that are used no matter the value of .Flags diff --git a/Kernel/include/vfs_ext.h b/Kernel/include/vfs_ext.h index 720bcbe0..acee1645 100644 --- a/Kernel/include/vfs_ext.h +++ b/Kernel/include/vfs_ext.h @@ -29,6 +29,9 @@ //! Marks a VFS handle as belonging to the kernel #define VFS_KERNEL_FLAG 0x40000000 +//! Architectual maximum number of file descriptors +#define MAX_FILE_DESCS 128 + /** * \brief VFS_Seek directions */ @@ -104,6 +107,18 @@ typedef struct sFInfo tVFS_ACL acls[]; //!< ACL buffer (size is passed in the \a MaxACLs argument to VFS_FInfo) } tFInfo; +/** + * \brief fd_set for select() + */ +typedef struct +{ + Uint16 flags[MAX_FILE_DESCS/16]; +} fd_set; + +#define FD_CLR(fd, fdsetp) ((fdsetp)->flags[(fd)/16]&=~(1<<((fd)%16))) +#define FD_SET(fd, fdsetp) ((fdsetp)->flags[(fd)/16]|=~(1<<((fd)%16))) +#define FD_ISSET(fd, fdsetp) ((fdsetp)->flags[(fd)/16]&(1<<((fd)%16))) + // === FUNCTIONS === /** * \brief Initialise the VFS (called by system.c) diff --git a/Kernel/threads.c b/Kernel/threads.c index ee48e3e0..cafb8068 100644 --- a/Kernel/threads.c +++ b/Kernel/threads.c @@ -1312,6 +1312,7 @@ int Mutex_Acquire(tMutex *Mutex) /** * \brief Release a held mutex * \param Mutex Mutex to release + * \note Releasing a non-held mutex has no effect */ void Mutex_Release(tMutex *Mutex) { diff --git a/Kernel/vfs/select.c b/Kernel/vfs/select.c new file mode 100644 index 00000000..46d6f39f --- /dev/null +++ b/Kernel/vfs/select.c @@ -0,0 +1,339 @@ +/* + * Acess2 VFS + * - By thePowersGang (John Hodge) + * + * select.c + * - Implements the select() system call (and supporting code) + */ +#define DEBUG 0 +#include +#include "vfs.h" +#include "vfs_int.h" +#include "vfs_ext.h" +#include + +// === CONSTANTS === +#define NUM_THREADS_PER_ALLOC 4 + +// === TYPES === +typedef struct sVFS_SelectThread tVFS_SelectThread; +typedef struct sVFS_SelectListEnt tVFS_SelectListEnt; + +// === STRUCTURES === +struct sVFS_SelectListEnt +{ + tVFS_SelectListEnt *Next; + tVFS_SelectThread *Threads[NUM_THREADS_PER_ALLOC]; +}; + +struct sVFS_SelectList +{ + tMutex Lock; + tVFS_SelectListEnt FirstEnt; +}; + +struct sVFS_SelectThread +{ + //! \brief Marks the thread as actively using this select + int IsActive; + //! \brief Semaphore to atomically put the listener to sleep + tSemaphore SleepHandle; // TODO: Allow timeouts (by setting an alarm?) +}; + +// === PROTOTYPES === + int VFS_Select(int MaxHandle, fd_set *ReadHandles, fd_set *WriteHandles, fd_set *ErrHandles, tTime *Timeout, int IsKernel); + int VFS_MarkFull(tVFS_Node *Node, BOOL IsBufferFull); + int VFS_MarkAvaliable(tVFS_Node *Node, BOOL IsDataAvaliable); + int VFS_MarkError(tVFS_Node *Node, BOOL IsErrorState); + int VFS_int_Select_Register(tVFS_SelectThread *Thread, int MaxHandle, fd_set *Handles, int Type, BOOL IsKernel); + int VFS_int_Select_Deregister(tVFS_SelectThread *Thread, int MaxHandle, fd_set *Handles, int Type, BOOL IsKernel); + int VFS_int_Select_AddThread(tVFS_SelectList *List, tVFS_SelectThread *Thread, int MaxAllowed); +void VFS_int_Select_RemThread(tVFS_SelectList *List, tVFS_SelectThread *Thread); + +// === GLOBALS === + +// === FUNCTIONS === +int VFS_Select(int MaxHandle, fd_set *ReadHandles, fd_set *WriteHandles, fd_set *ErrHandles, tTime *Timeout, int IsKernel) +{ + tVFS_SelectThread thread_info; + int ret; + + // Register with nodes + ret = VFS_int_Select_Register(&thread_info, MaxHandle, ReadHandles, 0, IsKernel); + ret += VFS_int_Select_Register(&thread_info, MaxHandle, WriteHandles, 1, IsKernel); + ret += VFS_int_Select_Register(&thread_info, MaxHandle, ErrHandles, 2, IsKernel); + + // If there were events waiting, de-register and return + if( ret ) + { + ret = VFS_int_Select_Deregister(&thread_info, MaxHandle, ReadHandles, 0, IsKernel); + ret += VFS_int_Select_Deregister(&thread_info, MaxHandle, ReadHandles, 1, IsKernel); + ret += VFS_int_Select_Deregister(&thread_info, MaxHandle, ReadHandles, 2, IsKernel); + return ret; + } + + // TODO: Implement timeout + + // Wait (only if there is no timeout, or it is greater than zero + if( !Timeout || *Timeout > 0 ) + { + ret = Semaphore_Wait(&thread_info.SleepHandle, 0); + } + + // Fill output (modify *Handles) + // - Also, de-register + ret = VFS_int_Select_Deregister(&thread_info, MaxHandle, ReadHandles, 0, IsKernel); + ret += VFS_int_Select_Deregister(&thread_info, MaxHandle, ReadHandles, 1, IsKernel); + ret += VFS_int_Select_Deregister(&thread_info, MaxHandle, ReadHandles, 2, IsKernel); + return ret; +} + +// --- Internal --- +/** + * \return Number of files with an action + */ +int VFS_int_Select_Register(tVFS_SelectThread *Thread, int MaxHandle, fd_set *Handles, int Type, BOOL IsKernel) +{ + int i, numFlagged = 0; + tVFS_SelectList **list; + int *flag, wantedFlagValue; + int maxAllowed; + + if( !Handles ) return 0; + + for( i = 0; i < MaxHandle; i ++ ) + { + tVFS_Handle *handle; + + // Is the descriptor set + if( !FD_ISSET(i, Handles) ) continue; + + handle = VFS_GetHandle( i | (IsKernel?VFS_KERNEL_FLAG:0) ); + // Is the handle valid? + if( !handle || !handle->Node ) + { + if( Type == 2 ) { // Bad FD counts as an error + numFlagged ++; + } + else { + FD_CLR(i, Handles); + } + continue; + } + + // Get the type of the listen + switch(Type) + { + case 0: // Read + list = &handle->Node->ReadThreads; + flag = &handle->Node->DataAvaliable; + wantedFlagValue = 1; + maxAllowed = 1; // Max of 1 for read + break; + case 1: // Write + list = &handle->Node->WriteThreads; + flag = &handle->Node->BufferFull; + wantedFlagValue = 0; + maxAllowed = 1; // Max of 1 for write + break; + case 2: // Error + list = &handle->Node->ErrorThreads; + flag = &handle->Node->ErrorOccurred; + wantedFlagValue = 1; + maxAllowed = -1; // No max for error listeners + break; + default: + Log_Error("VFS", "VFS_int_Select_Deregister: BUG CHECK, Unknown Type %i", Type); + return 0; + } + + // Alloc if needed + if( !*list ) { + *list = calloc(1, sizeof(tVFS_SelectList)); + } + + // Register + if( VFS_int_Select_AddThread(*list, Thread, maxAllowed ) ) + { + // Oops, error (or just no space) + FD_CLR(i, Handles); + } + + // Check for the flag + if( !!*flag == !!wantedFlagValue ) + numFlagged ++; + } + + return numFlagged; +} +/** + * \return Number of files with an action + */ +int VFS_int_Select_Deregister(tVFS_SelectThread *Thread, int MaxHandle, fd_set *Handles, int Type, BOOL IsKernel) +{ + int i, numFlagged = 0; + tVFS_SelectList **list; + int *flag, wantedFlagValue; + + if( !Handles ) return 0; + + for( i = 0; i < MaxHandle; i ++ ) + { + tVFS_Handle *handle; + + // Is the descriptor set + if( !FD_ISSET(i, Handles) ) continue; + + handle = VFS_GetHandle( i | (IsKernel?VFS_KERNEL_FLAG:0) ); + // Is the handle valid? + if( !handle || !handle->Node ) + { + if( Type == 2 ) { // Bad FD counts as an error + numFlagged ++; + } + else { + FD_CLR(i, Handles); + } + continue; + } + + // Get the type of the listen + switch(Type) + { + case 0: // Read + list = &handle->Node->ReadThreads; + flag = &handle->Node->DataAvaliable; + wantedFlagValue = 1; + break; + case 1: // Write + list = &handle->Node->WriteThreads; + flag = &handle->Node->BufferFull; + wantedFlagValue = 0; + break; + case 2: // Error + list = &handle->Node->ErrorThreads; + flag = &handle->Node->ErrorOccurred; + wantedFlagValue = 1; + break; + default: + Log_Error("VFS", "VFS_int_Select_Deregister: BUG CHECK, Unknown Type %i", Type); + return 0; + } + + // Remove + VFS_int_Select_RemThread(*list, Thread ); + + // Check for the flag + if( !!*flag == !!wantedFlagValue ) { + numFlagged ++; + } + else { + FD_CLR(i, Handles); + } + } + + return numFlagged; +} + +/** + * \return Boolean failure + */ +int VFS_int_Select_AddThread(tVFS_SelectList *List, tVFS_SelectThread *Thread, int MaxAllowed) +{ + int i, count = 0; + tVFS_SelectListEnt *block, *prev; + + // Lock to avoid concurrency issues + Mutex_Acquire(&List->Lock); + + block = &List->FirstEnt; + + // Look for free space + do + { + for( i = 0; i < NUM_THREADS_PER_ALLOC; i ++ ) + { + if( block->Threads[i] == NULL ) + { + block->Threads[i] = Thread; + Mutex_Release(&List->Lock); + return 0; + } + count ++; + if( MaxAllowed && count >= MaxAllowed ) { + return 1; + } + } + + prev = block; + block = block->Next; + } while(block); + + // Create new block + block = malloc( sizeof(tVFS_SelectListEnt) ); + if( !block ) { + Log_Warning("VFS", "VFS_int_Select_AddThread: malloc() failed"); + Mutex_Release(&List->Lock); + return -1; + } + block->Next = NULL; + block->Threads[0] = Thread; + for( i = 1; i < NUM_THREADS_PER_ALLOC; i ++ ) + { + block->Threads[i] = NULL; + } + + // Add to list + prev->Next = block; + + // Release + Mutex_Release(&List->Lock); + + return 0; +} + +void VFS_int_Select_RemThread(tVFS_SelectList *List, tVFS_SelectThread *Thread) +{ + int i; + tVFS_SelectListEnt *block, *prev; + + // Lock to avoid concurrency issues + Mutex_Acquire(&List->Lock); + + block = &List->FirstEnt; + + // Look for the thread + do + { + for( i = 0; i < NUM_THREADS_PER_ALLOC; i ++ ) + { + if( block->Threads[i] == Thread ) + { + block->Threads[i] = NULL; + + // Check if this block is empty + if( block != &List->FirstEnt ) + { + for( i = 0; i < NUM_THREADS_PER_ALLOC; i ++ ) + if( block->Threads[i] ) + break; + // If empty, free it + if( i == NUM_THREADS_PER_ALLOC ) { + prev->Next = block->Next; + free(block); + } + //TODO: If not empty, check if it can be merged downwards + } + + Mutex_Release(&List->Lock); + return ; + } + } + + prev = block; + block = block->Next; + } while(block); + + // Not on list, is this an error? +} + diff --git a/Makefile.cfg b/Makefile.cfg index 0ccb74ad..8c5b59b9 100644 --- a/Makefile.cfg +++ b/Makefile.cfg @@ -5,7 +5,7 @@ # Install destination configuration DISTROOT := a:/Acess2 xCP := mcopy -D o -xMKDIR := mmd +xMKDIR := mmd -D s ACESSDIR := $(dir $(lastword $(MAKEFILE_LIST))) ACESSDIR := $(shell cd $(ACESSDIR) && pwd)