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. 168-169)
- Minix updates this location every time a new process is
started so registers are automatically saved in the process descriptor,
the process table entry for the currently running process
- some registers are saved automatically, and others are pushed
by save (p. 712)
- the result is in sigregs (p 658) format
- save also increments the _k_reenter count
Interrupts in Minix
- see comments in kernel/mpx386.s, p. 707
- hardware interrupt causes execution of hwint00..hwint15
- these routines call save, which saves the register,
sets up a kernel stack if needed (if _k_reenter >= 0),
adds a return address that calls
_restart, and returns
- these routines then call intr_handle (kernel/i8259.c, p. 735),
which is written in C, with an argument to tell it which interrupt
it is handling
- once intr_handle returns, these routines disable
the specific interrupt (should be re-enabled by the device driver),
re-enable interrupts in general, 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, specified by
next_ptr in restart (p. 713)
- this new task will typically be a device driver
Device-Specific Interrupt Handling and intr_handle
- intr_handle is given a list of hooks, stored in
the irq_handlers array, and calls them in turn
- irq_handlers are set up by calls to put_irq_handler
which, except for the clock device, is called by do_irqctl
in kernel/system/do_irqctl.c (not in book)
- do_irqctl calls generic_handler, which transforms
interrupts into messages by calling lock_notify
in kernel/proc.c
- generic_handler then conditionally re-enables the
specific interrupt by returning a bit
- lock_notify calls mini_notify after "locking"
(disabling interrupts) but only if we are not already re-entering
(lock is declared on line 4861 of kernel/const.h,
p. 692
- mini_notify either creates the message and makes
the receiver ready to execute, or saves the message information (one
bit) if the receiver is already active
- in any case, intr_handle 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. 724) is called from the system call (soft
interrupt) handler (p. 712) in a process similar to a hardware interrupt,
but with interrupts enabled
- its three arguments are a "call number" which includes a function
(send, receive, or both) and flags (non-blocking), 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 save (to decide which
stack to use) and lock_notify, if less than zero,
lock_notify disables interrupts (which if called from
an interrupt, are already disabled)
- since interrupts are disabled whenever sys_call and
the other functions in kernel/proc.c are called,
these can modify global variables, especially the process queue,
rdy_head, rdy_tail, and the current and
next process, curr_ptr and next_ptr
- sys_call may block and queue its caller, but
sys_call itself never blocks and never executes another
process (until after it returns), so (and because interrupts are suspended)
sys_call does not need to worry about reentrant calls
Process Blocking and Reawakening in Minix
- literally, a process blocks when it makes a system call or
is interrupted, because the kernel is a separate thread with its
own stack
- logically, 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 appropriate process receives its
message or sends it a message
- blocking a process means removing it from the appropriate queue
(dequeue, p. 729), 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)
- the process that is blocked at the system call is
the one that will be reawakened, though perhaps after many other
processes get to execute
- awakening a process means inserting it into the ready 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 726, lines 7605-7610),
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 (not in book)
- 2-byte magic number helps avoid inadvertent execution of text
and other random files
- 1-byte flags field identifies different styles of executables
- 1-byte CPU field identifies architecture on which it is meant to run
- 1-byte header length allows for header variability
- segment lengths for text, data, bss segments (below)
- entry point records the address to jump to when executing the file
- 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, no actual data is stored
kernel file and memory formats
- see figure 2-31 on p. 129
- kernel on disk is simply a concatenation of:
- one a.out for the kernel and the tasks and some drivers
- one a.out for the memory manager (pm)
- one a.out for the file system (fs)
- one a.out for the inet server
- one a.out for the rs process
- one a.out for the init process
- anything else the OS is configured for
- entry point is the label MINIX in mpx386.s
(p. 709), which then calls cstart (p. 716) and finally
main (p. 718)
booting
- ROM built-in to the machine (the BIOS) loads the first sector (512
bytes) from the floppy disk and executes it
- or reads a floppy's worth of data from a CD drive and treats
it as a floppy
- 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 /boot/image or, in a /boot/image/ 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
I cannot provide a creative commons license for this page because it
includes an image I don't own. You may apply the creative commons
license from Sep 12th to the text of this page, but not to the image.