Operating Systems
Operating Systems
Purpose
Provide abstraction to apps
File systems
Processes, threads
VM, containers
Naming system
Manage resources
Memory
CPU
Storage
Scheduling
Concurrency
Transactions
Security
Structural Paradigms
The kernel is a program that is at the core of an operating system and generally has
complete control over everything in the operating system.
Monolithic Kernel
All non-essential components removed from the kernel (and moved into user space),
for modularization, extensibility, isolation, security, and ease of management
A collection of OS services running in user space
Heavy communication costs through message passing (marshaled through the
kernel)
Comparison
Processes
Processes isolate code that is executing. No parallel execution of instructions of a single
process. A process is comprised of:
Process Scheduling
Process scheduler select among available processes for next execution on CPU core. Its
goal is to maximize CPU use by quickly switching processes onto the CPU core. In order
to do this, the scheduler maintains scheduling queues of processes:
Ready queue: set of all processes residing in main memory, ready and waiting to
execute.
Wait queues: set of processes waiting for an event.
Processes migrate among the various queues. Processes can be CPU bound or I/O
bound both of these concepts influence how the scheduler prioritizes a process.
Batch (FIFO)
First in first out scheduling. As a result it is quite vulnerable to the convoy effect where a
number of relatively-short potential consumers of a resource get queued behind a
heavyweight resource consumer.
Shortest-Job-First (SJF)
Gives minimum average waiting time for a given set of processes. However, if many short
jobs are queued, they will cause any preexisting heavyweight processes to starve.
Priority Scheduling
A priority number is associated with each process. The CPU is allocated to the process
with the highest priority.
Amdahl's Law
Identifies performance gains from adding additional cores to an application that has both
serial and parallel components.
1
speedup ≤
(1−S)
S +
N
S = serial portion
N = processing cores
Context Switching
When the CPU switches to another process, the system must save the state of the old
process and load the saved state for the new process via a context switch. The context of
a process is represented in the PCB.
The Dispatcher
The dispatcher gives control of the CPU to the process selected by the CPU scheduler.
This includes:
Switching context
Switching to user mode
Jumping to the proper location in the user program to restart that program
Dispatch latency is the time it takes for the dispatcher to stop one process and start
another running.
Process Termination
Process executes last statement and then asks the operating system to delete it using the
exit() system call.
The parent may terminate the execution of child processes using the abort() system
call. Some reasons for doing so:
Information sharing
Computation speedup
Modularity
Convenience
Threads
Each thread has its own program counter (PC), CPU register, and stack but does not
have its own memory. Unlike processes, threads share memory. The main goal of threads
is to utilize parallelism across CPUs with multiple processor cores.
Kernel Threads: Kernel threads are supported within the kernel of the OS itself. All
modern operating systems support kernel level threads, allowing the kernel to perform
multiple simultaneous tasks and/or to service multiple kernel system calls simultaneously.
Why do threads need their own stack? Each thread should be able to do their own
function call and return.
Implicit Threading: Creation and management of threads done by compilers and run-
time libraries rather than programmers.
Multithreading Models
link
A critical section is a piece of code that accesses a shared variable (or resource) and
must not be concurrently executed by more than one thread.
Solution Criteria
Mutual Exclusion
If Process P is executing in its critical section, then no other processes can be executing
i
Progress
If no process is executing in its critical section and there exist some processes that wish
to enter their critical section, then the selection of the process that will enter the critical
section next cannot be postponed indefinitely.
Bounded Waiting
A bound must exist on the number of times that other processes are allowed to enter their
critical sections after a process has made a request to enter its critical section and before
that request is granted. Assume that each process executes at a nonzero speed. No
assumption concerning relative speed of the n processes.
Possible Solutions
Memory Barrier
A memory barrier instruction is used to ensure that all loads and stores instructions are
completed before any subsequent load or store operations are preformed. Therefore,
even if instructions were reordered, the memory barrier ensures that the store operations
are completed in memory and visible to other processors before future load or store
operations are preformed.
Hardware Instructions
Special hardware instructions that allow us to either test-and-modify the content of a word,
or to swap the contents of two words atomically (uninterruptedly). Essentially, read and
set a value as one instruction.
Test-and-Set instruction:
Compare-and-Swap instruction:
Mutex Locks
A "lock" or "mutex" is just a variable that holds the state of the lock at any instant in time. It
is either available or acquired. While a lock is held by a thread, other threads are
prevented from entering the critical section while the first thread maintains its hold.
The TAS functionality allows the thread to atomically update the flag to 1 and check its old
value. Simple spin locks as such are not fair and may lead to starvation.
Spin locks present the problem of priority inversion. This is where a thread of lower priority
acquires a lock on a shared resource that a thread of higher priority needs, causing the
program to hang or other threads of lower priority to run while the high priority thread
spins. This can be solved with priority inheritance where a high priority thread that is
waiting on a low priority thread's lock can temporarily boost the lower thread's priority
enabling it to run.
Semaphores
Semaphores are objects with an integer value that can be manipulated with two routines;
sem_wait() and sem_post() . One can use semaphores as both locks and condition
variables. Semaphores allow for threads and processes to sleep until a wake signal
instead of spin waiting and wasting CPU cycles. The initial value of a semaphore
determines this behavior.
As a condition variable, the semaphore can order waiting threads for wakeup via
signaling.
int flag[2];
int turn;
void init() {
// indicate you intend to hold the lock w/ ’flag’
flag[0] = flag[1] = 0;
// whose turn is it? (thread 0 or 1)
turn = 0;
}
void lock() {
// ’self’ is the thread ID of caller
flag[self] = 1;
// make it other thread’s turn
turn = 1 - self;
while ((flag[1-self] == 1) && (turn == 1 - self))
; // spin-wait while it’s not your turn
}
void unlock() {
// simply undo your intent
flag[self] = 0;
}
The intuition is that by handing off control to other threads if a thread is interrupted while
inside its critical section the other thread won't overwrite turn values and enter its own
critical section.
Deadlock
Two or more processes are waiting indefinitely link
Mutual exclusion
Hold-and-wait
No preemption
Circular wait
Prevention: FIX HERE
Avoiding deadlocks: Simplest and most useful model requires that each process declare
the maximum number of resources of each type that it may need. The deadlock
avoidance algorithm dynamically examines the resource allocation state to ensure there is
no circular resource locks.
Memory
One main goal of the operating system is to virtualize memory. Main memory and
registers are the only storage devices the CPU can access directly.
Address Space
The address space of a process contains all of the memory state of the running program.
Components such as the code (instructions), stack, and heap are all stored in a process'
address space. An address space is simply an abstraction that the OS is providing to a
running program.
For a program to be run, it must be brought from the backing store into memory and
placed within a process' address space (loaded into memory).
Library calls such as malloc() and free() are library calls that are built on top of
system calls which ask the OS for more memory or to release some back to the system.
One such system call is called brk() (and similarly, sbrk() ) which is used to change
the location of a program's break: the location of the end of the heap. The sbrk() call is
passed an increment.
Static Loading
Must load entire program from disk into memory allocate it to a process.
Dynamic Loading
At runtime only load what we need (when it is called), don't have to load entire code
section into memory when the program starts.
Static Linking
Dynamic Linking
Uses stubs to only bring library into memory when needed. Linking is postponed to
execution time.
Segmentation
The idea of having a base and limit pair per virtual segment of the address space such
that a segment is just a contiguous portion of the address space of a particular length i.e.,
code, stack, and heap. This allows the OS to place each of the segments in different parts
of physical memory.
More predictable, works better in situations like embedded systems using RTOS. Has
issues with external fragmentation because segments are placed contiguously into
memory using the base and limit registers.
Segmentation allows a segment to be shared across multiple running programs (multiple
virtual segments mapped to the same physical segment). Also, segmentation saves space
in physical memory (doesn't allocate space for the area between virtual stack and heap
segments)
base: contains the starting physical address where the segments reside in memory.
limit: specifies the length of the segment
Best fit
Worst fit
First fit
Next fit
Paging
Paging adopts the idea of chopping up space into fixed-size pieces
Page Table
The major role of the page table is to store address translations for each of the virtual
pages of the address space.
Valid bit: indicates whether a translation is valid (mark all unused pages as invalid).
Protection bitxs: indicates whether a page can be read from, written to, or executed
from.
Present bit: indicates whether this page is in physical memory or on disk.
Dirty bit: indicates whether a page has been modified since it was brought into
memory.
Reference bit: tracks if a page has been accessed
Paging and Segments:
Multi-level Page Tables:
Relies on:
Spatial locality
Temporal locality
Control Flow:
Number of hits
TLB hit rate =
Number of accesses
In the following diagram, the TLB improves performance due to spatial locality in the
scenario where each index of an array is accessed. Thus, only the first access to an
element on a page yields a TLB miss. This also demonstrates the effect of page size on
hit rate. The diagram represents the virtual address space:
In the scenario of a TLB miss, the hardware raises an exception, raises the privilege level
to kernel mode, and jumps to a trap handler. The trap handler looks up the translation in
the page table, updates the TLB, and returns from the trap. Upon which, the instruction is
retried.
Virtual vs. Physical Address Space
Virtual address: generated by the CPU
Physical address: address seen by the memory management unit (MMU)
Relocation Register: The value in the relocation register is added to every address
generated by a user process at the time it is sent to memory.
Memory Allocation
Compaction
A solution to memory fragmentation where the memory contents are shuffled to place all
free memory together in one large block.
Noncontiguous Allocation
Allows a process to reside in different locations in memory.
Paging
Physical address space of a process can be noncontiguous; a process is allocated
physical memory whenever its available.
Address Translation:
The address generated by the CPU is divided into:
Page number – used as an index into a page table which contains base address of
each page in physical memory.
Page offset – combined with base address to define the physical memory address
that is sent to the memory unit.
Page Table:
The page table is kept in main memory.
Persistence
Disks
Nonvolatile Memory
RAID
A file descriptor is just an integer, private per process (but can be duplicated by child
processes), and is used to access files. The proc structure on Unix systems stores an
array of open files, indexed by file descriptor.
struct proc {
...
struct file *ofile[nofile]; // Open files
...
}
The struct file tracks which underlying file the descriptor refers to, the current offset,
and other details such as permissions.
struct file {
int ref;
char readable;
char writable;
struct inode *ip;
uint offset;
};
The xv6 kernel keeps a system-wide open file table which is just an array with a lock.
struct {
struct spinlock lock;
struct file file[NFILE];
} ftable;
On-Disk Organization
Disk space is broken up into blocks. The space is comprised of a superblock, inodes, and
datablocks. Bitmaps are used to track space allocated to both inodes and datablocks.
The superblock contains information about this particular file system (how many inodes
and datablocks, where the inode table begins, etc). It will also include a magic number to
identify the file system type.
Thus, when mounting a file system, the OS will read the superblock first to initialize
various parameters, and then attach the volume to the file-system tree.
Inodes
An inode holds the metadata of a file. It is referred to by a number. Inside each inode is
virtually all of the information you need about a file.
Directory Organization
As do files, directories have inodes within the inode table.
With write buffering, delaying writes allows the file system to batch some updates into a
smaller set of I/Os. By buffering a number of writes in memory, the system can then
schedule the subsequent I/Os and increase performance.
Security
We want:
How:
Least privilege
What:
OS
Code
CPU
People
Mechanisms:
Midterm: