5.0 KiB
title, date, tags, author, cover
| title | date | tags | author | cover | ||||
|---|---|---|---|---|---|---|---|---|
| Labs of CS350 | 2022-02-22 17:08:17 -0400 |
|
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.