Newer
Older
#include "param.h"
#include "types.h"
#include "defs.h"
#include "x86.h"
extern char data[]; // defined in data.S
pde_t *kpgdir; // for use in scheduler()

Austin Clements
committed
// Set up CPU's kernel segment descriptors.

Austin Clements
committed
void

Austin Clements
committed
{
struct cpu *c;
// Map "logical" addresses to virtual addresses using identity map.

Austin Clements
committed
// Cannot share a CODE descriptor for both kernel and user
// because it would have to have DPL_USR, but the CPU forbids
// an interrupt from CPL=0 to DPL=3.
c = &cpus[cpunum()];
c->gdt[SEG_KCODE] = SEG(STA_X|STA_R, 0, 0xffffffff, 0);
c->gdt[SEG_KDATA] = SEG(STA_W, 0, 0xffffffff, 0);
c->gdt[SEG_UCODE] = SEG(STA_X|STA_R, 0, 0xffffffff, DPL_USER);
c->gdt[SEG_UDATA] = SEG(STA_W, 0, 0xffffffff, DPL_USER);

Austin Clements
committed
c->gdt[SEG_KCPU] = SEG(STA_W, &c->cpu, 8, 0);
lgdt(c->gdt, sizeof(c->gdt));
loadgs(SEG_KCPU << 3);
// Initialize cpu-local storage.
cpu = c;
proc = 0;
}
// Return the address of the PTE in page table pgdir
// that corresponds to virtual address va. If alloc!=0,
// create any required page table pages.
walkpgdir(pde_t *pgdir, const void *va, char* (*alloc)(void))
{
pde_t *pde;
pte_t *pgtab;
pde = &pgdir[PDX(va)];
// Make sure all those PTE_P bits are zero.
memset(pgtab, 0, PGSIZE);
// The permissions here are overly generous, but they can
// be further restricted by the permissions in the page table
// entries, if necessary.
// Create PTEs for virtual addresses starting at va that refer to
// physical addresses starting at pa. va and size might not
mappages(pde_t *pgdir, void *va, uint size, uint pa, int perm, char* (*alloc)(void))
a = (char *) PGROUNDDOWN((uint) va);
last = (char *) PGROUNDDOWN(((uint) va) + size - 1);
if(*pte & PTE_P)
panic("remap");
*pte = pa | perm | PTE_P;
if(a == last)
break;
a += PGSIZE;
pa += PGSIZE;
// The mappings from logical to virtual are one to one (i.e.,

Austin Clements
committed
// segmentation doesn't do anything).
// There is one page table per process, plus one that's used
// when a CPU is not running any process (kpgdir).
// A user process uses the same page table as the kernel; the
// page protection bits prevent it from using anything other
// than its memory.
//
// setupkvm() and exec() set up every page table like this:
// 0..USERTOP : user memory (text, data, stack, heap), mapped to some unused phys mem
// KERNBASE..KERNBASE+EXTMEM: mapped to 0..EXTMEM (below extended memory)
// KERNBASE+EXTMEM..KERNBASE+end : mapped to EXTMEM..end (mapped without write permission)

Frans Kaashoek
committed
// KERNBASE+end..KERBASE+PHYSTOP : mapped to end..PHYSTOP (rw data + free memory)

Austin Clements
committed
// 0xfe000000..0 : mapped direct (devices such as ioapic)
//
// The kernel allocates memory for its heap and for user memory

Frans Kaashoek
committed
// between kernend and the end of physical memory (PHYSTOP).

Austin Clements
committed
// The virtual address space of each user program includes the kernel
// (which is inaccessible in user mode). The user program sits in
// the bottom of the address space, and the kernel at the top at KERNBASE.
void *virt;
uint phys_start;
uint phys_end;
{ P2V(0), 0, 1024*1024, PTE_W}, // First 1Mbyte contains BIOS and some IO devices
{ (void *)KERNLINK, V2P(KERNLINK), V2P(data), 0}, // kernel text, rodata

Frans Kaashoek
committed
{ data, V2P(data), PHYSTOP, PTE_W}, // kernel data, memory

Austin Clements
committed
// Set up kernel part of a page table.
pde_t*

Frans Kaashoek
committed
setupkvm(char* (*alloc)(void))

Austin Clements
committed
{

Frans Kaashoek
committed
if((pgdir = (pde_t*)alloc()) == 0)

Austin Clements
committed
return 0;
memset(pgdir, 0, PGSIZE);

Frans Kaashoek
committed
if (p2v(PHYSTOP) > (void *) DEVSPACE)
panic("PHYSTOP too high");
for(k = kmap; k < &kmap[NELEM(kmap)]; k++)
if(mappages(pgdir, k->virt, k->phys_end - k->phys_start, (uint)k->phys_start,
k->perm, alloc) < 0)

Austin Clements
committed
return pgdir;
}
// Allocate one page table for the machine for the kernel address
// space for scheduler processes.
void
kvmalloc(void)
{
// Switch h/w page table register to the kernel-only page table,
// for when no process is running.

Austin Clements
committed
void

Austin Clements
committed
{
// Switch TSS and h/w page table to correspond to process p.
{
pushcli();
cpu->gdt[SEG_TSS] = SEG16(STS_T32A, &cpu->ts, sizeof(cpu->ts)-1, 0);
cpu->gdt[SEG_TSS].s = 0;
cpu->ts.ss0 = SEG_KDATA << 3;
cpu->ts.esp0 = (uint)proc->kstack + KSTACKSIZE;
ltr(SEG_TSS << 3);
// Load the initcode into address 0 of pgdir.
// sz must be less than a page.

Austin Clements
committed
void
inituvm(pde_t *pgdir, char *init, uint sz)
{

Austin Clements
committed
panic("inituvm: more than a page");

Austin Clements
committed
memset(mem, 0, PGSIZE);

Frans Kaashoek
committed
mappages(pgdir, 0, PGSIZE, v2p(mem), PTE_W|PTE_U, kalloc);

Austin Clements
committed
memmove(mem, init, sz);
}
// Load a program segment into pgdir. addr must be page-aligned
// and the pages from addr to addr+sz must already be mapped.

Austin Clements
committed
int
loaduvm(pde_t *pgdir, char *addr, struct inode *ip, uint offset, uint sz)
{
uint i, pa, n;
pte_t *pte;
if((uint)addr % PGSIZE != 0)
panic("loaduvm: addr must be page aligned");

Austin Clements
committed
for(i = 0; i < sz; i += PGSIZE){
panic("loaduvm: address should exist");

Austin Clements
committed
pa = PTE_ADDR(*pte);
if(sz - i < PGSIZE)
n = sz - i;
else
n = PGSIZE;
if(readi(ip, p2v(pa), offset+i, n) != n)

Austin Clements
committed
}

Austin Clements
committed
}
// Allocate page tables and physical memory to grow process from oldsz to
// newsz, which need not be page aligned. Returns new size or 0 on error.

Austin Clements
committed
allocuvm(pde_t *pgdir, uint oldsz, uint newsz)

Austin Clements
committed
if(newsz > USERTOP)
a = PGROUNDUP(oldsz);
for(; a < newsz; a += PGSIZE){

Austin Clements
committed
if(mem == 0){
cprintf("allocuvm out of memory\n");
deallocuvm(pgdir, newsz, oldsz);
return 0;

Austin Clements
committed
memset(mem, 0, PGSIZE);

Frans Kaashoek
committed
mappages(pgdir, (char*)a, PGSIZE, v2p(mem), PTE_W|PTE_U, kalloc);

Austin Clements
committed
// Deallocate user pages to bring the process size from oldsz to
// newsz. oldsz and newsz need not be page-aligned, nor does newsz
// need to be less than oldsz. oldsz can be larger than the actual
// process size. Returns the new process size.

Austin Clements
committed
deallocuvm(pde_t *pgdir, uint oldsz, uint newsz)
a = PGROUNDUP(newsz);
for(; a < oldsz; a += PGSIZE){
if(pte && (*pte & PTE_P) != 0){

Austin Clements
committed
panic("kfree");
char *v = p2v(pa);
kfree(v);
// Free a page table and all the physical memory pages

Austin Clements
committed
deallocuvm(pgdir, USERTOP, 0);
if(pgdir[i] & PTE_P) {
char * v = p2v(PTE_ADDR(pgdir[i]));
kfree(v);
}
// Given a parent process's page table, create a copy

Frans Kaashoek
committed
if((d = setupkvm(kalloc)) == 0)

Austin Clements
committed
if(!(*pte & PTE_P))

Austin Clements
committed
pa = PTE_ADDR(*pte);

Austin Clements
committed
goto bad;
memmove(mem, (char*)p2v(pa), PGSIZE);

Frans Kaashoek
committed
if(mappages(d, (void*)i, PGSIZE, v2p(mem), PTE_W|PTE_U, kalloc) < 0)

Austin Clements
committed
goto bad;
// Map user virtual address to kernel address.
char*
uva2ka(pde_t *pgdir, char *uva)
{
pte_t *pte;
if((*pte & PTE_P) == 0)
return 0;
if((*pte & PTE_U) == 0)
return 0;
return (char*)p2v(PTE_ADDR(*pte));
}
// Copy len bytes from p to user address va in page table pgdir.
// Most useful when pgdir is not the current page table.
// uva2ka ensures this only works for PTE_U pages.
int
copyout(pde_t *pgdir, uint va, void *p, uint len)
while(len > 0){
va0 = (uint)PGROUNDDOWN(va);
pa0 = uva2ka(pgdir, (char*)va0);
if(pa0 == 0)
if(n > len)
n = len;
memmove(pa0 + (va - va0), buf, n);
len -= n;
buf += n;
va = va0 + PGSIZE;
}