OS Notes
OS Notes
These notes are not intended to replace Atlidaki’s notes. They are a summary mostly of definitions that
doesn’t contain everything mentioned on the slides.
You are a commenter and can comment on anything you think is false or needs more information.
Lecture 01,02
Operating System (OS): A layer of software that acts as an abstraction between the
hardware and application software. It manages hardware resources and provides a
standardized, safe, and efficient environment for programs to run.
Kernel: The core, privileged part of the OS that implements the fundamental abstractions
and mechanisms. It is the "OS" code that runs in kernel mode.
—------------------------------------------------------------------------------------------------------------------------
Lecture 03
Exception Handler: The specific code within the operating system that is designed to deal
with a particular type of exception.
Portable Operating System Interface for UNIX (POSIX) is the IEEE standard for portable
UNIX-based OS syscall interfaces.
POSIX Defines
● Process: An instance of a program in execution.
● Thread: A single flow of control within a process.
● File: An object that can be read from and/or written to.
—------------------------------------------------------------------------------------------------------------------------
Lecture 04
Program Memory Layout (Virtual Address Space)
● .text Segment: Contains the executable instructions (machine code) of the
program. Typically read-only and executable. (read-execute perms)
● .rodata Segment: Contains read-only data, such as string constants and global
constants. (read-only perms)
● .data Segment: Contains initialized global and static variables. (read-write perms)
● .bss Segment: Contains uninitialized global and static variables. The OS
initializes this segment to zeros at load time. (read-write perms)
● Heap: A region for dynamic memory allocation during program execution (e.g., via
malloc in C). It grows towards higher memory addresses. (read-write perms)
● Stack: A region that manages function call execution. It stores local variables,
return addresses, and function parameters. It grows towards lower memory
addresses. (read-write perms)
Linking Methods
● Static Linking: The process of copying all necessary library code directly into the
final executable at compile time.
○ Advantage: Self-contained, portable.
○ Disadvantage: Executable file bloat, as common library code is duplicated.
● Dynamic Linking: The process of deferring the resolution of some library references
until runtime.
○ Advantage: Saves disk and memory space (shared libraries), easier to
update libraries.
○ Disadvantage: More complex loading process, potential for "DLL hell".
Dynamic Linker (e.g., [Link]): A special shared library loaded by the OS Loader whose
job is to finish preparing a dynamically linked program for execution by loading the required
shared libraries and resolving the symbolic references between them.
/proc/<PID>/maps: A Linux pseudo-file that shows the memory map of a running process,
detailing the virtual address ranges, permissions, and backing files for each segment.
Important Observations
● The addresses seen by a program are virtual addresses, not physical ones. The OS
and MMU (Memory Management Unit) translate these to physical addresses.
● The transition from a passive executable file on disk to an active process in
memory is a complex process involving the loader and, for dynamic binaries, the
dynamic linker.
—------------------------------------------------------------------------------------------------------------------------
Lecture 05
Program: A passive file on disk containing instructions and data (the "recipe").
Process Control Block (PCB): A kernel data structure (task_struct in Linux) that stores all
information about a process (state, registers, memory map, open files, etc.).
Process States
● Ready: Waiting to be assigned to a CPU.
● Running: Executing on a CPU.
● Waiting/Blocked: Unable to run until an external event occurs.
● Terminated: Finished execution.
○ Zombie: A special terminated state where the process has exited, but its
parent has not yet called wait() to read its exit status. The PCB remains until
the parent collects it.
○ Orphan: A process whose parent has terminated. The OS "re-parents" it to
the init process (PID 1), which will eventually call wait() to clean it up.
Process Scheduling
● Multiprogramming: The ability of an OS to run multiple processes concurrently on
a single CPU by switching between them.
● Context Switch: The process of saving the state of the currently running process
and loading the state of the next one to run. This is a core OS mechanism enabled
by the PCB.
● Preemption: The act of interrupting a running task to run another. This is typically
driven by a timer interrupt.
A scheduler is the component of the operating system that decides which process runs next
on the CPU.
—------------------------------------------------------------------------------------------------------------------------
Quiz 02
2)
● The bootloader is in ROM: No - The BIOS/UEFI firmware is in ROM. The
bootloader (e.g., GRUB) is a small program stored on the disk's first sector, which is
loaded into RAM by the BIOS.
● The stack is a static segment: No - The stack is a dynamic segment. Its size
changes at runtime as functions are called and return.
● A thread is a program in memory: No - A process is a program in memory. A
thread is a single sequence of execution within a process.
● POSIX was invented for portability: Yes - POSIX is a standard that ensures
applications can be easily ported between different Unix-like operating systems.
● A timer helps implement fault isolation: No - The primary mechanism for
implementing fault isolation is Virtual Memory, not the timer.
● A process is used to execute a program: Yes - A process is defined as "an
instance of a program in execution."
● A mode register helps implement preemption: No - A mode register (user/kernel
mode) helps implement dual-mode execution for security. The timer helps implement
preemption.
● A user space application can directly call a syscall: Yes - A user application can
directly invoke the syscall instruction (or its equivalent) to enter the kernel. It typically
uses a wrapper function from a library like glibc, but the direct invocation is possible.
● On a TLB miss, a main memory access must follow: Yes - A TLB miss means the
translation isn't in the fast TLB cache. The MMU must then perform a "page walk" by
accessing the page tables in main memory to find the correct translation.
● On x86, a double-fault is an exception of the type "fault": No - A double fault is
an abort. It's a severe error that occurs when the processor fails to handle a first fault
(e.g., a page fault during a page fault handler). It is generally not recoverable.
3)
(a) Disadvantages of Static Linking:
● Code Bloat (Storage): Every executable contains a full copy of the libraries it uses,
leading to much larger file sizes on disk.
● Code Bloat (Memory): If multiple statically-linked programs run, the same library
code is duplicated in RAM for each process, wasting physical memory.
● Difficult Updates: To update a library (e.g., for a security patch), every single
application that uses it must be recompiled and redistributed.
(b) Advantage and Disadvantage of Dynamic Linking:
● Advantage: Memory Sharing. Multiple running processes can share the same single
copy of a library in physical memory, saving RAM.
● Disadvantage: Dependency Hell. An executable won't run if the required shared
libraries (e.g., [Link].6) are missing, are the wrong version, or are incompatible.
(c) TLB Performance:
● Answer: Static Linking.
● Explanation: With static linking, only the library routines actually used by the
program are copied into its .text segment. These routines are packed together,
leading to good spatial locality. The program will likely need fewer code pages,
resulting in a higher TLB hit rate. Dynamic linking maps the entire library (including
unused functions) into the process's address space. This spreads the used routines
across more memory pages, hurting spatial locality and increasing TLB pressure,
likely leading to more misses.
Summary Relationship
1. Spatial Locality is a program's access pattern.
2. Good spatial locality leads to a high TLB Hit Rate because the program works
within a small set of memory pages.
3. Poor spatial locality (accessing many scattered pages) creates high TLB Pressure,
which forces a low TLB Hit Rate and kills performance.
5)
(a) Why is k22inc problematic?
● The instruction does two things: 1) reg += 4, and 2) mem[reg] += const.
● A page fault could occur when accessing mem[reg] in the second step. Page
faults are a type of fault, and faults require the OS to fix the problem (e.g.,
load the page from disk) and then restart the faulting instruction from the
beginning.
● Here's the critical problem: When the fault occurs, the CPU state that the OS
saves already has the register %reg updated (+4). When the instruction is
restarted, it will add 4 to the register again, and then try to access mem[reg].
Now it's trying to access mem[reg+8] instead of the intended mem[reg+4].
This makes the instruction's behavior dependent on whether a page fault
occurs, which is a severe bug.
(b) How can k22help fix this?
● You can use k22help <%reg> <const> before calling k22inc.
● k22help <%reg> <const> will "touch" the memory at mem[reg+const]. If that
memory is not mapped, it will trigger a page fault at a safe time. The OS
will handle it, and the page will be ready.
● Now, when you execute k22inc, the register update and the memory access
happen without an intervening page fault, ensuring correct behavior.
(c) Good enough explanation in the quiz answers
—------------------------------------------------------------------------------------------------------------------------
Lecture 06
Asynchronous IPC
● The sender "fires and forgets." The recipient is not necessarily waiting for the
message.
● Signal: A short, pre-defined notification sent to a process or thread. The recipient is
interrupted to handle it (e.g., SIGINT for interrupt, SIGSEGV for segmentation fault).
● Pending Signal: A signal that has been generated but not yet delivered to the target
process.
● Blocked Signal: A signal whose delivery is temporarily postponed by the target
process.
● Alternate Signal Stack: A separate stack used for executing signal handlers, crucial
for handling stack overflow errors.
Synchronous IPC
● The sender and recipient coordinate. The sender waits for the receiver to be
ready, and the receiver waits for the sender to send.
● Pipe: A unidirectional, kernel-managed communication channel. Reads block until
data is available.
● Shared Memory: A region of memory mapped into the address spaces of multiple
processes, enabling the fastest possible IPC by eliminating data copying.
Signal-Specific Details
● Unstoppable Signals: SIGKILL (9) and SIGSTOP (19) are special. They cannot be
caught, blocked, or ignored by a user process. The kernel guarantees it can always
terminate or stop a process using these signals. This is a critical failsafe.
● Who Handles the Signal? In a multithreaded process, a signal sent to the process
(e.g., from kill) is delivered to one arbitrary thread that is not blocking that signal. A
signal caused by a specific thread's action (e.g., SIGSEGV from an invalid memory
access) is delivered to that specific thread.
Key difference between threads and processes: Threads share the same memory
address space and resources, making creation and communication between them very
cheap. Processes are fully isolated.
Page Table: A data structure used by the operating system in virtual memory systems to
store the mapping between virtual addresses and physical addresses, enabling memory
isolation and efficient memory management for processes.
Page Table Base Register: A CPU register that holds the physical address of the base
(starting location) of the page table for the current process.
Stack Pointer: A CPU register that points to the top of the current stack in memory, used for
managing function calls, local variables, and temporary data storage.
Program Counter: A CPU register that contains the memory address of the next instruction
to be executed by the processor.
General Purpose Register: A CPU register that can be used for various computational
tasks like storing data, addresses, or intermediate results during program execution.
Critical Trade-offs
● Fork vs. Thread Creation: fork() is heavy (duplicates entire address space). Thread
creation is light (shares existing address space).
● Pipes vs. Shared Memory: Pipes are simple and safe (kernel-managed) but slow
(data copying). Shared memory is fast (zero-copy) but complex (requires manual
synchronization).
● Isolation vs. Sharing: Processes provide strong fault isolation. Threads within a
process share everything, so a bug in one thread can corrupt data used by another.
Threads belonging to a process are by default not protected from each other.
Concurrency: The illusion that multiple tasks are making progress at the same time. This is
achieved by the OS rapidly switching (time-slicing) a single CPU core between
threads/processes.
Parallelism: Actually executing multiple tasks simultaneously. This requires multiple CPU
cores, where one thread runs on core 1 and another runs on core 2 at the exact same time.
The Relationship: You can have concurrency without parallelism (a single-core system).
You can have parallelism without concurrency (one compute-heavy thread per core).
Modern systems have both.
—------------------------------------------------------------------------------------------------------------------------
Lecture 07
—------------------------------------------------------------------------------------------------------------------------
Lecture 08
—------------------------------------------------------------------------------------------------------------------------
Lecture 09
—------------------------------------------------------------------------------------------------------------------------
Lecture 10
Trivial Uniprocessor Lock: On a system with a single CPU and no preemption, no lock is
needed! The thread cannot be interrupted, so mutual exclusion exists. The lock functions are
empty.
Uniprocessor with Preemption: On a single CPU that can preempt, the solution is to
disable preemption inside the critical section. This prevents a context switch at an
inopportune moment. This is a simple but effective lock for kernels.
Multiprocessor (SMP) Spinlock: For multiple CPUs, disabling preemption on one core
doesn't help against threads on other cores. We need a shared variable and an atomic
instruction.
—------------------------------------------------------------------------------------------------------------------------
Lecture 11
What is a Deadlock?: A deadlock is a state where a set of threads are all blocked, each
waiting for a resource held by another, creating a circular dependency.
How to prevent deadlocks (before they happen)? Remove one of the four AND
preconditions
● No Mutual Exclusion: No exclusive access on a shared resource
● Enable preemption: No thread can hold the shared resource for more than a given
time frame while others are trying to obtain it
● Avoid hold and wait: No thread can hold the shared resource while requesting
another, and must, instead, try to obtain all the necessary resources at once, at the
beginning
● Avoid circular waiting: Impose a hierarchical ordering on shared resource
acquisition
—------------------------------------------------------------------------------------------------------------------------
Quiz 01
1.
Fault isolation: No
2.
Preemption: No
3.
PoLP: No
4.
This program prints the memory address of local variable x. If the MMU is enabled,
the address will be a virtual address (typically in the user-space range like 0x7fff...). If
the MMU is disabled, the address will be a direct physical address (often a much
lower value).
Remember: MMU -> Hardware that translates virtual addresses to physical addresses.
—------------------------------------------------------------------------------------------------------------------------
Quiz 04
1)
Case 1: The new thread runs after p = &x in main thread.
Then p points to x (value 3), so printf("%d\n", *p) prints 3.
Case 3:
1. Main thread creates the child thread
2. Main thread sets p = &x.
3. Main thread immediately returns from main → process exits.
4. The child thread might not have had time to:
● Be scheduled to run at all, or
● Reach the printf statement before being killed.
Thus: No output appears.
—------------------------------------------------------------------------------------------------------------------------
Lecture 16
Workload Requirements
Different workloads require different scheduling strategies:
● Hard Real-Time: Tasks must finish by strict deadlines (e.g., airbags). Uses Earliest
Deadline First (EDF).
● Soft Real-Time: Tasks need priority for performance (e.g., video streaming).
● CPU-Bound: Intensive computation tasks. These use Round Robin (RR) with large
time slices to avoid frequent context switches.
● I/O-Bound: Tasks that spend most of their time waiting for I/O. The scheduler should
favor these to minimize I/O device idle time.
Earliest Deadline First (EDF): An algorithm for hard real-time systems that prioritizes tasks
with the closest deadlines.
—------------------------------------------------------------------------------------------------------------------------
● Fault Isolation
● Illusion of continuous memory
● Frugal Resource Use:
○ Demand paging: Postpone allocating physical memory until necessary
○ Copy-On-Write: One physical replica of common readed-only code
● Performance: Programs can have a footprint as large as disk storage but run at
speeds close to CPU caches through locality and prefetching.
Memory Fragmentation
Implementation Mechanisms
Paging Details
● Page Table Entry (PTE): The individual index entry in a page table. In x86, it
includes specific status bits:
○ Present (P): Must be 1 to map the page.
○ Dirty (D): Indicates software has written to this page.
○ Accessed (A): Indicates the page has been read or written recently.
● PFN (Page Frame Number): The physical address of the 4KB page frame in RAM.
● Minor Page Fault: Occurs when the page is in memory but not mapped in the
process's page table (e.g., Copy-On-Write or anonymous pages).
● Major Page Fault: Occurs when the page must be fetched from disk or swap space.
When a processor tries to access a virtual address that is not currently mapped to
physical RAM, it triggers a Page Fault. The kernel's exception handler must then
decide whether to:
Replacement policies
Not all pages are equal: Reclaiming decision is made based on the the type of the occupied
pages
● File-backed pages: Dropped from the kernel's page cache first since they can be
reloaded later from the corresponding file.
● Anonymous pages w/o file backing: Must be written to the swap space in order to
be available for reloading; dropped next
● File-system-related kernel caches: Dropped last (next chapter)
● Last resort: Out-Of-Memory (OOM) killer terminates the process
Temporal locality states that if a particular memory location is referenced, it is highly likely
that the same location will be referenced again soon after.
Spatial locality states that if a particular memory location is referenced, it is highly likely that
nearby memory locations will be referenced soon after.
● CLOCK Algorithm: An "approximate LRU" (Last Recently Used) algorithm that uses
the "accessed" bit in the PTE to decide which pages to evict.
● Thrashing: A state where the system spends all its time swapping pages in and out
of disk; I/O utilization is 100% while CPU utilization drops to 0%.
● Out-Of-Memory (OOM) Killer: The kernel's last resort that terminates a process to
reclaim memory when the system is exhausted.
● Kernel oops(): A non-recoverable error when a page fault occurs in kernel mode at
an address not in user space.
● mmap (void *addr, ...): Creates a new mapping in the virtual address space of the
calling process.
● munmap (void *addr, size_t length): Deletes the mappings for the specified
address range.
● mprotect (void *addr, size_t len, int prot): Updates the protections for page(s) in
range [addr, addr+len) to "prot".
● msync (void *addr, ...): Flushes to disk changes made to the in-kernel copy of a file
that was mapped into memory using mmap.
● mlock/unlock (void *addr, …): Locks/unlocks part or all of the calling process's
virtual address space into main mem. preventing that memory from being paged to
the swap area.
The Buddy Allocator: To handle requests for contiguous memory, the kernel uses a
"Buddy" system. It divides memory into power-of-2 blocks (e.g., 4KB, 8KB, 16KB). If a
requested size isn't available, it splits a larger block into two "buddies"
● mem_map: A global array where the kernel stores a struct page for every single
physical frame in the system.
● Physical Frame Tracking: The kernel tracks the status of physical frames (e.g., free,
reserved, or mapped to a process) using metadata in the struct page.
—------------------------------------------------------------------------------------------------------------------------
Lecture 19
What is a File?
A file is a named, persistent collection of bytes that survives reboots and power
failures.
Types include:
● Regular files: Text or binary data.
● Special files: Represent devices (e.g., /dev/tty, /dev/sda).
● Named pipes: For inter-process communication.
● Sockets: For network or local communication.
● Directories: Lists of files.
● Symbolic links: Shortcuts to other files/directories.
What is a Directory?
A special file that contains a list of <filename, file identifier> pairs.
Directories organize files in a hierarchical, tree-like structure.
How it works:
Random Access
● Reading/writing at arbitrary positions.
● Hard to optimize; defeats prefetching.
● Examples: database record updates.
—------------------------------------------------------------------------------------------------------------------------
Lecture 20
Storage Basics
Files = abstraction for persistent data -> Need physical storage (like RAM for memory)
Allocation strategies:
1. Contiguous Allocation
Allocate a contiguous set of blocks, of sufficient size to each file.
Advantages:
● Low storage overhead (just store start block + length)
● Fast sequential access (blocks are next to each other)
● Easy random access (math: block = start + offset)
Disadvantages:
● Hard to grow a file (need continuous space)
● External fragmentation (gaps between files)
2. Linked Allocation
Allocate a linked list of blocks, with each block holding a pointer to the next block
(essentially, per-file, an on-disk linked list).
Advantages:
● No external fragmentation (use any free block)
● Easy to grow files (just add another link)
Disadvantages:
● Slow random access (must follow links)
● Storage overhead (each block stores a pointer)
● Unreliable: lose one block → lose the rest of the file
● Can’t cache or optimize access patterns well
3. Indexed Allocation
Use a special index block (inode) to store pointers to the data blocks
Advantages:
● No fragmentation
● Easy to grow files
● Fast random access (all pointers in one place → cache in RAM)
Disadvantages:
● Sequential access may be slower (blocks scattered)
● Index block has limited size → can’t store pointers for huge files
Disadvantages:
● More complex to implement
● Small overhead per file for indirect pointers
● More disk reads for very large files (multiple indirections)
Filesystem Layout
[Superblock] [Bitmap (inodes)] [Bitmap (data)] [Inodes] [Data Blocks]
Solutions:
● Ordered Writes
○ Ensures inodes never point to uninitialized data!
○ Can leak resources (fixable: run fsck periodically)
○ Cannot reorder writes and execute asynchronously <= Dealbreaker
● Journaling (Write-Ahead Logging)
○ Write all changes to a log first
○ Then apply to actual filesystem
○ If crash: replay log to recover
○ Used in ext3/4
Fault Tolerance
A system's ability to continue operating correctly despite
hardware failure (i.e., faults) is called fault tolerance.
● RAID 0 (Striping): Splits data into blocks and writes them across multiple disks
simultaneously. This increases speed (throughput) but offers no redundancy.
● RAID 1 (Mirroring): Writes an exact copy of the data to two or more disks. If one
fails, the system reads from the surviving copy.
● RAID 4 (Dedicated Parity): Stripes data across disks but dedicates one specific disk
solely for parity (error recovery data). If a data disk fails, the parity disk is used to
reconstruct it.
● RAID 5 (Distributed Parity): Stripes both data and parity information across all
drives. There is no single "parity disk"; the recovery data is spread out, preventing
the bottleneck of RAID 4.
● RAID 6 (Double Distributed Parity): Similar to RAID 5 but calculates two distinct
parity blocks for every data block and distributes them across all drives. This allows
for two simultaneous failures.
(> RAID 0)
Parity stored on
one dedicated
drive.
(≈ RAID 4)
Parity spread
across all
drives.
RAID Striping + ≥4 2 Disks High
6 Double Parity
—------------------------------------------------------------------------------------------------------------------------
Quizzes:
● [Link]
● [Link]
User threads are implemented by user Kernel threads are implemented by the
processes. OS.
The OS doesn’t recognize user level Kernel threads are recognized by the
threads. OS.
Process states:
Ready to Waiting is NOT possible.
Waiting to Running is NOT possible.
With Uniprogramming only one process can execute at a time; meanwhile all other
processes are waiting for the processor. With Multiprocessing more than one process can
be running simultaneously each on a different processor.
The return value for fork() is zero for the child process and a nonzero integer for
the parent process.
The child process is a duplicate of the parent process. (when using fork())
Bakery algorithm to solve CS problem: each process receives a number (may or may not
be unique) and the one with the lowest number is served next.
If one thread opens a file with read privileges then other threads in the same process can
also read from that file.
A thread is created faster than a process.
When the event for which a thread is blocked occurs, the thread moves to the ready queue.
False sharing occurs when multiple threads access different variables that happen to reside
on the same cache line, causing unnecessary cache invalidations.
Processes vs Threads: