mirror of
git://projects.qi-hardware.com/iris.git
synced 2025-01-30 10:01:05 +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)
|
||||
if !ret:
|
||||
return NULL
|
||||
ret->physical = 0
|
||||
ret->data.frame = 0
|
||||
ret->data.flags = 0
|
||||
return ret
|
||||
|
||||
Thread *Memory::alloc_thread ():
|
||||
@ -203,10 +204,11 @@ Cappage *Memory::alloc_cappage ():
|
||||
Cappage *ret = (Cappage *)search_free (sizeof (Cappage), (void **)&cappages)
|
||||
if !ret:
|
||||
return NULL
|
||||
ret->page = (Capability *)zalloc ()
|
||||
if !ret->page:
|
||||
ret->data.frame = zalloc ()
|
||||
if !ret->data.frame:
|
||||
free_cappage (ret)
|
||||
return NULL
|
||||
ret->data.flags = 0
|
||||
return ret
|
||||
|
||||
Memory *Memory::alloc_memory ():
|
||||
@ -230,8 +232,8 @@ void Memory::free_page (Page *page):
|
||||
if page->next:
|
||||
page->next->prev = page->prev
|
||||
unuse ()
|
||||
if page->physical:
|
||||
pfree (page->physical)
|
||||
if page->data.frame:
|
||||
pfree (page->data.frame)
|
||||
free_obj (page)
|
||||
|
||||
void Memory::free_thread (Thread *thread):
|
||||
@ -311,8 +313,8 @@ void Capability::invalidate ():
|
||||
|
||||
void Memory::free_cappage (Cappage *p):
|
||||
for unsigned i = 0; i < CAPPAGE_SIZE; ++i:
|
||||
p->page[i].invalidate ()
|
||||
zfree ((unsigned)p->page)
|
||||
((Capability *)p->data.frame)[i].invalidate ()
|
||||
zfree (p->data.frame)
|
||||
free_obj (p)
|
||||
|
||||
void Memory::free_memory (Memory *mem):
|
||||
|
@ -58,12 +58,22 @@ extern "C" {
|
||||
#define THREAD_FLAG_USER 0x1fffffff
|
||||
|
||||
#define CAP_PAGE_MAP 1
|
||||
#define CAP_PAGE_SHARE 2
|
||||
#define CAP_PAGE_SHARE_COW 3
|
||||
#define CAP_PAGE_FORGET 4
|
||||
// Not an operation; a capability without this bit cannot write to the page. */
|
||||
#define CAP_PAGE_WRITE 5
|
||||
#define CAP_PAGE_COPY 2
|
||||
#define CAP_PAGE_MOVE 3
|
||||
#define CAP_PAGE_GET_FLAGS 4
|
||||
#define CAP_PAGE_SET_FLAGS 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
|
||||
/* 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_SET_DEATH_NOTIFY 2
|
||||
@ -71,7 +81,7 @@ extern "C" {
|
||||
|
||||
#define CAPPAGE_SIZE 102
|
||||
/* Cappage has page's operations as well. */
|
||||
#define CAP_CAPPAGE_SET 6
|
||||
#define CAP_CAPPAGE_SET 7
|
||||
#define CAP_CAPPAGE_ALL_RIGHTS 0x1ff
|
||||
|
||||
#ifndef __KERNEL
|
||||
|
12
init.ccp
12
init.ccp
@ -31,7 +31,7 @@ static void init_idle ():
|
||||
// initialize idle_page
|
||||
idle_page.prev = NULL
|
||||
idle_page.next = NULL
|
||||
idle_page.physical = 0x80000000
|
||||
idle_page.data.frame = 0x80000000
|
||||
idle_page.refs = NULL
|
||||
idle_page.address_space = NULL
|
||||
current = &idle
|
||||
@ -123,7 +123,7 @@ static void init_threads ():
|
||||
unsigned idx = (p - (shdr->sh_addr & PAGE_MASK)) >> PAGE_BITS
|
||||
if !pages[idx]:
|
||||
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
|
||||
if !mem->map (pages[idx], p, writable):
|
||||
panic (0x22446688, "unable to map initial page")
|
||||
@ -135,8 +135,8 @@ static void init_threads ():
|
||||
page = mem->alloc_page ()
|
||||
if !page:
|
||||
panic (0x00220022, "out of memory")
|
||||
page->physical = mem->zalloc ()
|
||||
if !page->physical || !mem->map (page, p, true):
|
||||
page->data.frame = mem->zalloc ()
|
||||
if !page->data.frame || !mem->map (page, p, true):
|
||||
panic (0x33557799, "unable to map initial bss page")
|
||||
else:
|
||||
if !write:
|
||||
@ -146,14 +146,14 @@ static void init_threads ():
|
||||
break
|
||||
if a < shdr->sh_addr:
|
||||
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:
|
||||
if pages[p]:
|
||||
continue
|
||||
++top_memory.limit
|
||||
top_memory.pfree (thread_start[i] + (p << PAGE_BITS))
|
||||
Page *stackpage = mem->alloc_page ()
|
||||
stackpage->physical = mem->zalloc ()
|
||||
stackpage->data.frame = mem->zalloc ()
|
||||
if !stackpage || !mem->map (stackpage, 0x7ffff000, true):
|
||||
panic (0x13151719, "unable to map initial stack page")
|
||||
Receiver *recv = mem->alloc_receiver ()
|
||||
|
133
invoke.ccp
133
invoke.ccp
@ -10,7 +10,7 @@ Capability *Memory::find_capability (unsigned code, bool *copy):
|
||||
return NULL
|
||||
Capability *page = (Capability *)(code & PAGE_MASK)
|
||||
for Cappage *p = cappages; p; p = p->next:
|
||||
if p->page == page:
|
||||
if p->data.frame == (unsigned)page:
|
||||
return &page[num]
|
||||
else:
|
||||
// Normal capability
|
||||
@ -234,31 +234,92 @@ static void thread_invoke (unsigned target, unsigned protected_data, Capability
|
||||
default:
|
||||
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
|
||||
Cappage *cappage
|
||||
ShareData *share_data
|
||||
if (target & CAPTYPE_MASK) == CAPTYPE_PAGE:
|
||||
page = (Page *)protected_data
|
||||
cappage = NULL
|
||||
share_data = &page->data
|
||||
else:
|
||||
page = NULL
|
||||
cappage = (Cappage *)protected_data
|
||||
switch request:
|
||||
share_data = &cappage->data
|
||||
switch data[0]:
|
||||
case CAP_PAGE_MAP:
|
||||
if !page:
|
||||
return
|
||||
page->address_space->map (page, data, target & CAP_PAGE_WRITE)
|
||||
page->address_space->map (page, data[1], target & CAP_PAGE_WRITE)
|
||||
break
|
||||
case CAP_PAGE_SHARE:
|
||||
case CAP_PAGE_COPY:
|
||||
// TODO
|
||||
case CAP_PAGE_SHARE_COW:
|
||||
// TODO
|
||||
case CAP_PAGE_FORGET:
|
||||
case CAP_PAGE_MOVE:
|
||||
// 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:
|
||||
if !cappage || data >= CAPPAGE_SIZE || !(target & CAP_PAGE_WRITE):
|
||||
if !cappage || data[1] >= CAPPAGE_SIZE || !(target & CAP_PAGE_WRITE):
|
||||
return
|
||||
Capability *c = &cappage->page[data]
|
||||
Capability *c = &((Capability *)cappage->data.frame)[data[1]]
|
||||
c->invalidate ()
|
||||
// clone_capability needs a Memory, but doesn't use it when storage is provided.
|
||||
top_memory.clone_capability (cap, copy, c)
|
||||
@ -277,7 +338,7 @@ static void capability_invoke (unsigned target, unsigned protected_data, Capabil
|
||||
default:
|
||||
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:
|
||||
// data[0] is the request.
|
||||
// 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):
|
||||
// You are not allowed to perform this operation.
|
||||
return true
|
||||
if (target & (CAPTYPE_MASK | (1 << CAP_RECEIVER_CALL))) == (CAPTYPE_RECEIVER | (1 << CAP_RECEIVER_CALL)):
|
||||
// This is a call capability.
|
||||
Capability r
|
||||
Capability *c0 = c[0]
|
||||
if ~(unsigned)c0->target & ~KERNEL_MASK:
|
||||
fill_cap (&r, protected_data, ~0)
|
||||
c[0] = &r
|
||||
copy[0] = true
|
||||
bool ret = kernel_invoke ((unsigned)c0->target, c0->protected_data, d, c, copy, c0)
|
||||
r.invalidate ()
|
||||
return ret
|
||||
else:
|
||||
// Kernel call: don't create actual capablities.
|
||||
reply = NULL
|
||||
reply_receiver = (Receiver *)protected_data
|
||||
return kernel_invoke ((unsigned)c0->target, c0->protected_data, d, c, copy, c0)
|
||||
if (target & (CAPTYPE_MASK | (1 << CAP_RECEIVER_REPLY))) == (CAPTYPE_RECEIVER | (1 << CAP_RECEIVER_REPLY)):
|
||||
// This is a reply capability.
|
||||
((Receiver *)protected_data)->send_message (~0, d, c, copy)
|
||||
while self->parent:
|
||||
self = self->parent
|
||||
self->invalidate ()
|
||||
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:
|
||||
if target & (1 << CAP_RECEIVER_CALL):
|
||||
// This is a call capability.
|
||||
Capability r
|
||||
Receiver *t = c[0]->target
|
||||
unsigned p_d = c[0]->protected_data
|
||||
if ~(unsigned)t & ~KERNEL_MASK:
|
||||
fill_cap (&r, protected_data, ~0)
|
||||
c[0] = &r
|
||||
copy[0] = true
|
||||
bool ret = kernel_invoke ((unsigned)t, p_d, d, c, copy)
|
||||
r.invalidate ()
|
||||
return ret
|
||||
else:
|
||||
// Kernel call: don't create actual capablities.
|
||||
reply = NULL
|
||||
reply_receiver = (Receiver *)protected_data
|
||||
return kernel_invoke ((unsigned)t, p_d, d, c, copy)
|
||||
if target & (1 << CAP_RECEIVER_REPLY):
|
||||
// This is a reply capability.
|
||||
((Receiver *)protected_data)->send_message (~0, d, c, copy)
|
||||
return true
|
||||
receiver_invoke (target, protected_data, c[1], d[0], d[1])
|
||||
break
|
||||
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)
|
||||
break
|
||||
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
|
||||
case CAPTYPE_CAPABILITY:
|
||||
capability_invoke (target, protected_data, c[1], d[0], d[1])
|
||||
break
|
||||
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
|
||||
default:
|
||||
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.
|
||||
return target->send_message (protected_data, data, cap, copy)
|
||||
// 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"
|
||||
|
||||
struct Page : public Object <Page>:
|
||||
unsigned physical
|
||||
Page_arch arch
|
||||
|
||||
struct Thread : public Object <Thread>:
|
||||
Receiver *receivers
|
||||
unsigned pc, sp
|
||||
@ -80,8 +76,18 @@ struct Capability : public Object <Capability>:
|
||||
bool invoke (unsigned data[4], Capability *cap[4], bool copy[4])
|
||||
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>:
|
||||
Capability *page
|
||||
ShareData data
|
||||
|
||||
struct Memory : public Object <Memory>:
|
||||
Free *frees
|
||||
@ -162,6 +168,7 @@ void Memory_arch_free (Memory *mem)
|
||||
bool Memory_arch_map (Memory *mem, Page *page, unsigned address, bool write)
|
||||
void Memory_arch_unmap (Memory *mem, Page *page, unsigned address)
|
||||
Page *Memory_arch_get_mapping (Memory *mem, unsigned address, bool *writable)
|
||||
void Page_arch_update_mapping (Page *page)
|
||||
void arch_invoke ()
|
||||
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)
|
||||
if table[idx]:
|
||||
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
|
||||
p->mapping = address
|
||||
p->page = page
|
||||
@ -257,6 +257,9 @@ Page *Memory_arch_get_mapping (Memory *mem, unsigned address, bool *writable):
|
||||
*writable = (table[idx] & 4 ? 1 : 0)
|
||||
return page->page
|
||||
|
||||
void Page_arch_update_mapping (Page *page):
|
||||
// TODO
|
||||
|
||||
void arch_invoke ():
|
||||
Capability *target, *c[4]
|
||||
bool wait, copy[4]
|
||||
|
@ -54,7 +54,8 @@ format.
|
||||
To understand at least something of addresses, it's important to understand the
|
||||
memory model of the mips architecture:
|
||||
\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
|
||||
kuseg when used from kernel code. It will access the same pages as non-kernel
|
||||
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
|
||||
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,
|
||||
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
|
||||
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. The top level memory owns all memory in the system. Some of it
|
||||
directly, most of it through other memories.
|
||||
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. The top level Memory owns all memory in the system. Some of it
|
||||
directly, most of it through other Memories.
|
||||
|
||||
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
|
||||
@ -226,15 +230,15 @@ task, and then jumping to it.
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
owned by others, but the idle task is so trivial that it can be assumed to run
|
||||
without affecting them.
|
||||
owned by others (which is a security issue), but the idle task is so trivial
|
||||
that it can be assumed to run without affecting them.
|
||||
|
||||
\section{Intermezzo: some problems}
|
||||
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
|
||||
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.
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
on them not being changed by user code. So I'll need to use a different
|
||||
approach for saving the processor state. The solution is trivial: use k1 as
|
||||
before, but first load it from a fixed memory location. To be able to store k1
|
||||
itself, a page must be mapped in kseg3 (wired into the tlb), which can then be
|
||||
accessed with a negative index to \$zero.
|
||||
compilers will likely not touch them, the kernel can't actually rely on them
|
||||
not being changed by user code. So I'll need to use a different approach for
|
||||
saving the processor state. The solution is trivial: use k1 as before, but
|
||||
first load it from a fixed memory location. To be able to store k1 itself, a
|
||||
page must be mapped in kseg3 (wired into the tlb), which can then be accessed
|
||||
with a negative index to \$zero.
|
||||
|
||||
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
|
||||
@ -277,11 +287,11 @@ lead to random behaviour.
|
||||
|
||||
\section{Back to the idle task}
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
@ -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
|
||||
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
|
||||
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
|
||||
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}
|
||||
|
Loading…
x
Reference in New Issue
Block a user