mirror of
git://projects.qi-hardware.com/iris.git
synced 2025-02-07 01:51:55 +02:00
more
This commit is contained in:
parent
3fc672f50f
commit
e7f42c6b7e
16
alloc.ccp
16
alloc.ccp
@ -140,7 +140,8 @@ Page *Memory::alloc_page ():
|
|||||||
Page *ret = (Page *)search_free (sizeof (Page), (void **)&pages)
|
Page *ret = (Page *)search_free (sizeof (Page), (void **)&pages)
|
||||||
if !ret:
|
if !ret:
|
||||||
return NULL
|
return NULL
|
||||||
ret->physical = 0
|
ret->data.frame = 0
|
||||||
|
ret->data.flags = 0
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
Thread *Memory::alloc_thread ():
|
Thread *Memory::alloc_thread ():
|
||||||
@ -203,10 +204,11 @@ Cappage *Memory::alloc_cappage ():
|
|||||||
Cappage *ret = (Cappage *)search_free (sizeof (Cappage), (void **)&cappages)
|
Cappage *ret = (Cappage *)search_free (sizeof (Cappage), (void **)&cappages)
|
||||||
if !ret:
|
if !ret:
|
||||||
return NULL
|
return NULL
|
||||||
ret->page = (Capability *)zalloc ()
|
ret->data.frame = zalloc ()
|
||||||
if !ret->page:
|
if !ret->data.frame:
|
||||||
free_cappage (ret)
|
free_cappage (ret)
|
||||||
return NULL
|
return NULL
|
||||||
|
ret->data.flags = 0
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
Memory *Memory::alloc_memory ():
|
Memory *Memory::alloc_memory ():
|
||||||
@ -230,8 +232,8 @@ void Memory::free_page (Page *page):
|
|||||||
if page->next:
|
if page->next:
|
||||||
page->next->prev = page->prev
|
page->next->prev = page->prev
|
||||||
unuse ()
|
unuse ()
|
||||||
if page->physical:
|
if page->data.frame:
|
||||||
pfree (page->physical)
|
pfree (page->data.frame)
|
||||||
free_obj (page)
|
free_obj (page)
|
||||||
|
|
||||||
void Memory::free_thread (Thread *thread):
|
void Memory::free_thread (Thread *thread):
|
||||||
@ -311,8 +313,8 @@ void Capability::invalidate ():
|
|||||||
|
|
||||||
void Memory::free_cappage (Cappage *p):
|
void Memory::free_cappage (Cappage *p):
|
||||||
for unsigned i = 0; i < CAPPAGE_SIZE; ++i:
|
for unsigned i = 0; i < CAPPAGE_SIZE; ++i:
|
||||||
p->page[i].invalidate ()
|
((Capability *)p->data.frame)[i].invalidate ()
|
||||||
zfree ((unsigned)p->page)
|
zfree (p->data.frame)
|
||||||
free_obj (p)
|
free_obj (p)
|
||||||
|
|
||||||
void Memory::free_memory (Memory *mem):
|
void Memory::free_memory (Memory *mem):
|
||||||
|
@ -58,12 +58,22 @@ extern "C" {
|
|||||||
#define THREAD_FLAG_USER 0x1fffffff
|
#define THREAD_FLAG_USER 0x1fffffff
|
||||||
|
|
||||||
#define CAP_PAGE_MAP 1
|
#define CAP_PAGE_MAP 1
|
||||||
#define CAP_PAGE_SHARE 2
|
#define CAP_PAGE_COPY 2
|
||||||
#define CAP_PAGE_SHARE_COW 3
|
#define CAP_PAGE_MOVE 3
|
||||||
#define CAP_PAGE_FORGET 4
|
#define CAP_PAGE_GET_FLAGS 4
|
||||||
// Not an operation; a capability without this bit cannot write to the page. */
|
#define CAP_PAGE_SET_FLAGS 5
|
||||||
#define CAP_PAGE_WRITE 5
|
/* Not an operation; a capability without this bit cannot write to the page. */
|
||||||
|
#define CAP_PAGE_WRITE 6
|
||||||
#define CAP_PAGE_ALL_RIGHTS 0x1ff
|
#define CAP_PAGE_ALL_RIGHTS 0x1ff
|
||||||
|
/* Flag values for Page and Cappage objects. */
|
||||||
|
/* A writable page can be written to. This flag can not be set while the frame is shared. */
|
||||||
|
#define PAGE_FLAG_WRITABLE 1
|
||||||
|
/* When paying, the memory's use is incremented if the page holds a frame. It cannot be lost. Frames are lost when the last payer forgets them. */
|
||||||
|
#define PAGE_FLAG_PAYING 2
|
||||||
|
/* Frames can be moved to this Page if they are not shared; copying will always fail. Sharing a page while this flag is set will fail; moving it will move the flag with it. A page is guaranteed to be unshared when this flag is set. Trying to set this flag on a shared page has no effect. */
|
||||||
|
#define PAGE_FLAG_NOSHARE 4
|
||||||
|
/* This is a read-only flag, which is set if the Page is shared. */
|
||||||
|
#define PAGE_FLAG_SHARED 8
|
||||||
|
|
||||||
#define CAP_CAPABILITY_GET 1
|
#define CAP_CAPABILITY_GET 1
|
||||||
#define CAP_CAPABILITY_SET_DEATH_NOTIFY 2
|
#define CAP_CAPABILITY_SET_DEATH_NOTIFY 2
|
||||||
@ -71,7 +81,7 @@ extern "C" {
|
|||||||
|
|
||||||
#define CAPPAGE_SIZE 102
|
#define CAPPAGE_SIZE 102
|
||||||
/* Cappage has page's operations as well. */
|
/* Cappage has page's operations as well. */
|
||||||
#define CAP_CAPPAGE_SET 6
|
#define CAP_CAPPAGE_SET 7
|
||||||
#define CAP_CAPPAGE_ALL_RIGHTS 0x1ff
|
#define CAP_CAPPAGE_ALL_RIGHTS 0x1ff
|
||||||
|
|
||||||
#ifndef __KERNEL
|
#ifndef __KERNEL
|
||||||
|
12
init.ccp
12
init.ccp
@ -31,7 +31,7 @@ static void init_idle ():
|
|||||||
// initialize idle_page
|
// initialize idle_page
|
||||||
idle_page.prev = NULL
|
idle_page.prev = NULL
|
||||||
idle_page.next = NULL
|
idle_page.next = NULL
|
||||||
idle_page.physical = 0x80000000
|
idle_page.data.frame = 0x80000000
|
||||||
idle_page.refs = NULL
|
idle_page.refs = NULL
|
||||||
idle_page.address_space = NULL
|
idle_page.address_space = NULL
|
||||||
current = &idle
|
current = &idle
|
||||||
@ -123,7 +123,7 @@ static void init_threads ():
|
|||||||
unsigned idx = (p - (shdr->sh_addr & PAGE_MASK)) >> PAGE_BITS
|
unsigned idx = (p - (shdr->sh_addr & PAGE_MASK)) >> PAGE_BITS
|
||||||
if !pages[idx]:
|
if !pages[idx]:
|
||||||
pages[idx] = mem->alloc_page ()
|
pages[idx] = mem->alloc_page ()
|
||||||
pages[idx]->physical = thread_start[i] + (idx << PAGE_BITS)
|
pages[idx]->data.frame = thread_start[i] + (idx << PAGE_BITS)
|
||||||
++top_memory.limit
|
++top_memory.limit
|
||||||
if !mem->map (pages[idx], p, writable):
|
if !mem->map (pages[idx], p, writable):
|
||||||
panic (0x22446688, "unable to map initial page")
|
panic (0x22446688, "unable to map initial page")
|
||||||
@ -135,8 +135,8 @@ static void init_threads ():
|
|||||||
page = mem->alloc_page ()
|
page = mem->alloc_page ()
|
||||||
if !page:
|
if !page:
|
||||||
panic (0x00220022, "out of memory")
|
panic (0x00220022, "out of memory")
|
||||||
page->physical = mem->zalloc ()
|
page->data.frame = mem->zalloc ()
|
||||||
if !page->physical || !mem->map (page, p, true):
|
if !page->data.frame || !mem->map (page, p, true):
|
||||||
panic (0x33557799, "unable to map initial bss page")
|
panic (0x33557799, "unable to map initial bss page")
|
||||||
else:
|
else:
|
||||||
if !write:
|
if !write:
|
||||||
@ -146,14 +146,14 @@ static void init_threads ():
|
|||||||
break
|
break
|
||||||
if a < shdr->sh_addr:
|
if a < shdr->sh_addr:
|
||||||
continue
|
continue
|
||||||
((unsigned *)page->physical)[(a & ~PAGE_MASK) >> 2] = 0
|
((unsigned *)page->data.frame)[(a & ~PAGE_MASK) >> 2] = 0
|
||||||
for unsigned p = 0; p <= ((thread_start[i + 1] - thread_start[i] - 1) >> PAGE_BITS); ++p:
|
for unsigned p = 0; p <= ((thread_start[i + 1] - thread_start[i] - 1) >> PAGE_BITS); ++p:
|
||||||
if pages[p]:
|
if pages[p]:
|
||||||
continue
|
continue
|
||||||
++top_memory.limit
|
++top_memory.limit
|
||||||
top_memory.pfree (thread_start[i] + (p << PAGE_BITS))
|
top_memory.pfree (thread_start[i] + (p << PAGE_BITS))
|
||||||
Page *stackpage = mem->alloc_page ()
|
Page *stackpage = mem->alloc_page ()
|
||||||
stackpage->physical = mem->zalloc ()
|
stackpage->data.frame = mem->zalloc ()
|
||||||
if !stackpage || !mem->map (stackpage, 0x7ffff000, true):
|
if !stackpage || !mem->map (stackpage, 0x7ffff000, true):
|
||||||
panic (0x13151719, "unable to map initial stack page")
|
panic (0x13151719, "unable to map initial stack page")
|
||||||
Receiver *recv = mem->alloc_receiver ()
|
Receiver *recv = mem->alloc_receiver ()
|
||||||
|
117
invoke.ccp
117
invoke.ccp
@ -10,7 +10,7 @@ Capability *Memory::find_capability (unsigned code, bool *copy):
|
|||||||
return NULL
|
return NULL
|
||||||
Capability *page = (Capability *)(code & PAGE_MASK)
|
Capability *page = (Capability *)(code & PAGE_MASK)
|
||||||
for Cappage *p = cappages; p; p = p->next:
|
for Cappage *p = cappages; p; p = p->next:
|
||||||
if p->page == page:
|
if p->data.frame == (unsigned)page:
|
||||||
return &page[num]
|
return &page[num]
|
||||||
else:
|
else:
|
||||||
// Normal capability
|
// Normal capability
|
||||||
@ -234,31 +234,92 @@ static void thread_invoke (unsigned target, unsigned protected_data, Capability
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
|
||||||
static void page_invoke (unsigned target, unsigned protected_data, Capability *cap, bool copy, unsigned request, unsigned data):
|
static void page_invoke (unsigned target, unsigned protected_data, Capability *cap, bool copy, unsigned data[4]):
|
||||||
Page *page
|
Page *page
|
||||||
Cappage *cappage
|
Cappage *cappage
|
||||||
|
ShareData *share_data
|
||||||
if (target & CAPTYPE_MASK) == CAPTYPE_PAGE:
|
if (target & CAPTYPE_MASK) == CAPTYPE_PAGE:
|
||||||
page = (Page *)protected_data
|
page = (Page *)protected_data
|
||||||
cappage = NULL
|
cappage = NULL
|
||||||
|
share_data = &page->data
|
||||||
else:
|
else:
|
||||||
page = NULL
|
page = NULL
|
||||||
cappage = (Cappage *)protected_data
|
cappage = (Cappage *)protected_data
|
||||||
switch request:
|
share_data = &cappage->data
|
||||||
|
switch data[0]:
|
||||||
case CAP_PAGE_MAP:
|
case CAP_PAGE_MAP:
|
||||||
if !page:
|
if !page:
|
||||||
return
|
return
|
||||||
page->address_space->map (page, data, target & CAP_PAGE_WRITE)
|
page->address_space->map (page, data[1], target & CAP_PAGE_WRITE)
|
||||||
break
|
break
|
||||||
case CAP_PAGE_SHARE:
|
case CAP_PAGE_COPY:
|
||||||
// TODO
|
// TODO
|
||||||
case CAP_PAGE_SHARE_COW:
|
case CAP_PAGE_MOVE:
|
||||||
// TODO
|
|
||||||
case CAP_PAGE_FORGET:
|
|
||||||
// TODO
|
// TODO
|
||||||
|
case CAP_PAGE_GET_FLAGS:
|
||||||
|
reply_num (share_data->flags)
|
||||||
|
break
|
||||||
|
case CAP_PAGE_SET_FLAGS:
|
||||||
|
data[2] &= ~PAGE_FLAG_SHARED
|
||||||
|
if share_data->flags & PAGE_FLAG_SHARED:
|
||||||
|
data[1] &= ~(PAGE_FLAG_WRITABLE | PAGE_FLAG_NOSHARE)
|
||||||
|
unsigned old = share_data->flags
|
||||||
|
share_data->flags &= ~data[2]
|
||||||
|
share_data->flags |= data[1] & data[2]
|
||||||
|
if page && (share_data->flags ^ old) & PAGE_FLAG_WRITABLE:
|
||||||
|
Page_arch_update_mapping (page)
|
||||||
|
if (share_data->flags & ~old) & PAGE_FLAG_PAYING:
|
||||||
|
if page:
|
||||||
|
if !page->address_space->use():
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if !cappage->address_space->use():
|
||||||
|
break
|
||||||
|
else if (~share_data->flags & old) & PAGE_FLAG_PAYING:
|
||||||
|
// We stop paying. Free the frame if nobody else is paying.
|
||||||
|
if page:
|
||||||
|
Page *p
|
||||||
|
for p = (Page *)page->data.share_prev; p; p = (Page *)p->data.share_prev:
|
||||||
|
if p->data.flags & PAGE_FLAG_PAYING:
|
||||||
|
break
|
||||||
|
if !p:
|
||||||
|
for p = (Page *)page->data.share_next; p; p = (Page *)p->data.share_next:
|
||||||
|
if p->data.flags & PAGE_FLAG_PAYING:
|
||||||
|
break
|
||||||
|
if p:
|
||||||
|
page->address_space->unuse()
|
||||||
|
else:
|
||||||
|
// No Page is paying for this frame anymore.
|
||||||
|
page->address_space->pfree (page->data.frame)
|
||||||
|
for p = page; p; p = (Page *)p->data.share_prev:
|
||||||
|
p->data.frame = NULL
|
||||||
|
for p = (Page *)page->data.share_next; p; p = (Page *)p->data.share_next:
|
||||||
|
p->data.frame = NULL
|
||||||
|
else:
|
||||||
|
Cappage *p
|
||||||
|
for p = (Cappage *)cappage->data.share_prev; p; p = (Cappage *)p->data.share_prev:
|
||||||
|
if p->data.flags & PAGE_FLAG_PAYING:
|
||||||
|
break
|
||||||
|
if !p:
|
||||||
|
for p = (Cappage *)cappage->data.share_next; p; p = (Cappage *)p->data.share_next:
|
||||||
|
if p->data.flags & PAGE_FLAG_PAYING:
|
||||||
|
break
|
||||||
|
if p:
|
||||||
|
cappage->address_space->unuse()
|
||||||
|
else:
|
||||||
|
// No Page is paying for this frame anymore.
|
||||||
|
for unsigned i = 0; i < CAPPAGE_SIZE; ++i:
|
||||||
|
((Capability *)share_data->frame)[i].invalidate ()
|
||||||
|
cappage->address_space->pfree (cappage->data.frame)
|
||||||
|
for p = cappage; p; p = (Cappage *)p->data.share_prev:
|
||||||
|
p->data.frame = NULL
|
||||||
|
for p = (Cappage *)cappage->data.share_next; p; p = (Cappage *)p->data.share_next:
|
||||||
|
p->data.frame = NULL
|
||||||
|
break
|
||||||
case CAP_CAPPAGE_SET:
|
case CAP_CAPPAGE_SET:
|
||||||
if !cappage || data >= CAPPAGE_SIZE || !(target & CAP_PAGE_WRITE):
|
if !cappage || data[1] >= CAPPAGE_SIZE || !(target & CAP_PAGE_WRITE):
|
||||||
return
|
return
|
||||||
Capability *c = &cappage->page[data]
|
Capability *c = &((Capability *)cappage->data.frame)[data[1]]
|
||||||
c->invalidate ()
|
c->invalidate ()
|
||||||
// clone_capability needs a Memory, but doesn't use it when storage is provided.
|
// clone_capability needs a Memory, but doesn't use it when storage is provided.
|
||||||
top_memory.clone_capability (cap, copy, c)
|
top_memory.clone_capability (cap, copy, c)
|
||||||
@ -277,7 +338,7 @@ static void capability_invoke (unsigned target, unsigned protected_data, Capabil
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
|
||||||
static bool kernel_invoke (unsigned target, unsigned protected_data, unsigned d[4], Capability *c[4], bool copy[4]):
|
static bool kernel_invoke (unsigned target, unsigned protected_data, unsigned d[4], Capability *c[4], bool copy[4], Capability *self):
|
||||||
// Kernel calling convention:
|
// Kernel calling convention:
|
||||||
// data[0] is the request.
|
// data[0] is the request.
|
||||||
// cap[0] is the reply capability
|
// cap[0] is the reply capability
|
||||||
@ -285,33 +346,35 @@ static bool kernel_invoke (unsigned target, unsigned protected_data, unsigned d[
|
|||||||
if !((1 << d[0]) & target & ~REQUEST_MASK):
|
if !((1 << d[0]) & target & ~REQUEST_MASK):
|
||||||
// You are not allowed to perform this operation.
|
// You are not allowed to perform this operation.
|
||||||
return true
|
return true
|
||||||
reply = c[0]
|
if (target & (CAPTYPE_MASK | (1 << CAP_RECEIVER_CALL))) == (CAPTYPE_RECEIVER | (1 << CAP_RECEIVER_CALL)):
|
||||||
if d[0] == CAP_DEGRADE:
|
|
||||||
reply_cap (target & d[1], protected_data)
|
|
||||||
return true
|
|
||||||
switch target & CAPTYPE_MASK:
|
|
||||||
case CAPTYPE_RECEIVER:
|
|
||||||
if target & (1 << CAP_RECEIVER_CALL):
|
|
||||||
// This is a call capability.
|
// This is a call capability.
|
||||||
Capability r
|
Capability r
|
||||||
Receiver *t = c[0]->target
|
Capability *c0 = c[0]
|
||||||
unsigned p_d = c[0]->protected_data
|
if ~(unsigned)c0->target & ~KERNEL_MASK:
|
||||||
if ~(unsigned)t & ~KERNEL_MASK:
|
|
||||||
fill_cap (&r, protected_data, ~0)
|
fill_cap (&r, protected_data, ~0)
|
||||||
c[0] = &r
|
c[0] = &r
|
||||||
copy[0] = true
|
copy[0] = true
|
||||||
bool ret = kernel_invoke ((unsigned)t, p_d, d, c, copy)
|
bool ret = kernel_invoke ((unsigned)c0->target, c0->protected_data, d, c, copy, c0)
|
||||||
r.invalidate ()
|
r.invalidate ()
|
||||||
return ret
|
return ret
|
||||||
else:
|
else:
|
||||||
// Kernel call: don't create actual capablities.
|
// Kernel call: don't create actual capablities.
|
||||||
reply = NULL
|
reply = NULL
|
||||||
reply_receiver = (Receiver *)protected_data
|
reply_receiver = (Receiver *)protected_data
|
||||||
return kernel_invoke ((unsigned)t, p_d, d, c, copy)
|
return kernel_invoke ((unsigned)c0->target, c0->protected_data, d, c, copy, c0)
|
||||||
if target & (1 << CAP_RECEIVER_REPLY):
|
if (target & (CAPTYPE_MASK | (1 << CAP_RECEIVER_REPLY))) == (CAPTYPE_RECEIVER | (1 << CAP_RECEIVER_REPLY)):
|
||||||
// This is a reply capability.
|
// This is a reply capability.
|
||||||
((Receiver *)protected_data)->send_message (~0, d, c, copy)
|
((Receiver *)protected_data)->send_message (~0, d, c, copy)
|
||||||
|
while self->parent:
|
||||||
|
self = self->parent
|
||||||
|
self->invalidate ()
|
||||||
return true
|
return true
|
||||||
|
reply = c[0]
|
||||||
|
if d[0] == CAP_DEGRADE:
|
||||||
|
reply_cap (target & d[1], protected_data)
|
||||||
|
return true
|
||||||
|
switch target & CAPTYPE_MASK:
|
||||||
|
case CAPTYPE_RECEIVER:
|
||||||
receiver_invoke (target, protected_data, c[1], d[0], d[1])
|
receiver_invoke (target, protected_data, c[1], d[0], d[1])
|
||||||
break
|
break
|
||||||
case CAPTYPE_MEMORY:
|
case CAPTYPE_MEMORY:
|
||||||
@ -321,13 +384,13 @@ static bool kernel_invoke (unsigned target, unsigned protected_data, unsigned d[
|
|||||||
thread_invoke (target, protected_data, c[1], d)
|
thread_invoke (target, protected_data, c[1], d)
|
||||||
break
|
break
|
||||||
case CAPTYPE_PAGE:
|
case CAPTYPE_PAGE:
|
||||||
page_invoke (target, protected_data, c[1], copy[1], d[0], d[1])
|
page_invoke (target, protected_data, c[1], copy[1], d)
|
||||||
break
|
break
|
||||||
case CAPTYPE_CAPABILITY:
|
case CAPTYPE_CAPABILITY:
|
||||||
capability_invoke (target, protected_data, c[1], d[0], d[1])
|
capability_invoke (target, protected_data, c[1], d[0], d[1])
|
||||||
break
|
break
|
||||||
case CAPTYPE_CAPPAGE:
|
case CAPTYPE_CAPPAGE:
|
||||||
page_invoke (target, protected_data, c[1], copy[1], d[0], d[1])
|
page_invoke (target, protected_data, c[1], copy[1], d)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
panic (0x99337744, "invalid capability type invoked")
|
panic (0x99337744, "invalid capability type invoked")
|
||||||
@ -376,4 +439,4 @@ bool Capability::invoke (unsigned data[4], Capability *cap[4], bool copy[4]):
|
|||||||
// This is not a kernel capability: send a message to the receiver.
|
// This is not a kernel capability: send a message to the receiver.
|
||||||
return target->send_message (protected_data, data, cap, copy)
|
return target->send_message (protected_data, data, cap, copy)
|
||||||
// This is a kernel capability. Use a function to allow optimized call capabilities.
|
// This is a kernel capability. Use a function to allow optimized call capabilities.
|
||||||
return kernel_invoke ((unsigned)target, protected_data, data, cap, copy)
|
return kernel_invoke ((unsigned)target, protected_data, data, cap, copy, this)
|
||||||
|
17
kernel.hhp
17
kernel.hhp
@ -40,10 +40,6 @@ bool Object_base::is_free ():
|
|||||||
|
|
||||||
#include "arch.hh"
|
#include "arch.hh"
|
||||||
|
|
||||||
struct Page : public Object <Page>:
|
|
||||||
unsigned physical
|
|
||||||
Page_arch arch
|
|
||||||
|
|
||||||
struct Thread : public Object <Thread>:
|
struct Thread : public Object <Thread>:
|
||||||
Receiver *receivers
|
Receiver *receivers
|
||||||
unsigned pc, sp
|
unsigned pc, sp
|
||||||
@ -80,8 +76,18 @@ struct Capability : public Object <Capability>:
|
|||||||
bool invoke (unsigned data[4], Capability *cap[4], bool copy[4])
|
bool invoke (unsigned data[4], Capability *cap[4], bool copy[4])
|
||||||
void invalidate ()
|
void invalidate ()
|
||||||
|
|
||||||
|
struct ShareData :
|
||||||
|
unsigned frame
|
||||||
|
unsigned flags
|
||||||
|
void *share_first
|
||||||
|
void *share_prev, *share_next
|
||||||
|
|
||||||
|
struct Page : public Object <Page>:
|
||||||
|
ShareData data
|
||||||
|
Page_arch arch
|
||||||
|
|
||||||
struct Cappage : public Object <Cappage>:
|
struct Cappage : public Object <Cappage>:
|
||||||
Capability *page
|
ShareData data
|
||||||
|
|
||||||
struct Memory : public Object <Memory>:
|
struct Memory : public Object <Memory>:
|
||||||
Free *frees
|
Free *frees
|
||||||
@ -162,6 +168,7 @@ void Memory_arch_free (Memory *mem)
|
|||||||
bool Memory_arch_map (Memory *mem, Page *page, unsigned address, bool write)
|
bool Memory_arch_map (Memory *mem, Page *page, unsigned address, bool write)
|
||||||
void Memory_arch_unmap (Memory *mem, Page *page, unsigned address)
|
void Memory_arch_unmap (Memory *mem, Page *page, unsigned address)
|
||||||
Page *Memory_arch_get_mapping (Memory *mem, unsigned address, bool *writable)
|
Page *Memory_arch_get_mapping (Memory *mem, unsigned address, bool *writable)
|
||||||
|
void Page_arch_update_mapping (Page *page)
|
||||||
void arch_invoke ()
|
void arch_invoke ()
|
||||||
void arch_register_interrupt (unsigned num, Receiver *r)
|
void arch_register_interrupt (unsigned num, Receiver *r)
|
||||||
|
|
||||||
|
5
mips.ccp
5
mips.ccp
@ -229,7 +229,7 @@ bool Memory_arch_map (Memory *mem, Page *page, unsigned address, bool write):
|
|||||||
unsigned idx = (address >> 12) & ((1 << 9) - 1)
|
unsigned idx = (address >> 12) & ((1 << 9) - 1)
|
||||||
if table[idx]:
|
if table[idx]:
|
||||||
mem->unmap ((Page *)table[idx + 0x200], address)
|
mem->unmap ((Page *)table[idx + 0x200], address)
|
||||||
table[idx] = page->physical ? ((page->physical & ~0x80000000) >> 6) | 0x18 | (write ? 0x4 : 0) | 0x2 : 0
|
table[idx] = page->data.frame ? ((page->data.frame & ~0x80000000) >> 6) | 0x18 | (write ? 0x4 : 0) | 0x2 : 0
|
||||||
table[idx + 0x200] = (unsigned)p
|
table[idx + 0x200] = (unsigned)p
|
||||||
p->mapping = address
|
p->mapping = address
|
||||||
p->page = page
|
p->page = page
|
||||||
@ -257,6 +257,9 @@ Page *Memory_arch_get_mapping (Memory *mem, unsigned address, bool *writable):
|
|||||||
*writable = (table[idx] & 4 ? 1 : 0)
|
*writable = (table[idx] & 4 ? 1 : 0)
|
||||||
return page->page
|
return page->page
|
||||||
|
|
||||||
|
void Page_arch_update_mapping (Page *page):
|
||||||
|
// TODO
|
||||||
|
|
||||||
void arch_invoke ():
|
void arch_invoke ():
|
||||||
Capability *target, *c[4]
|
Capability *target, *c[4]
|
||||||
bool wait, copy[4]
|
bool wait, copy[4]
|
||||||
|
@ -54,7 +54,8 @@ format.
|
|||||||
To understand at least something of addresses, it's important to understand the
|
To understand at least something of addresses, it's important to understand the
|
||||||
memory model of the mips architecture:
|
memory model of the mips architecture:
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item usermode code will never reference anything in the upper half of the memory (above 0x80000000). If it does, it receives a segmentation fault.
|
\item usermode code will never reference anything in the upper half of the
|
||||||
|
memory (above 0x80000000). If it does, it receives a segmentation fault.
|
||||||
\item access in the lower half is paged and can be cached. This is called
|
\item access in the lower half is paged and can be cached. This is called
|
||||||
kuseg when used from kernel code. It will access the same pages as non-kernel
|
kuseg when used from kernel code. It will access the same pages as non-kernel
|
||||||
code finds there.
|
code finds there.
|
||||||
@ -201,13 +202,16 @@ simple rule in my system: everyone must pay for what they use. For memory,
|
|||||||
this means that a process brings its own memory where the kernel can write
|
this means that a process brings its own memory where the kernel can write
|
||||||
things about it. The kernel does not need its own allocation system, because
|
things about it. The kernel does not need its own allocation system, because
|
||||||
it always works for some process. If the process doesn't provide the memory,
|
it always works for some process. If the process doesn't provide the memory,
|
||||||
the operation will fail.
|
the operation will fail.\footnote{There are some functions with \textit{alloc}
|
||||||
|
in their name. However, they allocate pieces of memory which is owned by the
|
||||||
|
calling process. The kernel never allocates anything for itself, except during
|
||||||
|
boot.}
|
||||||
|
|
||||||
Memory will be organized hierarchically. It belongs to a container, which I
|
Memory will be organized hierarchically. It belongs to a container, which I
|
||||||
shall call \textit{memory}. The entire memory is the property of another
|
shall call \textit{Memory}. The entire Memory is the property of another
|
||||||
memory, its parent. This is true for all but one, which is the top level
|
Memory, its parent. This is true for all but one, which is the top level
|
||||||
memory. The top level memory owns all memory in the system. Some of it
|
Memory. The top level Memory owns all memory in the system. Some of it
|
||||||
directly, most of it through other memories.
|
directly, most of it through other Memories.
|
||||||
|
|
||||||
The kernel will have a list of unclaimed pages. For optimization, it actually
|
The kernel will have a list of unclaimed pages. For optimization, it actually
|
||||||
has two lists: one with pages containing only zeroes, one with pages containing
|
has two lists: one with pages containing only zeroes, one with pages containing
|
||||||
@ -226,15 +230,15 @@ task, and then jumping to it.
|
|||||||
There are two options for the idle task, again with their own drawbacks. The
|
There are two options for the idle task, again with their own drawbacks. The
|
||||||
idle task can run in kernel mode. This is easy, it doesn't need any paging
|
idle task can run in kernel mode. This is easy, it doesn't need any paging
|
||||||
machinery then. However, this means that the kernel must read-modify-write the
|
machinery then. However, this means that the kernel must read-modify-write the
|
||||||
status register of coprocessor 0, which contains the operating mode, on every
|
Status register of coprocessor 0, which contains the operating mode, on every
|
||||||
context switch. That's quite an expensive operation for such a critical path.
|
context switch. That's quite an expensive operation for such a critical path.
|
||||||
|
|
||||||
The other option is to run it in user mode. The drawback there is that it
|
The other option is to run it in user mode. The drawback there is that it
|
||||||
needs a page directory and a page table. However, since the code is completely
|
needs a page directory and a page table. However, since the code is completely
|
||||||
trusted, it may be possible to sneak that in through some unused space between
|
trusted, it may be possible to sneak that in through some unused space between
|
||||||
two interrupt handlers. That means there's no fault when accessing some memory
|
two interrupt handlers. That means there's no fault when accessing some memory
|
||||||
owned by others, but the idle task is so trivial that it can be assumed to run
|
owned by others (which is a security issue), but the idle task is so trivial
|
||||||
without affecting them.
|
that it can be assumed to run without affecting them.
|
||||||
|
|
||||||
\section{Intermezzo: some problems}
|
\section{Intermezzo: some problems}
|
||||||
Some problems came up while working. First, I found that the code sometimes
|
Some problems came up while working. First, I found that the code sometimes
|
||||||
@ -246,10 +250,16 @@ In all compiled code, functions are called as \verb+jalr $t9+. It took quite
|
|||||||
some time to figure this out, but setting t9 to the called function in my
|
some time to figure this out, but setting t9 to the called function in my
|
||||||
assembly code does indeed solve the problem.
|
assembly code does indeed solve the problem.
|
||||||
|
|
||||||
|
I also found that every compiled function starts with setting up gp. This is
|
||||||
|
complete nonsense, since gp is not changed by any code (and it isn't restored
|
||||||
|
at the end of a function either). I'll report this as a but to the compiler.
|
||||||
|
Because it is done for every function, it means a significant performance hit
|
||||||
|
for any program.
|
||||||
|
|
||||||
The other problem is that the machine was still doing unexpected things.
|
The other problem is that the machine was still doing unexpected things.
|
||||||
Appearantly, u-boot enables interrupts and handles them. This is not very nice
|
Appearantly, u-boot enables interrupts and handles them. This is not very nice
|
||||||
when I'm busy setting up interrupt handlers. So before doing anything else, I
|
when I'm busy setting up interrupt handlers. So before doing anything else, I
|
||||||
first switch off all interrupts by writing 0 to the status register of CP0.
|
first switch off all interrupts by writing 0 to the Status register of CP0.
|
||||||
|
|
||||||
This also reminded me that I need to flush the cache, so that I can be sure
|
This also reminded me that I need to flush the cache, so that I can be sure
|
||||||
everything is correct. For that reason, I need to start at 0xa0000000, not
|
everything is correct. For that reason, I need to start at 0xa0000000, not
|
||||||
@ -262,12 +272,12 @@ worry about it.
|
|||||||
|
|
||||||
Finally, I read in the books that k0 and k1 are in fact normal general purpose
|
Finally, I read in the books that k0 and k1 are in fact normal general purpose
|
||||||
registers. So while they are by convention used for kernel purposes, and
|
registers. So while they are by convention used for kernel purposes, and
|
||||||
compilers will likely not touch them. However, the kernel can't actually rely
|
compilers will likely not touch them, the kernel can't actually rely on them
|
||||||
on them not being changed by user code. So I'll need to use a different
|
not being changed by user code. So I'll need to use a different approach for
|
||||||
approach for saving the processor state. The solution is trivial: use k1 as
|
saving the processor state. The solution is trivial: use k1 as before, but
|
||||||
before, but first load it from a fixed memory location. To be able to store k1
|
first load it from a fixed memory location. To be able to store k1 itself, a
|
||||||
itself, a page must be mapped in kseg3 (wired into the tlb), which can then be
|
page must be mapped in kseg3 (wired into the tlb), which can then be accessed
|
||||||
accessed with a negative index to \$zero.
|
with a negative index to \$zero.
|
||||||
|
|
||||||
At this point, I was completely startled by crashes depending on seemingly
|
At this point, I was completely startled by crashes depending on seemingly
|
||||||
irrelevant changes. After a lot of investigation, I saw that I had forgotten
|
irrelevant changes. After a lot of investigation, I saw that I had forgotten
|
||||||
@ -277,11 +287,11 @@ lead to random behaviour.
|
|||||||
|
|
||||||
\section{Back to the idle task}
|
\section{Back to the idle task}
|
||||||
With all this out of the way, I continued to implement the idle task. I hoped
|
With all this out of the way, I continued to implement the idle task. I hoped
|
||||||
to be able to never write to the status register. However, this is not
|
to be able to never write to the Status register. However, this is not
|
||||||
possible. The idle task must be in user mode, and it must call wait. That
|
possible. The idle task must be in user mode, and it must call wait. That
|
||||||
means it needs the coprocessor 0 usable bit set. This bit may not be set for
|
means it needs the coprocessor 0 usable bit set. This bit may not be set for
|
||||||
normal processes, however, or they would be able to change the tlb and all
|
normal processes, however, or they would be able to change the tlb and all
|
||||||
protection would be lost. However, writing to the status register is not a
|
protection would be lost. However, writing to the Status register is not a
|
||||||
problem. First of all, it is only needed during a task switch, and they aren't
|
problem. First of all, it is only needed during a task switch, and they aren't
|
||||||
as frequent as context switches (every entry to the kernel is a context switch,
|
as frequent as context switches (every entry to the kernel is a context switch,
|
||||||
only when a different task is entered from the kernel than exited to the kernel
|
only when a different task is entered from the kernel than exited to the kernel
|
||||||
@ -289,7 +299,7 @@ is it a task switch). Furthermore, and more importantly, coprocessor 0 is
|
|||||||
intgrated into the cpu, and writing to it is actually a very fast operation and
|
intgrated into the cpu, and writing to it is actually a very fast operation and
|
||||||
not something to be avoided at all.
|
not something to be avoided at all.
|
||||||
|
|
||||||
So to switch to user mode, I set up the status register so that it looks like
|
So to switch to user mode, I set up the Status register so that it looks like
|
||||||
it's handling an exception, set EPC to the address of the idle task, and use
|
it's handling an exception, set EPC to the address of the idle task, and use
|
||||||
eret to ``return'' to it.
|
eret to ``return'' to it.
|
||||||
|
|
||||||
@ -308,4 +318,102 @@ Having a timer is important for preemptive multitasking: a process needs to be
|
|||||||
interrupted in order to be preempted, so there needs to be a periodic interrupt
|
interrupted in order to be preempted, so there needs to be a periodic interrupt
|
||||||
source.
|
source.
|
||||||
|
|
||||||
|
During testing it is not critical to have a timer interrupt. Without it, the
|
||||||
|
system can still do cooperative multitasking, and all other aspects of the
|
||||||
|
system can be tested. So I decided to leave the timer interrupts until I'm
|
||||||
|
going to write the drivers for the rest of the hardware as well.
|
||||||
|
|
||||||
|
\section{Invoke}
|
||||||
|
So now I need to accept calls from programs and handle them. For this, I need
|
||||||
|
to decide what such a call looks like. It will need to send a capability to
|
||||||
|
invoke, and a number of capabilities and numbers as arguments. I chose to send
|
||||||
|
four capabilities (so five in total) and also four numbers. The way to send
|
||||||
|
these is by setting registers before making a system call. Similarly, when the
|
||||||
|
kernel returns a message, it sets the registers before returing to the program.
|
||||||
|
|
||||||
|
I wrote one file with assembly for receiving interrupts and exceptions
|
||||||
|
(including system calls) and one file with functions called from this assembly
|
||||||
|
to do most of the work. For syscall, I call an arch-specific\footnote{I split
|
||||||
|
off all arch-specific parts into a limited number of files. While I am
|
||||||
|
currently writing the kernel only for the Trendtac, I'm trying to make it easy
|
||||||
|
to port it to other machines later.} invoke function, which reads the message,
|
||||||
|
puts it in variables, and calls the real invoke function.
|
||||||
|
|
||||||
|
The real invoke function analyzes the called capability: if it is in page 0
|
||||||
|
(which is used by the interrupt handlers, and cannot hold real capabilities),
|
||||||
|
it must be a kernel-implemented object. If not, it is a pointer to a Receiver.
|
||||||
|
|
||||||
|
Then kernel object calls are handled, and messages to receivers are sent. When
|
||||||
|
all is done, control is returned to the current process, which may or may not
|
||||||
|
be the calling process. If it isn't, the processor state is initialized for
|
||||||
|
the new process by setting the coprocessor 0 usable bit in the Status register
|
||||||
|
and the asid bits in the EntryHi register of CP0.
|
||||||
|
|
||||||
|
\section{Paging}
|
||||||
|
While implementing user programs, I needed to think about paging as well. When
|
||||||
|
a TLB miss occurs, the processor must have a fast way to reload it. For this,
|
||||||
|
page tables are needed. On Intel processors, these need to be in the format
|
||||||
|
that Intel considered useful. On a mips processor, the programmer can choose
|
||||||
|
whatever they want. The Intel format is a page containing the
|
||||||
|
\textit{directory}, 1024 pointers to other pages. Each of those pages contains
|
||||||
|
1024 pointers to the actual page. That way, 10 bits of the virtual address
|
||||||
|
come from the directory, 10 bits from the page table, and 12 from the offset
|
||||||
|
within the page, leading to a total of 32 bits of virtual memory addressing.
|
||||||
|
|
||||||
|
On mips, we need 31 bits, because addresses with the upper bit set will always
|
||||||
|
result in an address error. So using the same format would waste half of the
|
||||||
|
page directory. However, it is often useful to have address to mapped page
|
||||||
|
information as well. For this, a shadow page table structure would be needed.
|
||||||
|
It seems logical to use the upper half of the directory page for the shadow
|
||||||
|
directory. However, I chose a different approach: I used the directory for
|
||||||
|
bits 21 to 30 (as opposed to 22 to 31). Since there are still 12 bit
|
||||||
|
addressable pages, this leaves 9 bits for the page tables. I split every page
|
||||||
|
table in two, with the data for EntryLo registers in the lower half, and a
|
||||||
|
pointer to page information in the upper half of the page. This way, my page
|
||||||
|
tables are smaller, and I waste less space for mostly empty page tables.
|
||||||
|
|
||||||
|
To make a TLB refill as fast as possible, I implemented it directly in the
|
||||||
|
assembly handler. First, I check if k0 and k1 are both zero. If not, I use
|
||||||
|
the slow handler. If they are, I can use them as temporaries, and simply set
|
||||||
|
them to zero before returning. Then I read the current directory (which I save
|
||||||
|
during a task switch), get the proper entry from it, get the page table from
|
||||||
|
there, get the proper entry from that as well, and put that in the TLB. Having
|
||||||
|
done that, I reset k0 and k1, and return. No other registers are changed, so
|
||||||
|
they need not be saved either. If anything unexpected happens (there is no
|
||||||
|
page table or no page entry at the faulting address), the slow handler is
|
||||||
|
called, which will fail as well, but it will handle the failure. This is
|
||||||
|
slightly slower than handling the failure directly, but speed is no issue in
|
||||||
|
case of such a failure.
|
||||||
|
|
||||||
|
While implementing this, I have been searching for a problem for some time. In
|
||||||
|
the end, I found that the value in the EntryLo registers does not have the bits
|
||||||
|
at their normal locations, but 6 bits back. I was mapping the wrong page in,
|
||||||
|
and thus got invalid data when it was being used.
|
||||||
|
|
||||||
|
\section{Sharing}
|
||||||
|
The next big issue is sharing memory. In order to have efficient
|
||||||
|
communication, it is important to use shared memory. The question is how to
|
||||||
|
implement it. A Page can be mapped to memory in the address space that owns
|
||||||
|
it. It can be mapped to multiple locations in that address space. However I
|
||||||
|
may remove this feature for performance reasons. It doesn't happen much
|
||||||
|
anyway, and it is always possible to map the same frame (a page in physical
|
||||||
|
memory) to multiple virtual addresses by creating an multiple Pages.
|
||||||
|
|
||||||
|
For sharing, a frame must also be mappable in a different address space. In
|
||||||
|
that case, an operation must be used which copies or moves the frame from one
|
||||||
|
Page to another. There is a problem with rights, though: if there is an
|
||||||
|
operation which allows a frame to be filled into a Page, then the rights of
|
||||||
|
capabilities to that Page may not be appropriate for the frame. For example,
|
||||||
|
if I have a frame which I am not allowed to write, and a frame which I am
|
||||||
|
allowed to write, I should not be able to write to the first frame by
|
||||||
|
transferring it to the second Page. So some frame rights must be stored in the
|
||||||
|
Page, and they must be updated during copy and move frame operations.
|
||||||
|
|
||||||
|
Move frame is only an optimization. It allows the receiver to request a
|
||||||
|
personal copy of the data without actually copying anything. The result for
|
||||||
|
the sender is a Page without a frame. Any mappings it has are remembered, but
|
||||||
|
until a new frame is requested, no frame will be mapped at the address. A Page
|
||||||
|
is also able to \textit{forget} its frame, thereby freeing some of its memory
|
||||||
|
quota.
|
||||||
|
|
||||||
\end{document}
|
\end{document}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user