Today's plan
- inter-process communication
- races
- pipes
- semaphores
- monitors
- message passing
- read-copy-update (RCU)
Remaining Minix System Calls
- chroot
- protection:
chmod,
getuid,
setuid,
chown.
Inter-process Communication
- processes may depend on each other
- example: a consumer may wait for the producer to produce, or a producer
may wait for the consumer to consume and free up space in the shared buffer
- IPC mechanisms may support one or more of the following:
- synchronization: needed so process A can tell when process B has reached a
certain point. Example: barrier synchronization stops all processes
in a group until the last one has arrived at the barrier
- data exchange: process A receives data from process B. May
involve synchronization so process A waits for process B to complete
preparing the data
- mutual exclusion (a special case of synchronization):
at most one process may be accessing a certain set of variables. If
a process expects to be the only one accessing a given set of variables,
that process is in its critical region
Race Conditions
- incrementing is not an atomic operation
- if:
- one process (A) begins to increment a memory location by reading
a value into a register
- then A gets suspended, then
- another process (B) increments the memory location, then
- the first process writes the (now) incorrect value back to memory
the final result should be n+2, but is only n+1
- other non-atomic operations include inserting a value into
a data structure (e.g. adding a file to a print spool or adding a
value to a queue) or writing contents to a file
Avoiding Race Conditions
- prevent execution of any other process while process A is in
its critical region:
- by disabling interrupts, or
- by requiring all code to acquire a mutual exclusion lock
before entering a critical region, and release it at the end of the
critical region, or
- by requiring all code to acquire a binary semaphore
- code that cannot acquire a lock must spin, that is, loop
- code that cannot acquire (down) a semaphore will be put to
sleep (suspended), and woken up once the semaphore is released (uped)
- setting locks or semaphores generally requires atomic operations:
- interrupts can be disabled while the lock or semaphore is accessed, or
- hardware atomic operations such as Test-And-Set instructions can
perform the operation while guaranteeing atomicity, even in the case
of multiple processors
- more complex software-only solutions are available as well,
including Peterson's algorithm
- semaphores are much more general, allowing up to n acquire
operations. This can allow semaphores to be used, for example, to keep
track of the number of items (or free spaces) in a buffer.
Reader and Writer Locks
- some data structures have reader processes and
writer processes
- any data structure could have any number of readers if there were
no writers
- a read lock can be acquired any number of times, as long as there
are no writers
- the corresponding write lock can only be acquired if there are no
other readers or writers
- as an alternative:
- writers increment a counter when they enter and exit their
critical section
- writers only enter if the counter was even
- readers record the value of the counter at the beginning of the
critical section, and only enter if the counter is even
- readers repeat their critical section if the counter has changed
by the end of the critical section
- very efficient for writers, can be very slow (livelock) for readers
Pipes
- ideal for consumer-producer problem
- on Unix/Posix: pipes of bytes. In theory pipes of arbitrary
data structures could also be useful
- on Unix/Posix can use arbitrary file operations, including
read and write, select, and close.
- consumer reads from pipe, up to a maximum specified by the
read call, may block if no data is available
- producer writes to pipe, may block if no buffer space is available
- in a system with very little memory, could synchronize
producer and consumer to copy data from producer's buffer directly to
consumer's buffer
- in practice a system buffer gives better performance than no buffer,
so most systems allow the producer to write up to a certain amount
of data (typically 4096 bytes), then consumer can read large blocks of data
- a single thread that is both producer and consumer of the same
pipe will block if the system suspends the consumer until the buffer is full
- a single thread that is both producer and consumer of the same
pipe will block if the process tries to write more than the buffer hods
Monitors
- a monitor is a module (similar to a C++/Java class) with some
private variables that only the monitor routines can access
- at most one monitor routine can be active at any given time
- when a second monitor routine is called, it suspends (before
executing) until the first has completed
- a monitor routine never needs to be re-entrant, that is,
it is guaranteed to complete before the next call to the same routine
(or any other routine in the same monitor)
- a monitor routine that cannot continue can wait, and another
is allowed to run (and eventually signal the one that called wait)
- writing re-entrant code is harder (and errors are harder to debug),
so monitors help write correct concurrent programs
Common Practice
- unfortunately, monitors require language support
- instead, in practice programmers must
- identify related global variables that are changed in individual
critical regions
- create a lock associated with each group of variables
- acquire the lock each time before reading or modifying the variables
- release the lock each time after reading or modifying the variables
- these operations are error-prone
- failure to release a lock may deadlock the entire system, since all
processes may eventually be waiting for the lock to be released
- it is easy to forget to release a lock -- just return
from within the critical region
Message Passing
- can extend pipes to pass structured data rather than just bytes
- can also extend pipes to data among different processors or
across a network
- the result is called message passing
- message passing systems must answer questions of:
- identifying receiver, e.g. a process or a mailbox (a mailbox could
be accessed via a file descriptor, e.g. a socket)
- dealing with lost messages or overflowing buffers: acknowledgements,
sequence numbers
- efficiency, avoiding copying unless absolutely necessary --
important on message-passing supercomputers
- message passing may not provide enough synchronization, so
external forms of synchronization, e.g. barrier synchronization,
can be used
- another form of synchronization is to have the sender
rendezvouz (first process to reach the rendezvouz point waits
for the other) with the receiver, so data can be copied
directly from the sender's to the receiver's buffer
- much in common with networking, but sometimes on a single system
Read-Copy-Update (RCU)
- some data structures have reader processes and
writer processes
- any data structure could have any number of readers if there were
no writers
- instead of having reader/writer locks,
- readers could simply notify the system that they are reading, and
when they are done reading
- writers could
- create a copy of part of the data structure
- wait for all the readers to finish reading
- destructively update the data structure to point to the new copy
- this is called RCU, and is very efficient for readers

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 License.