coordination between the drivers, servers, user processes, and
the kernel is essential
in Minix, kernel functions needed by the servers or drivers
are placed in the system task
like all tasks, the system task receives messages, executes appropriate
code, and returns results, without blocking
a special call, cause_sig (p. 199), sends a signal
to a user process:
a bit is set in the process's pending signal bitset
if the process was executable, it is dequeued
a notification is sent to the process manager
a number of calls perform the kernel portion of process management
system calls:
fork,
exec,
exit,
times,
kill.
other calls perform tasks on behalf of the process manager or file
server, for example mapping memory to a process,
copying data between adress spaces, converting
virtual to physical addresses, tracing a process execution,
and rebooting or shutting down the system
Supporting Fork and Exec
fork (p. 729) requires copying the proc table entry (the PM has already
selected the child process ID), splitting the remaining quantum among
parent and child, changing the return value for the
child (which is returned in the reply message), and clearing the usage times
pending and enabled signals should only be kept by the parent
exec resets the stack pointer and places a new PC in
the process table, then readies the process for execution. Someone
else must map the new code segments to the memory before calling this
code
exit does bookkeeping and removes the process from any queues for
any messages it was trying to send, then cancels signals and sets the
slot to free -- someone else is responsible for closing file descriptors,
deallocating memory, etc
Minix kernel tasks
the clock task and the system task are separately schedulable,
but run in kernel space
these are essentially threads of the kernel "process"
so the minix kernel has three threads: the message passing code,
which includes the scheduler/message passing, the clock thread,
and the system thread
each of these threads executes briefly, then suspends again
Virtual Memory in Minix
umap_local, umap_remote,
umap_bios, starting on p. 760
a virtual address vir_addr corresponds to a physical
address pa based on two per-segment fields, mem_vir
and mem_phys (see lines 10015 and 10018.)
possible segments include text, data, and stack
vir_addr - mem_vir is the offset o into the segment
p = o + mem_phys is the physical address
computation on lines 10015-10019
all computations aligned on "page" (click) boundaries
Minix I/O
structure reflects conceptual structure of:
device-specific I/O: drivers, implemented as driver processes
device-independent I/O: server layer
user-level I/O: user process layer
Minix device drivers
device drivers execute as separate processes, and (after
initialization) only when they receive a message
user processes send messages to the file system or PM server, and
servers send messages to the device drivers
device drivers may also receive messages from the interrupt handlers
interrupt handler messages carry no data, they are just notifications
messages from the servers may carry information about the
(minor) device number, which process is requesting the I/O, how many
bytes are requested, where on the device and where in memory the bytes
are or should be placed, and especially what operation is requested
Minix device driver threading
each device driver may be interrupted, but no variables are
shared with other device drivers, so there can be no race conditions:
each device driver is much like a monitor
device drivers may receive messages, and therefore suspend
the block device drivers will only accept messages from the interrupt
handler while they are waiting for a result, so (like a non-waiting monitor)
these device drivers are not re-entrant
the terminal task will accept keyboard input while waiting for a
response from a serial line read, but will not accept further requests
for keyboard input until the first one has been satisfied
Minix device driver structure
a device driver must:
initialize its device(s)
loop forever,
receiving a message (usually device drivers block here)
carrying out the message (occasionally device drivers block here)
sending a response (device drivers should never block here)
all the block device drivers (RAM, floppy, hard disk, CD, SCSI) share
similar functions, and are implemented with a single, generic main loop
in drivers/libdriver/driver.c, p. 773
the generic main loop uses an array of function pointers (of type
struct driver, p. 770) to execute device-specific operations
Minix block driver
for example, a dev_open message results in a call to
(*dp->dr_open)(dp, &mess)
a call to read or write results in a call to
(*dp->dr_rdwt)(dp, &mess)
drivers/libdriver/driver.c contains generic versions for
some of these functions, for example do_rdwt, which
calls
if a block device chooses to use do_rdwt, it can implement
these functions to provide the desired functionality
for example, for the RAM disk, m_transfer can perform
the I/O, and m_prepare just verifies the device number and returns
the device base and size
in contrast, for the hard disk (at_wini), w_transfer
sets up the operation, then waits for the transfer to complete
the wait can be waiting for an interrupt (line 12898) in case of read,
or waiting in a busy loop (line 12909) in case of a write
in-class question: the "interrupt wait" waits for an interrupt,
but will receive from ANY (line 13132), which means it may discard a
message. Why not listen to a message from the hardware only? Why
does the driver never get a message from another source while waiting
for its interrupt? (5 minutes)
File and Block I/O structure
all device-independent file operations are done by the FS server
this includes interfacing to device drivers, buffering,
error reporting, and using a device-independent block size
Block I/O operations
open a device -- check to make sure it is actually there, initialize
it, read the partition table
close a device
read a block, specifying the start location on disk, the number of
bytes, and the buffer address
write a block, specifying the start location on disk, the number of
bytes, and the buffer address
IOCTL a device, getting or setting the partition table to/from memory
do a scattered read or a scattered write, providing an array of blocks
to be read or written
Scattered I/O operations
each scattered operation is a collection of reads or writes
of individual (variable sized) blocks
each Minix 3 scattered operation is all reads or all writes
each Minix 3 scattered operation is sequential on the device, but the
source/results may be scattered among different buffers
optional reads allow for prefetching, and the device driver can
decide whether prefetching is a good idea (e.g. not beyond the end of
a track)
Partition Table
a partition table for the x86 is a list of 4 partition entries at
offset 446 (x1BE) in the first (boot) sector of a disk
each partition entry has a 16 bytes of data (the last 2 bytes
of the first sector hold the magic number 55AA, line 11571)
the first 8 bytes include flags (e.g. 0x80 for a bootable disk)
and geometry (heads, sectors, cylinders)
the final 8 bytes are two 4-byte numbers recording the linear
number of the start and size of the partition, in sectors
partitions may be nested, e.g. the first partition may have
another partition table in its first sector