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 \
refcount.o \
scheduler.o \
segment.o \
serialterminal.o \
signal.o \
sound.o \

View File

@ -28,6 +28,7 @@
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sortix/mman.h>
@ -35,6 +36,7 @@
#include <sortix/kernel/platform.h>
#include <sortix/kernel/memorymanagement.h>
#include <sortix/kernel/process.h>
#include <sortix/kernel/segment.h>
#include <sortix/kernel/symbol.h>
#include "elf.h"
@ -42,25 +44,6 @@
namespace Sortix {
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
// headers may vary depending on the ELF header and that using a simple
// 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->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;
if ( pht->flags & PF_X ) { prot |= PROT_EXEC; }
if ( pht->flags & PF_R ) { prot |= PROT_READ; }
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;
}
if ( !Memory::MapRange(mapto, mapbytes, prot) )
// TODO: Memory leak of segment?
return 0;
assert(process == CurrentProcess());
// Insert our newly allocated memory into the processes segment
// list such that it can be reclaimed later.
if ( process->segments )
process->segments->prev = segment;
segment->next = process->segments;
process->segments = segment;
if ( !Memory::MapRange(segment.addr, segment.size, prot) )
{
kthread_mutex_unlock(&process->segment_lock);
process->ResetAddressSpace();
return 0;
}
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.
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->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;
if ( pht->flags & PF_X ) { prot |= PROT_EXEC; }
if ( pht->flags & PF_R ) { prot |= PROT_READ; }
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;
}
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;
}
// Insert our newly allocated memory into the processes segment
// list such that it can be reclaimed later.
if ( process->segments )
process->segments->prev = segment;
segment->next = process->segments;
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.
uint8_t* memdest = (uint8_t*) virtualaddr;

View File

@ -30,6 +30,7 @@
#include <sortix/kernel/clock.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/refcount.h>
#include <sortix/kernel/segment.h>
#include <sortix/kernel/time.h>
#include <sortix/kernel/timer.h>
#include <sortix/kernel/user-timer.h>
@ -50,30 +51,6 @@ struct ioctx_struct;
typedef struct ioctx_struct ioctx_t;
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
{
friend void Process__OnLastThreadExit(void*);
@ -156,7 +133,10 @@ public:
kthread_mutex_t threadlock;
public:
ProcessSegment* segments;
struct segment* segments;
size_t segments_used;
size_t segments_length;
kthread_mutex_t segment_lock;
public:
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_FORK (1<<6)
#define PROT_HEAP (1<<7)
#define MAP_SHARED (1<<0)
#define MAP_PRIVATE (1<<1)

View File

@ -25,6 +25,7 @@
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sortix/clock.h>
@ -60,53 +61,6 @@
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()
{
string_table = NULL;
@ -140,6 +94,10 @@ Process::Process()
ptrlock = KTHREAD_MUTEX_INITIALIZER;
idlock = KTHREAD_MUTEX_INITIALIZER;
user_timers_lock = KTHREAD_MUTEX_INITIALIZER;
segments = NULL;
segments_used = 0;
segments_length = 0;
segment_lock = KTHREAD_MUTEX_INITIALIZER;
mmapfrom = 0x80000000UL;
exitstatus = -1;
pid = AllocatePID();
@ -370,16 +328,17 @@ void Process::LastPrayer()
void Process::ResetAddressSpace()
{
assert(Memory::GetAddressSpace() == addrspace);
ProcessSegment* tmp = segments;
while ( tmp != NULL )
{
Memory::UnmapRange(tmp->position, tmp->size);
ProcessSegment* todelete = tmp;
tmp = tmp->next;
delete todelete;
}
ScopedLock lock(&segment_lock);
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;
}
@ -645,34 +604,34 @@ Process* Process::Fork()
if ( !clone )
return NULL;
ProcessSegment* clonesegments = NULL;
struct segment* clone_segments = NULL;
// Fork the segment list.
if ( segments )
{
clonesegments = segments->Fork();
if ( clonesegments == NULL ) { delete clone; return NULL; }
size_t segments_size = sizeof(struct segment) * segments_used;
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.
clone->addrspace = Memory::Fork();
if ( !clone->addrspace )
{
// Delete the segment list, since they are currently bogus.
ProcessSegment* tmp = clonesegments;
while ( tmp != NULL )
{
ProcessSegment* todelete = tmp;
tmp = tmp->next;
delete todelete;
}
delete clone; return NULL;
free(clone_segments);
delete clone;
return NULL;
}
// 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.
clone->segments = clonesegments;
clone->segments = clone_segments;
clone->segments_used = segments_used;
clone->segments_length = segments_used;
// Remember the relation to the child process.
AddChildProcess(clone);
@ -759,8 +718,6 @@ Process* Process::Fork()
void Process::ResetForExecute()
{
// TODO: Delete all threads and their stacks.
string_table_length = 0;
symbol_table_length = 0;
delete[] string_table; string_table = NULL;
@ -1145,48 +1102,60 @@ void Process::Remove(Process* process)
pidlist->Remove(index);
}
void* sys_sbrk(intptr_t increment)
static void* sys_sbrk(intptr_t increment)
{
Process* process = CurrentProcess();
ProcessSegment* dataseg = NULL;
for ( ProcessSegment* iter = process->segments; iter; iter = iter->next )
ScopedLock lock(&process->segment_lock);
// 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;
if ( dataseg && iter->position < dataseg->position )
continue;
dataseg = iter;
heap_segment = candidate;
}
if ( !dataseg )
if ( !heap_segment )
return errno = ENOMEM, (void*) -1UL;
addr_t currentend = dataseg->position + dataseg->size;
addr_t newend = currentend + increment;
if ( newend < dataseg->position )
return errno = EINVAL, (void*) -1UL;
if ( newend < currentend )
assert(IsUserspaceSegment(heap_segment));
// Decrease the size of the heap segment if requested.
if ( increment < 0 )
{
addr_t unmapfrom = Page::AlignUp(newend);
if ( unmapfrom < currentend )
{
size_t unmapbytes = Page::AlignUp(currentend - unmapfrom);
Memory::UnmapRange(unmapfrom, unmapbytes);
}
uintptr_t abs_amount = Page::AlignDown(- (uintptr_t) increment);
if ( heap_segment->size < abs_amount )
abs_amount = heap_segment->size;
uintptr_t new_end = heap_segment->addr + heap_segment->size - abs_amount;
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
// without segments possibly colliding!
addr_t mapfrom = Page::AlignUp(currentend);
if ( mapfrom < newend )
{
size_t mapbytes = Page::AlignUp(newend - mapfrom);
int prot = PROT_FORK | PROT_READ | PROT_WRITE | PROT_KREAD | PROT_KWRITE;
if ( !Memory::MapRange(mapfrom, mapbytes, prot) )
return (void*) -1UL;
}
uintptr_t abs_amount = Page::AlignUp(increment);
uintptr_t max_growth = 0 - (heap_segment->addr + heap_segment->size);
if ( max_growth < abs_amount )
return errno = ENOMEM, (void*) -1UL;
struct segment growth;
growth.addr = heap_segment->addr + heap_segment->size;
growth.size = abs_amount;
growth.prot = heap_segment->prot;
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()

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