0% found this document useful (0 votes)
3 views

Operating Systems

The document provides an overview of operating systems, detailing their purpose, structural paradigms, process management, scheduling algorithms, and memory management techniques. It explains the differences between monolithic and microkernel architectures, outlines process states and control blocks, and discusses interprocess communication and threading models. Additionally, it covers synchronization issues, race conditions, deadlock conditions, and memory virtualization methods, including static and dynamic loading and linking.

Uploaded by

ht8qghqrnd
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

Operating Systems

The document provides an overview of operating systems, detailing their purpose, structural paradigms, process management, scheduling algorithms, and memory management techniques. It explains the differences between monolithic and microkernel architectures, outlines process states and control blocks, and discusses interprocess communication and threading models. Additionally, it covers synchronization issues, race conditions, deadlock conditions, and memory virtualization methods, including static and dynamic loading and linking.

Uploaded by

ht8qghqrnd
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 32

Operating Systems

Purpose
Provide abstraction to apps
File systems
Processes, threads
VM, containers
Naming system
Manage resources
Memory
CPU
Storage

It achieves the above by implementing specific algorithms and techniques:

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

Single piece of code in memory (exists completely in kernel space)


Limited information hiding

Layered System (Dijkstra)

Monolithic layers (rings)


Each inner layer is more privileged; requires a TRAP to move down
Hardware-enforcement possible
Microkernel

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

Basis for Microkernel Monolithic Kernel


Comparison
Size Smaller than monolithic Larger than microkernel
kernel
Execution Slow Fast
Extendible Easily extendible Hard to extend
Security If a service crashes, .... If a service crashes, the whole
system crashes
Code More code Less code
Example QNX, Symbian, L4Linux Linux, BSDs

User Space and Kernel Space


Modern operating systems usually segregate virtual memory into user space and kernel
space for the purpose of memory and hardware protection from malicious and errant
software behaviour.
Program Execution
A program is a passive entity stored on disk (executable file). A program becomes a
process when an executable file is loaded into memory.

Processes
Processes isolate code that is executing. No parallel execution of instructions of a single
process. A process is comprised of:

The program code, also called text section


Current activity including program counter, processor register
Stack containing temporary data
Function parameters, return addresses, local variables
Data section containing global variables
Heap containing dynamically allocated memory
A visualization of a process address space in memory.

As a process executes it changes state:

New: process is being created


Running: instructions are being executed
Waiting: the process is waiting for some event to occur
Ready: the process is waiting to be assigned to a processor
Terminated: the process has finished execution

Process Control Block (PCB)


A data structure that stores process information.

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.

CPU scheduling decisions may take place when a process:

1. Switches from running to waiting state


2. Switches from running to ready state
3. Switches from waiting to ready
4. Terminates
Non-preemptive scheduling: Once the CPU has been allocated to a process, the process
keeps the CPU until it releases it either by terminating or by switching to the waiting state.

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.

Shortest Time-to-Completion First (STCF)


Whenever a new process arrives in the ready queue, the decision on which process to
schedule next is redone using the SJF algorithm.
Round Robin (RR)
Each process gets a small unit of CPU time (time quantum). After this time elapsed,
preempt and add to end of ready queue. Great for response time, fairness.

Priority Scheduling
A priority number is associated with each process. The CPU is allocated to the process
with the highest priority.

Potential problem: Starvation – low priority processes may never execute


Solution: Aging – as time progresses increase the priority of the process

Multilevel Feedback Queue


The ready queue consists of multiple queues. Each priority has its separate queue and
the process in the highest priority queue is scheduled.

A process can move between the various queues.


Each queue can have a different scheduling algorithm.
Aging can be implemented using multilevel feedback queue
The rules:

If Priority(A) > Priority(B), A runs (B doesn't).


If Priority(A) = Priority(B), A & B run in the round-robin fashion using the time slice
(quantum length) of the given queue.
When a job enters the system, it is placed at the highest priority (the topmost queue).
Once a job uses up its time allotment at a given level (regardless of how many times
it has given up the CPU), its priority is reduced (i.e., it moves down one queue).
After some time period S , move all the jobs in the system to the topmost queue.

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

The serial portion of an application has a disproportionate effect on performance gained


by adding addition 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.

Returns status data from child to parent (via wait() )


Process' resources are deallocated by the operating system

The parent may terminate the execution of child processes using the abort() system
call. Some reasons for doing so:

Child has exceeded allocated resources


Task assigned to child is no longer required
The parent is exiting, and the operating system does not allow a child to continue if
its parent terminates (cascading termination!)

Interprocess Communication (IPC)


Processes within a system may be independent or cooperating. A cooperating process
can affect or be affected by other processes, including sharing data.

Reasons for cooperating processes:

Information sharing
Computation speedup
Modularity
Convenience

Two models of IPC:

Shared memory - A region of memory that is shared by cooperating processes


(under the control of users).
Message passing - Messages are passed between processes through the kernel
(under the control of OS).

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.

The address space of a multi-threaded process is different than a single-threaded


process.
User Threads: User threads are supported above the kernel, without kernel support.
These are the threads that application programmers would put into their programs (and
are managed by user level thread libraries).

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.

How do we measure good?

CPU utilization: keep the CPU as busy as possible.


Throughput: number of processes that complete their execution per time unit.
Turnaround time: amount of time to execute a particular process.
Waiting time: amount of time a process has been waiting in the ready queue.
Response time: amount of time it takes from when a request was submitted until the
first response is produced.

Multithreading Models
link

Mitigating Race Conditions


A race condition arises if multiple threads of execution enter the critical section at roughly
the same time; both attempt to update the shared data structure, leading to a surprising
(and perhaps undesirable) outcome.

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

To solve synchronization issues the following criteria must be satisfied:

Mutual Exclusion
If Process P is executing in its critical section, then no other processes can be executing
i

in their critical sections.

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

Some potential solutions to the problem of race conditions are:

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:

int TestAndSet(int *old_ptr, int new) {


int old = *old_ptr; // fetch old value at old_ptr
*old_ptr = new; // store ’new’ into old_ptr
return old; // return the old value
}

Compare-and-Swap instruction:

int CompareAndSwap(int *ptr, int expected, int new) {


int original = *ptr;
if (original == expected)
*ptr = new;
return original;
}

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.

Here's an example of using TestAndSet() in a spin lock:

typedef struct __lock_t {


int flag;
} lock_t;

void init(lock_t *lock) {


// 0: lock is available, 1: lock is held
lock->flag = 0;
}
void lock(lock_t *lock) {
while (TestAndSet(&lock->flag, 1) == 1)
; // spin-wait (do nothing)
}

void unlock(lock_t *lock) {


lock->flag = 0;
}

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.

If a semaphore's value is initialized as 1 it will act as a lock.


If a semaphore's value is initialized as 0 it will act as a condition variable.

As a condition variable, the semaphore can order waiting threads for wakeup via
signaling.

int sem_wait(sem_t *s) {


// decrement the value of semaphore s by one
// wait if value of semaphore s is negative
}

int sem_post(sem_t *s) {


// increment the value of semaphore s by one
// if there are one or more threads waiting, wake one
}
Peterson's Algorithm
Doesn't work on modern processors. Instructions can now be reordered due to
optimization. Link to website

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

There is communication deadlock (processes waiting for communication). There is also


resource deadlock (processes waiting for memory to be released).

Four conditions need to hold simultaneously for a deadlock to occur:

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.

Recovering: Rollback process to a safe state

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

Bringing library code into memory.

Dynamic Linking

Uses stubs to only bring library into memory when needed. Linking is postponed to
execution time.

Dynamic Relocation (Hardware-based)


This is implemented by two hardware registers in each CPU: one is called the base
register, and the other the limit register. This allows us to place the address space
anywhere in physical memory and ensure that any requested addresses are within the
confines of the address space. The processor then translates any virtual memory
references into physical memory references by adding the value of the base register to
the virtual address.

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)

Introduces a few problems:

External memory fragmentation.


Isn't flexible enough to support a sparse address space (i.e., a large but sparsely-
used heap all in one virtual segment -> entire heap must reside in memory in order
to be accessed).

Segmentation allows physical address space of a process to be noncontiguous. A


program is allocated a collection of segments. Each virtual address consists of a two
tuple, <segment-number, offset> .

A segment table maps to physical addresses. Each table has:

base: contains the starting physical address where the segments reside in memory.
limit: specifies the length of the segment

Free Space Management

Use a data structure to track a free chunks of space in memory.


Malloc allocates requested space and space for the header block.

There are a few strategies for allocating space:

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.

Per-process data structure.


Page table is stored in memory.
Page Table Entries (PTE)

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:

Translation Look-aside Buffers (TLB)


A TLB is part of the chip's memory-management unit (MMU), and is simply a hardware
cache of popular virtual-to-physical address translations. The hardware searches the
entries in parallel to see if there is a match. A TLB entry might look like:

Relies on:

Spatial locality
Temporal locality

Control Flow:

1. Extract VPN from virtual address.


2. Check if TLB holds translation.
3. If so, TLB hit: extract PFN, concatenate and access physical memory.
4. If not, TLB miss: check page table, update TLB with translation, retry instruction.

A hit rate can be calculated by:

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)

Memory Management Unit (MMU)


The MMU is a hardware device that at run time maps virtual addresses to physical
addresses.

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.

This avoids external fragmentation.


Avoids problem of varying sized memory chunks.

Paging works by:

Divide physical memory into fixed-size blocks called frames.


Divide virtual memory into blocks of the same size called pages.
Keep track of free frames.
To run a program of size N pages, need to find N free frames and load program.
Set up a page table to translate virtual to physical addresses. Each process has its
own page table.
Backing store likewise split into blocks.
Still have internal fragmentation

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.

Page-table base register (PTBR) points to the page table.


Page-table length register (PLTR) indicates size of the page table.

Translation Look-aside Buffer (TLB):

Typically small (64 to 1,024 entries)


On a TLB miss, the value is loaded into the TLB for faster access next time.
Some TLBs store address-space identifiers (ASIDs) in each TLB entry which
uniquely identifies each process to provide address space protection for that process
(for context switches)

Persistence
Disks

Can be subdivided into partitions


Disks or partitions can be RAID protected against failure
Disk or partition can be used raw – without a file system, or formatted with a file
system
Partitions also known as mini disks, slices
Entity containing file system is known as a volume
Each volume containing a file system also tracks that file ...

Nonvolatile Memory

Keeps data after system is turned off


SSDs
Can be more reliable than HDDs
More expensive per MB
Maybe have a shorten life span
Less capacity
Much faster
Busses can be too slow

RAID

Redundant Array of Inexpensive Disks


Increases the mean time to failure
Mean time to repair – exposure time when another failure could cause data loss
Mean time to data loss based on above factors
Frequently combined with NVRAM to improve write performance

Error Detection and Correction

Parity, CRC detection

Files and Directories


Creating Files
To create a file, use open() which returns a file descriptor.

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;
};

Reading And Writing Files


The tool strace allows you to see what system calls have been made by a program.
Here's example output of reading the contents of a file using cat :

prompt> strace cat foo


...
open("foo", O_RDONLY|O_LARGEFILE) = 3
read(3, "hello\n", 4096) = 6
write(1, "hello\n", 6) = 6
hello
read(3, "", 4096) = 0
close(3) = 0
...
prompt>

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;

It is also possible for processes to share an open file table entry:


Writing
The file system, most of the time, will buffer calls to write() for some time. The system
call fsync(int fd) causes the file system to force all dirty data to disk for a particular file
descriptor.

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.

Caching And Buffering


Caches are used to stored popular blocks.

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:

Confidentiality (Bell-LaPadula - Cant read up cant write down)


Integrity (Biba)
Availability

How:

Least privilege

What:

OS
Code
CPU
People

Mechanisms:

Lampson access matrix


Access control list
Questions:

Motivation for Many to Many?


Are the kernel thread mappings just for system calls?
Do user threads have to be mapped to kernel threads in order to utilize more than
one processor?
CPU can only see a process, not its threads. In order to make threads visible to
CPU we use a threading library to map the user threads to kernel threads;
allowing user threads to run on multiple cores/processors.
Do race conditions only happen when threads are running on multiple cores?
No, with preemption concurrent threads can execute critical sections. On
processors running in parallel, race conditions are just more likely to occur.

Midterm:

Example of deadlock and how it occurs


Define the four necessary conditions that characterize deadlock
Identify a deadlock situation in a resource allocation graph
Evaluate the four different approaches for preventing deadlocks
Walk through scheduling algorithms (RR etc)

Scheduling, Concurrency & Threads, Locks

You might also like