Refactor kernel segment bookkeeping.

This commit is contained in:
Jonas 'Sortie' Termansen 2013-08-20 02:23:53 +02:00
parent 68aba3d137
commit d71179b540
7 changed files with 309 additions and 183 deletions

View File

@ -117,6 +117,7 @@ poll.o \
process.o \ process.o \
refcount.o \ refcount.o \
scheduler.o \ scheduler.o \
segment.o \
serialterminal.o \ serialterminal.o \
signal.o \ signal.o \
sound.o \ sound.o \

View File

@ -28,6 +28,7 @@
#include <errno.h> #include <errno.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <sortix/mman.h> #include <sortix/mman.h>
@ -35,6 +36,7 @@
#include <sortix/kernel/platform.h> #include <sortix/kernel/platform.h>
#include <sortix/kernel/memorymanagement.h> #include <sortix/kernel/memorymanagement.h>
#include <sortix/kernel/process.h> #include <sortix/kernel/process.h>
#include <sortix/kernel/segment.h>
#include <sortix/kernel/symbol.h> #include <sortix/kernel/symbol.h>
#include "elf.h" #include "elf.h"
@ -42,25 +44,6 @@
namespace Sortix { namespace Sortix {
namespace ELF { namespace ELF {
static int ToProgramSectionType(int flags)
{
switch ( flags & (PF_X | PF_R | PF_W) )
{
case 0:
return SEG_NONE;
case PF_X:
case PF_X | PF_R:
case PF_X | PF_W:
case PF_X | PF_R | PF_W:
return SEG_TEXT;
case PF_R:
case PF_W:
case PF_R | PF_W:
default:
return SEG_DATA;
}
}
// TODO: This code doesn't respect that the size of program headers and section // TODO: This code doesn't respect that the size of program headers and section
// headers may vary depending on the ELF header and that using a simple // headers may vary depending on the ELF header and that using a simple
// table indexation isn't enough. // table indexation isn't enough.
@ -114,34 +97,47 @@ addr_t Construct32(Process* process, const uint8_t* file, size_t filelen)
assert(pht->offset + pht->filesize < filelen); assert(pht->offset + pht->filesize < filelen);
assert(pht->filesize <= pht->memorysize); assert(pht->filesize <= pht->memorysize);
ProcessSegment* segment = new ProcessSegment;
if ( segment == NULL )
return 0;
segment->position = mapto;
segment->size = Page::AlignUp(mapbytes);
segment->type = ToProgramSectionType(pht->flags);
int prot = PROT_FORK | PROT_KREAD | PROT_KWRITE; int prot = PROT_FORK | PROT_KREAD | PROT_KWRITE;
if ( pht->flags & PF_X ) { prot |= PROT_EXEC; } if ( pht->flags & PF_X ) { prot |= PROT_EXEC; }
if ( pht->flags & PF_R ) { prot |= PROT_READ; } if ( pht->flags & PF_R ) { prot |= PROT_READ; }
if ( pht->flags & PF_W ) { prot |= PROT_WRITE; } if ( pht->flags & PF_W ) { prot |= PROT_WRITE; }
if ( segment->Intersects(process->segments) ) if ( (pht->flags & (PF_X | PF_R | PF_W)) == (PF_R | PF_W) )
prot |= PROT_HEAP;
struct segment segment;
segment.addr = mapto;
segment.size = Page::AlignUp(mapbytes);
segment.prot = prot;
kthread_mutex_lock(&process->segment_lock);
if ( !IsUserspaceSegment(&segment) ||
IsSegmentOverlapping(process, &segment) )
{ {
delete segment; kthread_mutex_unlock(&process->segment_lock);
process->ResetAddressSpace();
return 0; return 0;
} }
if ( !Memory::MapRange(mapto, mapbytes, prot) ) assert(process == CurrentProcess());
// TODO: Memory leak of segment?
return 0;
// Insert our newly allocated memory into the processes segment if ( !Memory::MapRange(segment.addr, segment.size, prot) )
// list such that it can be reclaimed later. {
if ( process->segments ) kthread_mutex_unlock(&process->segment_lock);
process->segments->prev = segment; process->ResetAddressSpace();
segment->next = process->segments; return 0;
process->segments = segment; }
if ( !AddSegment(process, &segment) )
{
Memory::UnmapRange(segment.addr, segment.size);
kthread_mutex_unlock(&process->segment_lock);
process->ResetAddressSpace();
return 0;
}
kthread_mutex_unlock(&process->segment_lock);
// Copy as much data as possible and memset the rest to 0. // Copy as much data as possible and memset the rest to 0.
uint8_t* memdest = (uint8_t*) virtualaddr; uint8_t* memdest = (uint8_t*) virtualaddr;
@ -269,36 +265,47 @@ addr_t Construct64(Process* process, const uint8_t* file, size_t filelen)
assert(pht->offset + pht->filesize < filelen); assert(pht->offset + pht->filesize < filelen);
assert(pht->filesize <= pht->memorysize); assert(pht->filesize <= pht->memorysize);
ProcessSegment* segment = new ProcessSegment;
if ( segment == NULL )
return 0;
segment->position = mapto;
segment->size = Page::AlignUp(mapbytes);
segment->type = ToProgramSectionType(pht->flags);
int prot = PROT_FORK | PROT_KREAD | PROT_KWRITE; int prot = PROT_FORK | PROT_KREAD | PROT_KWRITE;
if ( pht->flags & PF_X ) { prot |= PROT_EXEC; } if ( pht->flags & PF_X ) { prot |= PROT_EXEC; }
if ( pht->flags & PF_R ) { prot |= PROT_READ; } if ( pht->flags & PF_R ) { prot |= PROT_READ; }
if ( pht->flags & PF_W ) { prot |= PROT_WRITE; } if ( pht->flags & PF_W ) { prot |= PROT_WRITE; }
if ( segment->Intersects(process->segments) ) if ( (pht->flags & (PF_X | PF_R | PF_W)) == (PF_R | PF_W) )
prot |= PROT_HEAP;
struct segment segment;
segment.addr = mapto;
segment.size = Page::AlignUp(mapbytes);
segment.prot = prot;
kthread_mutex_lock(&process->segment_lock);
if ( !IsUserspaceSegment(&segment) ||
IsSegmentOverlapping(process, &segment) )
{ {
delete segment; kthread_mutex_unlock(&process->segment_lock);
process->ResetAddressSpace();
return 0; return 0;
} }
if ( !Memory::MapRange(mapto, mapbytes, prot) ) assert(process == CurrentProcess());
if ( !Memory::MapRange(segment.addr, segment.size, prot) )
{ {
// TODO: Memory leak of segment? kthread_mutex_unlock(&process->segment_lock);
process->ResetAddressSpace();
return 0; return 0;
} }
// Insert our newly allocated memory into the processes segment if ( !AddSegment(process, &segment) )
// list such that it can be reclaimed later. {
if ( process->segments ) Memory::UnmapRange(segment.addr, segment.size);
process->segments->prev = segment; kthread_mutex_unlock(&process->segment_lock);
segment->next = process->segments; process->ResetAddressSpace();
process->segments = segment; return 0;
}
kthread_mutex_unlock(&process->segment_lock);
// Copy as much data as possible and memset the rest to 0. // Copy as much data as possible and memset the rest to 0.
uint8_t* memdest = (uint8_t*) virtualaddr; uint8_t* memdest = (uint8_t*) virtualaddr;

View File

@ -30,6 +30,7 @@
#include <sortix/kernel/clock.h> #include <sortix/kernel/clock.h>
#include <sortix/kernel/kthread.h> #include <sortix/kernel/kthread.h>
#include <sortix/kernel/refcount.h> #include <sortix/kernel/refcount.h>
#include <sortix/kernel/segment.h>
#include <sortix/kernel/time.h> #include <sortix/kernel/time.h>
#include <sortix/kernel/timer.h> #include <sortix/kernel/timer.h>
#include <sortix/kernel/user-timer.h> #include <sortix/kernel/user-timer.h>
@ -50,30 +51,6 @@ struct ioctx_struct;
typedef struct ioctx_struct ioctx_t; typedef struct ioctx_struct ioctx_t;
struct Symbol; struct Symbol;
const int SEG_NONE = 0;
const int SEG_TEXT = 1;
const int SEG_DATA = 2;
const int SEG_STACK = 3;
const int SEG_OTHER = 4;
struct ProcessSegment
{
public:
ProcessSegment() : prev(NULL), next(NULL) { }
public:
ProcessSegment* prev;
ProcessSegment* next;
addr_t position;
size_t size;
int type;
public:
bool Intersects(ProcessSegment* segments);
ProcessSegment* Fork();
};
class Process class Process
{ {
friend void Process__OnLastThreadExit(void*); friend void Process__OnLastThreadExit(void*);
@ -156,7 +133,10 @@ public:
kthread_mutex_t threadlock; kthread_mutex_t threadlock;
public: public:
ProcessSegment* segments; struct segment* segments;
size_t segments_used;
size_t segments_length;
kthread_mutex_t segment_lock;
public: public:
kthread_mutex_t user_timers_lock; kthread_mutex_t user_timers_lock;

View File

@ -0,0 +1,60 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
sortix/kernel/segment.h
Structure representing a segment in a process.
*******************************************************************************/
#ifndef INCLUDE_SORTIX_KERNEL_SEGMENT_H
#define INCLUDE_SORTIX_KERNEL_SEGMENT_H
namespace Sortix {
class Process;
struct segment
{
uintptr_t addr;
size_t size;
int prot;
};
static inline int segmentcmp(const void* a_ptr, const void* b_ptr)
{
const struct segment* a = (const struct segment*) a_ptr;
const struct segment* b = (const struct segment*) b_ptr;
return a->addr < b->addr ? -1 :
b->addr < a->addr ? 1 :
a->size < b->size ? -1 :
b->size < a->size ? 1 :
0 ;
}
bool AreSegmentsOverlapping(const struct segment* a, const struct segment* b);
bool IsUserspaceSegment(const struct segment* segment);
struct segment* FindOverlappingSegment(Process* process, const struct segment* new_segment);
bool IsSegmentOverlapping(Process* process, const struct segment* new_segment);
bool AddSegment(Process* process, const struct segment* new_segment);
bool PlaceSegment(struct segment* solution, Process* process, void* addr_ptr,
size_t size, int flags);
} // namespace Sortix
#endif

View File

@ -43,6 +43,7 @@
#define PROT_KERNEL (PROT_KEXEC | PROT_KWRITE | PROT_KREAD) #define PROT_KERNEL (PROT_KEXEC | PROT_KWRITE | PROT_KREAD)
#define PROT_FORK (1<<6) #define PROT_FORK (1<<6)
#define PROT_HEAP (1<<7)
#define MAP_SHARED (1<<0) #define MAP_SHARED (1<<0)
#define MAP_PRIVATE (1<<1) #define MAP_PRIVATE (1<<1)

View File

@ -25,6 +25,7 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <sortix/clock.h> #include <sortix/clock.h>
@ -60,53 +61,6 @@
namespace Sortix { namespace Sortix {
bool ProcessSegment::Intersects(ProcessSegment* segments)
{
for ( ProcessSegment* tmp = segments; tmp != NULL; tmp = tmp->next )
{
if ( tmp->position < position + size &&
position < tmp->position + tmp->size )
{
return true;
}
}
if ( next ) { return next->Intersects(segments); }
return false;
}
ProcessSegment* ProcessSegment::Fork()
{
ProcessSegment* nextclone = NULL;
if ( next )
{
nextclone = next->Fork();
if ( nextclone == NULL ) { return NULL; }
}
ProcessSegment* clone = new ProcessSegment();
if ( clone == NULL )
{
while ( nextclone != NULL )
{
ProcessSegment* todelete = nextclone;
nextclone = nextclone->next;
delete todelete;
}
return NULL;
}
if ( nextclone )
nextclone->prev = clone;
clone->next = nextclone;
clone->position = position;
clone->size = size;
return clone;
}
Process::Process() Process::Process()
{ {
string_table = NULL; string_table = NULL;
@ -140,6 +94,10 @@ Process::Process()
ptrlock = KTHREAD_MUTEX_INITIALIZER; ptrlock = KTHREAD_MUTEX_INITIALIZER;
idlock = KTHREAD_MUTEX_INITIALIZER; idlock = KTHREAD_MUTEX_INITIALIZER;
user_timers_lock = KTHREAD_MUTEX_INITIALIZER; user_timers_lock = KTHREAD_MUTEX_INITIALIZER;
segments = NULL;
segments_used = 0;
segments_length = 0;
segment_lock = KTHREAD_MUTEX_INITIALIZER;
mmapfrom = 0x80000000UL; mmapfrom = 0x80000000UL;
exitstatus = -1; exitstatus = -1;
pid = AllocatePID(); pid = AllocatePID();
@ -370,16 +328,17 @@ void Process::LastPrayer()
void Process::ResetAddressSpace() void Process::ResetAddressSpace()
{ {
assert(Memory::GetAddressSpace() == addrspace); ScopedLock lock(&segment_lock);
ProcessSegment* tmp = segments;
while ( tmp != NULL )
{
Memory::UnmapRange(tmp->position, tmp->size);
ProcessSegment* todelete = tmp;
tmp = tmp->next;
delete todelete;
}
assert(Memory::GetAddressSpace() == addrspace);
for ( size_t i = 0; i < segments_used; i++ )
Memory::UnmapRange(segments[i].addr, segments[i].size);
Memory::Flush();
segments_used = segments_length = 0;
free(segments);
segments = NULL; segments = NULL;
} }
@ -645,34 +604,34 @@ Process* Process::Fork()
if ( !clone ) if ( !clone )
return NULL; return NULL;
ProcessSegment* clonesegments = NULL; struct segment* clone_segments = NULL;
// Fork the segment list. // Fork the segment list.
if ( segments ) if ( segments )
{ {
clonesegments = segments->Fork(); size_t segments_size = sizeof(struct segment) * segments_used;
if ( clonesegments == NULL ) { delete clone; return NULL; } if ( !(clone_segments = (struct segment*) malloc(segments_size)) )
{
delete clone;
return NULL;
}
memcpy(clone_segments, segments, segments_size);
} }
// Fork address-space here and copy memory. // Fork address-space here and copy memory.
clone->addrspace = Memory::Fork(); clone->addrspace = Memory::Fork();
if ( !clone->addrspace ) if ( !clone->addrspace )
{ {
// Delete the segment list, since they are currently bogus. free(clone_segments);
ProcessSegment* tmp = clonesegments; delete clone;
while ( tmp != NULL ) return NULL;
{
ProcessSegment* todelete = tmp;
tmp = tmp->next;
delete todelete;
}
delete clone; return NULL;
} }
// Now it's too late to clean up here, if anything goes wrong, we simply // Now it's too late to clean up here, if anything goes wrong, we simply
// ask the process to commit suicide before it goes live. // ask the process to commit suicide before it goes live.
clone->segments = clonesegments; clone->segments = clone_segments;
clone->segments_used = segments_used;
clone->segments_length = segments_used;
// Remember the relation to the child process. // Remember the relation to the child process.
AddChildProcess(clone); AddChildProcess(clone);
@ -759,8 +718,6 @@ Process* Process::Fork()
void Process::ResetForExecute() void Process::ResetForExecute()
{ {
// TODO: Delete all threads and their stacks.
string_table_length = 0; string_table_length = 0;
symbol_table_length = 0; symbol_table_length = 0;
delete[] string_table; string_table = NULL; delete[] string_table; string_table = NULL;
@ -1145,48 +1102,60 @@ void Process::Remove(Process* process)
pidlist->Remove(index); pidlist->Remove(index);
} }
void* sys_sbrk(intptr_t increment) static void* sys_sbrk(intptr_t increment)
{ {
Process* process = CurrentProcess(); Process* process = CurrentProcess();
ProcessSegment* dataseg = NULL; ScopedLock lock(&process->segment_lock);
for ( ProcessSegment* iter = process->segments; iter; iter = iter->next )
// Locate the heap segment.
struct segment* heap_segment = NULL;
for ( size_t i = process->segments_used; !heap_segment && i != 0; i-- )
{ {
if ( !iter->type == SEG_DATA ) struct segment* candidate = &process->segments[i-1];
if ( !(candidate->prot & PROT_HEAP) )
continue; continue;
if ( dataseg && iter->position < dataseg->position ) heap_segment = candidate;
continue;
dataseg = iter;
} }
if ( !dataseg ) if ( !heap_segment )
return errno = ENOMEM, (void*) -1UL; return errno = ENOMEM, (void*) -1UL;
addr_t currentend = dataseg->position + dataseg->size;
addr_t newend = currentend + increment; assert(IsUserspaceSegment(heap_segment));
if ( newend < dataseg->position )
return errno = EINVAL, (void*) -1UL; // Decrease the size of the heap segment if requested.
if ( newend < currentend ) if ( increment < 0 )
{ {
addr_t unmapfrom = Page::AlignUp(newend); uintptr_t abs_amount = Page::AlignDown(- (uintptr_t) increment);
if ( unmapfrom < currentend ) if ( heap_segment->size < abs_amount )
{ abs_amount = heap_segment->size;
size_t unmapbytes = Page::AlignUp(currentend - unmapfrom); uintptr_t new_end = heap_segment->addr + heap_segment->size - abs_amount;
Memory::UnmapRange(unmapfrom, unmapbytes); Memory::UnmapRange(new_end, abs_amount);
} heap_segment->size -= abs_amount;
// TODO: How do we handle that the heap shrinks to 0 bytes?
} }
else if ( currentend < newend )
// Increase the size of the heap if requested.
if ( 0 < increment )
{ {
// TODO: HACK: Make a safer way of expanding the data segment uintptr_t abs_amount = Page::AlignUp(increment);
// without segments possibly colliding! uintptr_t max_growth = 0 - (heap_segment->addr + heap_segment->size);
addr_t mapfrom = Page::AlignUp(currentend); if ( max_growth < abs_amount )
if ( mapfrom < newend ) return errno = ENOMEM, (void*) -1UL;
{ struct segment growth;
size_t mapbytes = Page::AlignUp(newend - mapfrom); growth.addr = heap_segment->addr + heap_segment->size;
int prot = PROT_FORK | PROT_READ | PROT_WRITE | PROT_KREAD | PROT_KWRITE; growth.size = abs_amount;
if ( !Memory::MapRange(mapfrom, mapbytes, prot) ) growth.prot = heap_segment->prot;
return (void*) -1UL; if ( !IsUserspaceSegment(&growth) )
} return errno = ENOMEM, (void*) -1UL;
if ( FindOverlappingSegment(process, &growth) )
return errno = ENOMEM, (void*) -1UL;
if ( !Memory::MapRange(growth.addr, growth.size, growth.prot) )
return errno = ENOMEM, (void*) -1UL;
heap_segment->size += growth.size;
} }
dataseg->size += increment;
return (void*) newend; assert(IsUserspaceSegment(heap_segment));
return (void*) (heap_segment->addr + heap_segment->size);
} }
size_t sys_getpagesize() size_t sys_getpagesize()

108
sortix/segment.cpp Normal file
View File

@ -0,0 +1,108 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
segment.cpp
Structure representing a segment in a process.
*******************************************************************************/
#include <sys/types.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <sortix/kernel/decl.h>
#include <sortix/kernel/memorymanagement.h>
#include <sortix/kernel/process.h>
#include <sortix/kernel/segment.h>
namespace Sortix {
bool AreSegmentsOverlapping(const struct segment* a, const struct segment* b)
{
return a->addr < b->addr + b->size && b->addr < a->addr + a->size;
}
bool IsUserspaceSegment(const struct segment* segment)
{
uintptr_t userspace_addr;
size_t userspace_size;
Memory::GetUserVirtualArea(&userspace_addr, &userspace_size);
if ( segment->addr < userspace_addr )
return false;
uintptr_t userspace_end = userspace_addr + userspace_size;
if ( userspace_end - segment->addr < segment->size )
return false;
return true;
}
struct segment* FindOverlappingSegment(Process* process, const struct segment* new_segment)
{
// process->segment_lock is held at this point.
// TODO: Speed up using binary search.
for ( size_t i = 0; i < process->segments_used; i++ )
{
struct segment* segment = &process->segments[i];
if ( AreSegmentsOverlapping(segment, new_segment) )
return segment;
}
return NULL;
}
bool IsSegmentOverlapping(Process* process, const struct segment* new_segment)
{
// process->segment_lock is held at this point.
return FindOverlappingSegment(process, new_segment) != NULL;
}
bool AddSegment(Process* process, const struct segment* new_segment)
{
// process->segment_lock is held at this point.
// assert(!IsSegmentOverlapping(new_segment));
// Check if we need to expand the segment list.
if ( process->segments_used == process->segments_length )
{
size_t new_length = process->segments_length ?
process->segments_length * 2 : 8;
size_t new_size = new_length * sizeof(struct segment);
struct segment* new_segments =
(struct segment*) realloc(process->segments, new_size);
if ( !new_segments )
return false;
process->segments = new_segments;
process->segments_length = new_length;
}
// Add the new segment to the segment list.
process->segments[process->segments_used++] = *new_segment;
// Sort the segment list after address.
qsort(process->segments, process->segments_used, sizeof(struct segment),
segmentcmp);
return true;
}
} // namespace Sortix