First Process from XV6

Brian Pan
4 min readMar 8, 2018

Before start

This topic will talk about after preparing several resources for operating systems (page allocators, kernel page table, process table, …), how the first process creates and works.

The steps of creating first process are the following:

  1. Allocate process structure (Context, Page table directory, Kernel Stack pointer…)
  2. Create a page table for kernel space
  3. Allocate page for the user init code
  4. Configure trap frame for iret

Allocate process structure

Look into proc.h. There is a struct called proc illustrating process structure in xv6.

struct proc {uint sz;                 // Size of process memory (bytes)
pde_t* pgdir; // pointer for Page table Directory
char *kstack; // pointer that points to the bottom of the kernel stack for this process

enum procstate state; // Process state
int pid; // Process ID
struct proc *parent; // Parent Process

struct trapframe *tf; // pointer points to trapframe of current syscall
struct context *context; // swtch() here to run process

void *chan; // If non-zero, sleeping on chan; for multi-process
void *killed; // If non-zero, have been killed
struct file *ofile[NOFILE]; //Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
}

This proc structure involves several concepts like synchronization, memory management, trap/interrupt, file systems.

void 
userinit(void)
{
struct *p;
extern char _binary_initcode_start[], _binary_initcode_size[];

// initialize a runnable process
// prepare kstack, trapframe, trapret
p = allocproc();
.....
}

Create a page table for kernel space

In void userinit(void) (proc.c), it sets up first user process. Then, take a look at how it generates page table.

static struct proc *initproc;void 
userinit(void)
{
struct *p;
extern char _binary_initcode_start[], _binary_initcode_size[];

// alloc resources for proc
p = allocproc();
// initproc is a static global pointer
initproc = p;
// setup kernel virtual memory
if(p->pgdir = setupkvm()==0)
panic("userinit: out of memory?");
....
}

How does setupkvm() work will start another one post to discuss more. In sum, this method get free space from freelist and map the virtual memory toward phyisical memory.

Important to mention is that when assigning kernel page table, all process share same kernel page table. This is for saving limited spaces in XV6 memory (234MB only). The implementation is in pte_t *walkpgdir(pde_t *pgdir, const void *va, int alloc). By looping the same struct kmap in vm.c, it is obvious that only one copy of the kernel page table among all processes.

process 1, process 2 share same kernel page tables

Allocate page for the user init code

Still, look at code snippet in proc.c

// init user virtual memory
inituvm(p->pgdir, _binary_initcode_start, (int)_binary_initcode_size);

inituvm(pde_t *pgdir, char *init, uint, sz) (vm.c)

void
inituvm(pde_t *pgdir, char *init, uint sz)
{
char *mem;
if(sz >= PGSIZE)
panic("inituvm: more than a page");
mem = kalloc();
memset(mem, 0, PGSIZE);
// PTE_U means user accessible
mappages(pgdir, 0, PGSIZE, V2P(mem), PTE_W|PTE_U);
memmove(mem, init, sz);
}

Apparently, inituvm assigns spaces in freelist for data of the _binary_initcode_start.

Configure trap frame for iret

Recap: Trap frame in stack
  1. Prepare trap frame in allocproc(void) (proc.c 76)
static struct proc*
allocproc(void)
{
.....
// stack ptr
sp = p->kstack+KSTACKSIZE;
// Leave room for trap frame
sp -= sizeof *p->tf;
p->tf = (struct trapframe*)sp;
...
}

2. Set up values in trap frame (userinit proc.c)

void
userinit(void)
{
...
// set up trap frame
memset(p->tf, 0, sizeof(*p->tf));
// Descriptor privilege level to 3
p->tf->cs = (SEG_UCODE << 3) | DPL_USER;
p->tf->ds = (SEG_UDATA << 3) | DPL_USER;
p->tf->es = p->tf->ds;
p->tf->ss = p->tf->ds;
p->tf->eflags = FL_IF;
p->tf->esp = PGSIZE;
p->tf->eip = 0; // beginning of initcode.S
...
}

Well, here sets up the Descriptor Privilege Level to 3 is used for privilege control. This issue is important when it comes to talk about system call. That will be another post to talk about this issue.

The rest of the userinit is to change the cwd to ‘/’ and update the process state to RUNNABLE.

Then, after finishing create first process, main.c will run mpmain() that finds a RUNNABLE process and does the Context Switch.

Summary

This post introduces how first process starts. It does the following:

  1. Prepare the Kernel Memory Space ( userinit(), setupkvm() )
  2. Prepare Kernel Stack( allocproc() )
  3. Prepare User Memory Space( userinit(), inituvm() )
  4. Set up Trap Frame ( allocproc(), userinit() )
  5. Set up Context ( allocproc() )

At last, we have a well-prepared process for XV6 to run. Yeah!

Reference

  1. brilliant OS course by Anton Burtsev in UC Irvine

--

--