Today's plan
- Minix context switch implementation
- Minix kernel synchronization
- Minix process suspension and reactivation
- a.out and kernel executable formats
- booting
Minix context switch
- context switch on (hardware) interrupt or on system call
(software interrupt)
- interrupt from user mode will reload stack pointer from
a given location in memory (book, p. 129)
- Minix updates this location every time a new process is
started so registers are automatically saved in the process descriptor
- some registers are saved automatically, and others are pushed
by save (p. 595)
- the result is in sigregs (p 547) format
- save also increments the _k_reenter count
Interrupts in Minix
- hardware interrupt causes execution of hwint00..hwint15
- these routines call save, which saves the register,
sets up a kernel stack (including an appropriate return address), and
returns
- these routines then disable the specific interrupt (differently
for interrupts 0-7 and 8-15) and re-enable interrupts in general
- these routines then call a device-dependent interrupt handler
written in C
- once the interrupt handler returns, these routines disable
interrupts, re-enable the specific interrupt, and return
- the return goes to the address set up by save, which
will either return to the kernel code that was interrupted, or
start or restart a process (often not the one that was
interrupted -- for example, a newly awakened task)
Device-Specific Interrupt Handler and interrupt
- the device-specific interrupt handler acknowledges the
interrupt
- the device-specific interrupt handler then calls interrupt
(kernel/proc.c, p. 604) with the task to be sent a message
- interrupt checks to see if the kernel is already active,
and if so, defers its operation
- otherwise, interrupt checks to see if the task is waiting for
an interrupt -- if not, the fact is recorded in the task's process
descriptor
- finally, if the task is waiting, interrupt sends it
a message to say a hardware interrupt has been received, makes that
task ready to run, and returns.
- in any case, interrupt never blocks -- if other kernel
code is executing, it simply returns after recording that the interrupt
was held back
System Call Handling
- sys_call (p. 605) is called from the system call (soft
interrupt) handler (p. 595) in a process similar to a hardware interrupt,
with interrupts enabled
- its three arguments are a function (send, receive, or both), the
task with which to communicate, and a message
- system calls from user processes can only go to one of the
servers, and can only be both send and receive
- send, receive, or both may block
Synchronization of reentrant processes in Minix
- _k_reenter incremented by _s_call before enabling
interrupts
- _k_reenter checked by interrupt, if nonzero,
interrupt disables interrupts (lock), sets
p_int_held true for the process, adds the process to a global queue,
and re-enables interrupts
- the only global used by sys_call is proc_ptr (the
current process), and it is accessed before any process scheduling can
occur by calling mini_send or mini_receive.
- sys_call may block and queue its caller, but
sys_call itself never blocks and never executes another
process (until after it returns), so
sys_call does not need to worry about reentrant calls
- other routines that want to insure interrupt does not
interfere with them simply set switching to true before
their critical section, and to false afterwards -- this guarantees
interrupt will not change the process queues or the
current process
- all this works (without atomic operations) because:
- interrupt will defer sending a
message (by very careful design) if another part of the kernel is running
- interrupted calls to interrupt nest properly, so their
local variables are safe even though all the calls are using the same
kernel stack
- interrupted system calls are allowed to complete (by
interrupt) with no change to their local or global variables
- system calls never suspend until the very end (s_call
falling through to restart), so they do not need to be
reentrant
Process Blocking and Reawakening in Minix
- a process blocks when it sends a message to a process that
is not receiving, or when it receives a message and no sender is ready
- a process is awakened when the process it sent a message to,
or received a message from, receives or sends the message
- blocking a process means removing it from the appropriate queue
(unready, p. 610), as well as setting the appropriate flags
(SENDING, RECEIVING, or both) and, if sending, putting
it on the receiver's queue (so the ANY receive semantics can
be implemented in O(1) time)
- in some sense the process is blocked as soon as it makes the
system call (software interrupt), and the sytem call is a separate
thread with its own stack -- in this view, "blocking" the process by
removing it from its ready queue simply completes the operation begun
by the software interrupt
- in any case, the process that is blocked at the system call is
the one that will be reawakened
- awakening a process means removing it from the appropriate queue,
perhaps after filling its receive buffer
- the semantics of send and receive are such that a process cannot
receive until it is done sending
- to avoid deadlocks, mini_send will fail if the receiver
is trying to send to us (mini_send, p 607, lines 7075-7085),
perhaps transitively (A sending to B sending to C -- C is not allowed
to send to A)
a.out format
- described in include/a.out.h
- 2-byte magic number helps avoid inadvertent execution of text
and other random files
- 1-byte CPU field identifies architecture on which it is meant to run
- 1-byte header length allows for header variability
- text segment contains executable code
- data segment contains initialized data, with initial values
- bss segment contains data initialized to zero, so only the size
is recorded in the file
- data and bss segments
- entry point records the address to jump to when executing the file
kernel file and memory formats
- see figure 2-27 on p. 101
- kernel on disk is simply a concatenation of:
- one a.out for the kernel and all the tasks
- one a.out for the memory manager
- one a.out for the file system
- one a.out for the inet server
- one a.out for the init process
- entry point is the label MINIX in mpx386.s,
which then calls cstart and finally main
booting
- ROM built-in to the machine (the BIOS) loads the first sector (512
bytes) from the floppy disk and executes it
- or reads the partition table from the hard disk, locates the active
partition, loads the boot block (512 bytes) from the active partition,
and executes it
- boot sector program is hardcoded with the sectors of a program
called boot which does the actual initialization -- a different
boot sector is written by installboot depending on where
boot is on the disk
- boot understands the minix file system, and searches for
a file named /minix or, in a /minix/ directory, the
newest file or whatever file is specified by the boot parameters
- boot copies this file to memory (at location 2K for Minix)
and jumps to the entry point
- diskless workstations need enough networking in the ROM
to request a kernel image from a server