Hand-In Procedure
You are to turn in this homework at the beginning of lecture. Please write up your answers to the exercises below and hand them in to a staff member at the beginning of the lecture. Write your CSE login ID at the top of your submission
wait()
. When the parent calls wait()
eventually,
it still expects to read the correct exitcode that the child returned.
To support this functionality, UNIX does not completely remove the process
till it's parent has called wait()
on it.
Such processes that have completed execution but still have an entry in the process table are called zombie processes. Usually, the presence of zombie processes in the system for a long time indicates a bug in the program (it is a common error).
Read about Zombie process at this wikipedia article. Also read about the SIGCHLD signal.
Turn in:
In class we discussed that the shell implements "&
" functionality
by not calling wait()
immediately. Should the shell
never call wait()
? When should it call
wait()
? Answer by providing short pseudo-code.
Be sure you have read xv6 chapter 2 and familiarized yourself with the xv6 code it refers to before lecture.
Use QEMU with GDB to trace through the free list creation
and memory allocation process described in the first part of the chapter.
Make sure you understand how the C pointer arithmetic
in kfree
's for
loop works:
especially the relationship between the
rp, r, and p variables.
Also step through
the creation of the first process by allocproc()
,
and then step through the first context switch into that process
via the assembly language swtch()
code.
Nothing to turn in for this part.
Read: xv6
trapasm.S
,
trap.c
,
syscall.c
,
initcode.S
,
usys.S
.
Skim
vectors.pl
,
vectors.S
,
lapic.c
,
ioapic.c
,
picirq.c
.
Introduction
Try to understand xv6's
trapasm.S
,
trap.c
,
syscall.c
,
initcode.S
,
usys.S
.
Skim
vectors.S
,
lapic.c
,
ioapic.c
,
picirq.c
.
The assigned chapter (chapter 3 of xv6 book) provides a commentary for these files, but you may find it useful to also consult: Chapter 5 of Intel's system programming guide. Be aware that terms such as exceptions, traps, interrupts, faults and aborts have no standard meaning.
Turn in:
In xv6, set a breakpoint at the beginning of syscall()
to
catch the very first system call. What values are on the stack at
this point? Turn in the output of x/37x $esp
at that
breakpoint with each value labeled as to what it is (e.g.,
saved %ebp
for trap
,
trapframe.eip
, scratch space, etc.).
Read: swtch.S and proc.c (focus on the code that switches
between processes, specifically scheduler
and sched
).
Also process creation: sys_fork() and copyproc().
In this part of the homework you will investigate how the kernel switches between two processes.
Assignment:
Suppose a process that is running in the kernel
calls sched()
, which ends up jumping
into scheduler()
.
Turn in:
Where is the stack that sched()
executes on?
Turn in:
Where is the stack that scheduler()
executes on?
Turn in:
When sched()
calls swtch()
,
does that call to swtch()
ever return? If so, when?
Now think back to lecture 2 and the invariants
that gcc expects any function, including swtch
, to
maintain. Compare these invariants with what swtch
actually implements, and the state that our kernel maintains in
a struct context
.
Turn in: Could swtch
do less work and still be
correct? Could we reduce the size of a struct context
?
Provide concrete examples if yes, or argue for why not.
Surround the call to swtch()
in scheduler()
with calls
to cprintf()
like this:
cprintf("a"); swtch(&cpu->scheduler, &proc->context); cprintf("b");
Similarly,
surround the call to swtch()
in sched()
with calls
to cprintf()
like this:
cprintf("c"); swtch(&proc->context, cpu->scheduler); cprintf("d");
Rebuild your kernel and boot it on QEMU. With a few exceptions you should see a regular four-character pattern repeated over and over.
Turn in: What is the four-character pattern?
Turn in: The very first characters are ac
. Why does
this happen?
A bootsector is only 512 bytes which is a very short space to be able to fit any useful code. So typically a bootsector loads another "program" from disk and jumps to it. For example, the bootsector might load the bootloader code (e.g., GRUB, LILO) and jump to it. The bootloader, in turn, loads the actual OS.
xv6 bootsector contains a very simple IDE disk device driver to read the sectors from disk. An IDE disk has a very simple interface and hence it is possible to fit a device driver inside the bootsector. But what if the boot device (containing the bootloader and the kernel) was a CDROM, SCSI disk, USB disk, etc. All these devices have much more complex interfaces and it will be hard (if not impossible) to fit their device drivers in a bootsector. So how does the OS boot from these devices?
The answer is that BIOS helps in the boot process. BIOS supports certain "BIOS functions" that the bootsector (or bootloader) can call. Some examples of the functionality supported by these functions are: get the physical memory size, read the nth sector of the boot device, etc. This functionality is exposed using the interrupt interface and the int instruction (similar to how system calls are handled on 32-bit linux). Pintos is an OS that is capable of booting through a USB device. You can try booting your personal computer using pintos by using the following command:
pintos --hardware --This will create a disk image called
os.dsk
. You can
write this disk image to your USB device using:
dd if=os.dsk of=/dev/sdbassuming your USB disk is at
/dev/sdb
.
Skim through information on "BIOS Interrupt calls" on Wikipedia here, here and here.
Turn in:
Look
at Pintos bootsector code and list all the BIOS
interrupt calls used by Pintos and their corresponding functionality.
This completes the homework.
Based on MIT 6.828 materials by Frans Kaashoek and others; and Yale OS course materials by Bryan Ford.