Files
CoderSherlock.github.io/_posts/2022-02-22-cs350-labs.md
T

5.0 KiB

title, date, tags, author, cover
title date tags author cover
Labs of CS350 2022-02-22 17:08:17 -0400
Xv6
Teaching
Operating system
Binghamton university
Pengzhan Hao /static/2022-02/BU.jpeg

This will be a series regarding lab I gave during the spring 2022 semester.

The reason why I am writing this down is because it has been a week and no students ask for the solution of the last Lab. I realise that learning gap between students are huge, especially when a non-profit university is admitting more and more students. To help all students in understanding concepts of modern OS, I decided to write this post.

It starts with the past lab content I have (as the skelton), and will be amended with extra materials I think it helps. Remember, it's for helping in learning. DON'T COPY & PASTE CODE!

Index

Lab1: Introduction of Makefile and Xv6.
Lab3: System calls for process management.
Lab4: Inter-processes communication.
Lab6/7: CPU scheduling.

Lab1-Introduction

Lab3-Process

Lab4-IPC

Lab6-7-Scheduling

First user process in xv6

In xv6, as the same as conventional linux OS, the very first user level process is init. Before init's running, all the OS bootstraps are happened in a high privileged mode(kernel level).

Xv6's kernel has the entry point as the main function located in the file main.c. The main function invokes 17 functions to set up kernel page tables, interrupt handlers, I/O devices and etc. When all kernel preparations are done, by calling the function userinit(), kernel will boot up process init.

int
main(void)
{
  kinit1(end, P2V(4*1024*1024)); // phys page allocator
  kvmalloc();      // kernel page table
  mpinit();        // collect info about this machine
  lapicinit();
  seginit();       // set up segments
  cprintf("\ncpu%d: starting xv6\n\n", cpu->id);
  picinit();       // interrupt controller
  ioapicinit();    // another interrupt controller
  consoleinit();   // I/O devices & their interrupts
  uartinit();      // serial port
  pinit();         // process table
  tvinit();        // trap vectors
  binit();         // buffer cache
  fileinit();      // file table
  ideinit();       // disk
  if(!ismp)
    timerinit();   // uniprocessor timer
  startothers();   // start other processors
  kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers()
  userinit();      // first user process
  // Finish setting up this processor in mpmain.
  mpmain();
}

It's tricky since that init is a user process, but kernel can't call any user level system calls to create it. Why? 1. Kernel has all privileges to create a user process. So it doesn't need to call system calls such as fork(). And 2. All other user processes can be created by forking from its parent. Forking including clone the whole user virtual memory layout. However, first process have no parent to fork from. That's why its makes the creation of the first user process becomes so unique.

In proc.c, userinit() define there gives us the whole procedure of creating init. Similar to the fork(), but more simple. Process control block(structures for storing the process status) was created at the very first by calling allocproc(). After then, by invoking setupkvm()(defined in vm.c), kernel memory map was setup for the process. During setting up kernel memory map, a page size virtual memory will assigned to the process as ready. And later, this page size memory will be used to store instructions of init.

Followed by setup kernel stack for the init process, calling inituvm() will load init's text into the page that just being allocated. inituvm() takes 3 arguments: a pointer to the process's page directory (p->pgdir), a char-type pointer declared from external which point to init's text segment(_binary_initcode_start), and a char-type pointer which point to an external integer as the size of the init's text segment(_binary_initcode_size). Simply put, it will load instructions of init into the memory.

So now, the problem becomes when and where did instructions for init has compiled into the kernel?

void
userinit(void)
{
  struct proc *p;
  extern char _binary_initcode_start[], _binary_initcode_size[];
  
  p = allocproc();
  initproc = p;
  if((p->pgdir = setupkvm()) == 0)
    panic("userinit: out of memory?");
  inituvm(p->pgdir, _binary_initcode_start, (int)_binary_initcode_size);
  p->sz = PGSIZE;
  memset(p->tf, 0, sizeof(*p->tf));
  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

  safestrcpy(p->name, "initcode", sizeof(p->name));
  p->cwd = namei("/");

  p->state = RUNNABLE;
}

If you search the keyword "_binary_initcode_start" in the source code, you can't find any references. The clue comes from the Makefile.