Dive into Systems – Free Online Book (Matthews, Newhall and Webb, v1.2) BSCS

BSCS and BSSE students can read Dive into Systems free online. It is a computer systems textbook by Suzanne J. Matthews, Tia Newhall and Kevin C. Webb, written for the Computer Organization and Architecture course and for the systems courses that follow it, such as Operating Systems and parallel computing. There is no official PDF: the authors keep the book free to read in the browser, and a low-cost print edition is sold by No Starch Press.

The book takes a vertical slice through a computer. You start with C and the GDB and Valgrind debugging tools, then move down to binary data, logic gates and a working CPU, and on to x86-64, IA32 and ARMv8 assembly. From there it climbs back up through the memory hierarchy and code optimization to the operating system, multithreaded programming with Pthreads and OpenMP, and GPUs, MPI and cloud systems. It assumes you have finished a first programming course, and each chapter is written to be read on its own.

Book Overview

CourseComputer Organization & Architecture / Introduction to Computer Systems
Degree ProgramsBSCS, BSSE
LevelUniversity / Undergraduate (intermediate)
EditionOnline Version 1.2 (updated August 2023); print edition by No Starch Press (August 2022)
AuthorsSuzanne J. Matthews, Tia Newhall, Kevin C. Webb
CopyrightDive into Systems, LLC (2020)
LanguageEnglish
Total Chapters15 chapters plus an introduction and 2 appendices
Programming LanguageC (GCC on Linux), plus x86-64, IA32 and ARMv8-A assembly
ExercisesEnd-of-chapter exercise pages (interactive exercises in progress)
LicenseCC BY-NC-ND 4.0 — Model: Link-only
FormatFree online book (multi-page and single-page HTML); no PDF; low-cost print edition

Chapter List

Chapter 0: Introduction

Difficulty: Easy · Key topics: computer system, CPU, RAM, secondary storage, operating system, multicore, GCC and Linux, abstraction

The introduction defines what the book means by a computer system: input and output ports, a CPU, RAM, secondary storage and an operating system sitting between the hardware and user programs. It compares a desktop, a laptop and a Raspberry Pi to show that they share the same parts in different packages, and notes that all of them now have multiple cores. It then sets three goals: how a computer runs a program, how to judge the cost of what a program does, and how to write parallel code. The last part explains the tools used (C, GCC, Linux) and the book’s Asides, Notes and Warnings.

Key Points:

  • A computer system is hardware (I/O, CPU, RAM, secondary storage) plus system software, mainly the operating system.
  • RAM loses its contents when power is removed; secondary storage keeps programs and data without power.
  • The book targets general-purpose, reprogrammable systems; calculators and microcontrollers are out of scope.
  • A Raspberry Pi uses a system-on-a-chip that puts the CPU and RAM on one package and draws about 5 W.
  • The examples use C, compiled with GCC and run on Linux.
  • Commands are shown after a $ prompt, which you must not type.

Memory Tip: Picture the system as layers: your program on top, the operating system in the middle, hardware at the bottom. Every later chapter zooms into one layer.

Common Mistake: Skipping this chapter as “just background”. Its reading paths tell you which chapters to start with if you already know C.

Important Questions:

  • What are the main components of a computer system? I/O ports, the CPU, RAM and secondary storage make up the hardware, and the operating system is the core system software that manages them.
  • Why does the book use Linux and C? C translates fairly directly to assembly and machine code, and Linux is free, open source and widely used in supercomputing.

Chapter 1: By the C, the Beautiful C

Difficulty: Easy · Key topics: gcc, variable types, ASCII char, printf and scanf, conditionals and loops, arrays, strings, structs

Chapter 1 teaches C by comparing it with Python, so it suits students who already know one scripting language. It starts with compiling and running a program with gcc, then covers declared variable types, the sizes of the numeric types, characters as ASCII numbers and the arithmetic operators. Next come formatted input and output with printf and scanf, conditionals and loops, functions, and C’s lower-level arrays, strings and structs. The chapter closes by saying that these lower-level abstractions give the programmer more control over memory, which Chapter 2 then builds on.

Key Points:

  • Every variable must be declared with a type before use; a program must have a main function returning int.
  • gcc -Wall -g -o hello hello.c turns on warnings, adds debug information and names the executable.
  • A char is a number: 'A' is 65, and 'A' + 1 is 'B'.
  • Dividing two int values truncates, so 11 / 2 is 5.
  • printf does not add a newline; scanf arguments are addresses written with &.
  • C does no bounds checking on arrays, and strings are char arrays ending in a null character.

Practice Tip: Print a char with both %d and %c in one printf. Seeing 65 next to A shows that a character is just a small integer.

Common Mistake: Forgetting the & in scanf("%d", &num). scanf needs the variable’s address, and passing the value instead usually crashes the program.

Important Questions:

  • How is C different from Python for blocks and statements? C uses braces instead of indentation, a semicolon after each statement, and every statement lives inside a function.
  • Why does 11 / 2 give 5 in C? Both operands are int, so the division truncates; making either operand a float or double gives a real result.

Chapter 2: A Deeper Dive Into C

Difficulty: Medium · Key topics: stack and heap, scope, pointers, malloc and free, string library, pointer arithmetic, libraries and linking

This chapter revisits arrays, strings and structs through pointers. It first maps a program’s memory into code, data, heap and stack, and shows how scope decides where a variable lives. It then introduces pointer variables, the & and * operators and passing arguments by pointer. Dynamic memory with malloc and free follows, then the string library and C file input and output. Section 2.9 covers constants, enum and typedef, command-line arguments, void *, pointer arithmetic, static and shared libraries, and compiling C to assembly.

Key Points:

  • Local variables and parameters live in a function’s stack frame; globals live in the data section; malloc memory lives on the heap.
  • Declare a pointer as int *ptr; &x gives an address and *ptr reads or writes the target.
  • All C arguments are passed by value; passing a pointer lets a function change the caller’s variable.
  • Always test malloc‘s return value for NULL, and set a pointer to NULL after free.
  • strcpy and strcat do not check the destination size; strcmp compares contents, while == compares addresses.
  • Pointer arithmetic moves by whole elements: an int * advances 4 bytes per increment.
  • Linker flags: -I for headers, -L for library folders, -l for the library name.

Practice Tip: Write a program that calls malloc(sizeof(int) * 20), fills the array, prints it and frees it. Then run it under Valgrind (Chapter 3) to confirm there are no leaks.

Common Mistake: Using == to compare two strings. It compares base addresses, so two identical texts can still be “not equal”. Use strcmp.

Important Questions:

  • Why must you set a pointer to NULL after free? A stale pointer dereferenced later could silently modify memory that a new malloc reused; with NULL it crashes immediately, which is easier to debug.
  • What is the difference between static and shared libraries? A static library (.a) is copied into the executable by the linker; a shared library (.so) is only referenced and is loaded at run time.

Chapter 3: C Debugging Tools

Difficulty: Medium · Key topics: GDB, breakpoints, step and next, backtrace, Valgrind Memcheck, memory leaks, debugging threads

Chapter 3 teaches two tools that find most beginner C bugs. The GDB sections walk through two sample programs: one that reports the wrong maximum because of an off-by-one loop and a missing return value, and one that crashes with a segmentation fault because main never allocated an array. Later sections cover advanced GDB features, debugging at the assembly level and debugging multithreaded Pthreads programs. The Valgrind section explains why memory access errors are hard to see and shows how to read a Memcheck report for invalid reads and writes, double frees and leaks.

Key Points:

  • Compile with -g so GDB can map machine code to source lines, and avoid -O2 while debugging.
  • GDB core commands: break, run, cont, next (skips over calls), step (enters calls), print, where, frame N.
  • next runs the line without entering function calls; step goes inside them.
  • Memcheck detects uninitialized reads, out-of-bounds heap access, double frees and memory leaks.
  • Run it as valgrind --leak-check=yes ./a.out after compiling with -g.
  • Fix access errors before leaks, unless leaks are exhausting the heap.

Practice Tip: Write a loop with i <= len over an array and run it under GDB, printing arr[i] at each step. You will see the garbage value just past the end.

Common Mistake: Debugging an optimized build. With -O flags, GDB cannot map instructions back to your lines reliably, so values seem to jump around.

Important Questions:

  • How do you find the cause of a segmentation fault in GDB? Run the program, use where to see the crashing line and call stack, print the pointers involved, and use frame N to inspect the caller.
  • What does Valgrind Memcheck catch that GDB does not? It reports invalid heap reads and writes, uninitialized values, double frees and leaked blocks, with the line where each block was allocated.

Chapter 4: Binary and Data Representation

Difficulty: Medium · Key topics: bits and bytes, base conversion, two’s complement, overflow, bitwise operators, byte order, floating point

Chapter 4 starts from one idea: all information in a computer is stored as 0s and 1s, and meaning comes from how a program interprets them. It converts between binary, decimal and hexadecimal, then compares signed-magnitude with two’s complement for negative numbers. Binary addition, subtraction, multiplication and division lead into overflow detection for unsigned and signed values. The chapter also covers bitwise operators and shifts, big-endian and little-endian byte order, and real numbers as fixed-point and IEEE 754 floating point, with real failures such as the Patriot missile and Ariane 5 as warnings.

Key Points:

  • N bits represent 2^N values; a byte is 8 bits and has 256 values.
  • Each hex digit equals exactly one 4-bit group (a nibble).
  • Two’s complement: negate by flipping all bits and adding one; the range is -2^(N-1) to 2^(N-1) – 1.
  • Unsigned overflow happens when the carry out does not match the carry in; signed overflow happens when two operands of the same sign give a result of the other sign.
  • Right shift is logical for unsigned types and arithmetic (sign-copying) for signed types.
  • x86 stores integers little-endian: the least significant byte is at the lowest address.
  • A 32-bit float has 1 sign bit, 8 exponent bits (bias 127) and 23 significand bits.

Memory Tip: For two’s complement negation, remember “flip and add one”: 13 is 00001101, flipped is 11110010, plus one gives 11110011, which is -13.

Common Mistake: Treating bitwise NOT as negation. ~x only flips the bits and does not add one, so ~26 is -27, not -26.

Important Questions:

  • How do you detect signed overflow in addition? If both operands have the same sign and the result has a different sign, the operation overflowed; operands of different signs can never overflow.
  • What is the difference between big-endian and little-endian? Big-endian puts the most significant byte at the lowest address, little-endian puts the least significant byte there; x86 and most ARM systems are little-endian.

Chapter 5: What von Neumann Knew: Computer Architecture

Difficulty: Hard · Key topics: von Neumann model, logic gates, ALU and storage circuits, fetch-decode-execute-writeback, clock, pipelining, hazards, multicore

Chapter 5 builds a processor from the bottom up. It begins with the history of computing and the five units of the von Neumann model, with instructions and data sharing one memory. Logic gates come next, then arithmetic, control and storage circuits, including the RS and gated D latches that hold a bit. These parts are assembled into a CPU whose instructions run through fetch, decode, execute and write-back stages under a clock. The chapter then explains pipelining, data and control hazards, and how superscalar, multithreaded and multicore designs push performance in modern CPUs.

Key Points:

  • The von Neumann model is stored-program: instructions and data live in the same memory.
  • The program counter (PC) holds the next instruction’s address; the instruction register (IR) holds the current one.
  • AND, OR and NOT form a complete set of gates; NAND alone is also complete.
  • A 32-bit register is 32 gated D latches sharing one write-enable signal.
  • The clock period must be long enough for the slowest stage, usually Execute.
  • Pipelining raises throughput (CPI approaches 1) but does not reduce per-instruction latency.
  • The power wall after the early 2000s pushed designers from faster clocks to multicore.

Memory Tip: Remember the four CPU stages as F-D-E-W: Fetch the instruction, Decode it, Execute in the ALU, Write the result back.

Common Mistake: Judging CPU speed by clock rate alone. The chapter says cycles per instruction (CPI) matters just as much, because a faster clock with a worse CPI can be slower.

Important Questions:

  • What are data and control hazards in a pipeline? A data hazard is two instructions needing the same data at overlapping stages (fixed by operand forwarding); a control hazard happens at a branch when the next instruction is unknown (handled by stalling, branch prediction or eager execution).
  • What is the difference between RISC and CISC? RISC uses a small set of simple instructions that run quickly; CISC has richer, variable-length instructions that do more work but may need several cycles.

Chapter 6: Under the C: Dive into Assembly

Difficulty: Medium · Key topics: assembly language, compiler translation, ISA, registers, stack frames, x86-64, IA32, ARMv8-A

Chapter 6 is the short gateway to the assembly chapters. It defines assembly as the most readable form of machine code and explains that a compiler translates C according to the language rules, the operating system and the machine’s instruction set. It gives four reasons to learn assembly: seeing details that high-level code hides, programming resource-constrained devices, security analysis, and performance-critical system code such as boot sequences and context switches. It then introduces the three ISAs covered next and the features they share: registers, arithmetic, control flow, data movement and stack operations.

Key Points:

  • An ISA defines the instructions, their binary encoding, the registers and how instructions change CPU and memory state.
  • The book covers x86-64 (Chapter 7), IA32 (Chapter 8) and ARMv8-A (Chapter 9).
  • A C compiler targets one specific ISA, such as x86 for Intel or ARM for a Raspberry Pi.
  • Every function call adds a stack frame holding its locals and parameters; the return removes it.
  • Boot code and thread or process context switching are often handwritten in assembly.

Memory Tip: Think of the compiler as a translator that follows fixed rules, not a clever thinker. Assembly just shows you the translation.

Common Mistake: Assuming handwritten assembly always beats compiled code. The chapter notes that most modern compilers produce code about as efficient as handwritten assembly.

Important Questions:

  • What does an ISA define? The instruction set and its binary encoding, the set of CPU registers, and the effect each instruction has on CPU and memory state.
  • Why learn assembly if compilers write it for us? It reveals hidden behaviour (like an uninitialized local returning a stale value), helps with security work, and is needed for embedded and low-level system code.

Chapter 7: 64-bit x86 Assembly

Difficulty: Hard · Key topics: registers, AT&T syntax, operand forms, cmp and jumps, stack frames, recursion, arrays, structs, buffer overflow

Chapter 7 reads real compiler output for x86-64 using AT&T syntax. It starts with the 16 general registers, operand forms and instruction size suffixes, and shows how to disassemble a program with objdump -d. It then covers common and extra arithmetic instructions, cmp and test with condition flags, and conditional jumps for if statements and loops. The function sections explain the stack frame, call and ret, and argument registers, with sumr as a recursion example. Arrays, matrices and structs follow, and the chapter ends with a stack buffer overflow exploit and its defenses.

Key Points:

  • The first six arguments go in %rdi, %rsi, %rdx, %rcx, %r8, %r9; the return value is in %rax.
  • AT&T syntax lists the source first and the destination second; constants start with $, registers with %.
  • A memory operand like (%rdx,%rcx,4) means base + index * scale, with scale 1, 2, 4 or 8.
  • cmp sets flags ZF, SF, OF and CF; je, jg, jl and ja then branch on them.
  • The stack grows toward lower addresses; push subtracts 8 from %rsp; leaveq and retq tear down a frame.
  • lea computes an address without reading memory; mov with parentheses reads memory.
  • Struct fields must be aligned to their size, so the compiler inserts padding bytes.

Practice Tip: Compile a small adder.c, run objdump -d, and match each C statement to its instructions. Then draw the stack for each push and call.

Common Mistake: Reading AT&T operands backwards. mov %rsp, %rbp copies %rsp into %rbp; the source is first.

Important Questions:

  • What do the function prologue and epilogue do? The prologue does push %rbp and mov %rsp,%rbp to set up the frame, and reserves locals by subtracting from %rsp; the epilogue’s leaveq restores both pointers and retq returns.
  • How does a buffer overflow let an attacker redirect a program? Unchecked input overruns a local buffer and overwrites the saved return address, so the function returns to an address the attacker chose.

Chapter 8: 32-bit x86 Assembly

Difficulty: Hard · Key topics: IA32, %ebp and %esp, arguments on the stack, call and ret, AT&T syntax, 32-bit builds

Chapter 8 repeats the Chapter 7 topics for the older 32-bit IA32 instruction set, so that each assembly chapter can be taught on its own. It explains how to check whether a machine is 32-bit with uname -m and how to build 32-bit executables on 64-bit Linux. The key difference is function calling: IA32 passes arguments on the stack instead of in registers. The caller writes them at the top of its frame before call, and the callee reads the first argument at %ebp+8. Conditionals, loops, arrays, structs and buffer overflows follow the same pattern as Chapter 7.

Key Points:

  • IA32 is the 32-bit predecessor of x86-64; most Intel and AMD systems since 2007 are 64-bit.
  • Inside a callee, the return address is at %ebp+4 and the first argument is at %ebp+8.
  • The nth argument sits at (%ebp+8) + 4*(n-1).
  • The return value goes in %eax; leave is mov %ebp,%esp followed by pop %ebp.
  • On 64-bit Ubuntu, 32-bit builds may need gcc-multilib and libc6-dev-i386.

Memory Tip: IA32 puts arguments on the stack (above the saved %ebp), while x86-64 puts the first six in registers. If you know one, the other is just where the arguments sit.

Common Mistake: Using 64-bit register names such as %rdi to read arguments in IA32 code. In 32-bit code the arguments are at offsets from %ebp.

Important Questions:

  • How does IA32 pass function arguments? The caller stores them on the stack before call; the callee finds them at positive offsets from %ebp, starting at %ebp+8.
  • Do you still need this chapter? Only if your course teaches 32-bit x86; if it uses x86-64 or ARM, Chapters 7 or 9 are enough.

Chapter 9: ARMv8 Assembly

Difficulty: Hard · Key topics: A64 ISA, x0-x30 and w registers, load/store model, Raspberry Pi, ARM vs Intel, buffer overflows

Chapter 9 covers the ARMv8-A application profile and its A64 instruction set, using a Raspberry Pi 3B+ with 64-bit Ubuntu for the examples. It explains how to check for aarch64, and how QEMU, cross-compilation or Amazon EC2 A1 instances can stand in if you have no ARM board. The register section introduces the 31 registers x0 to x30, their 32-bit w halves, sp, pc and the zero register. Instruction format puts the destination first, and all data must be loaded into registers before use. The same topics as Chapter 7 follow.

Key Points:

  • ARM is a RISC design, popular in mobile devices because it is energy efficient; Japan’s Fugaku supercomputer used ARM.
  • There are 31 general registers x0–x30; w0–w30 are their lower 32 bits.
  • The instruction format is opcode D, O1, O2, with the destination register first.
  • Constants start with #; memory operands use brackets such as [x0, #8].
  • Load/store model: data in memory must be loaded into a register, operated on, then stored back.
  • ARM binaries built on an Intel PC cannot run there directly; QEMU can emulate an ARM system.

Memory Tip: In ARM the destination comes first (add x0, x1, x2), the opposite of AT&T x86 where it comes last. Say it aloud as “ARM, answer first”.

Common Mistake: Trying to do arithmetic directly on a memory location. A64 can only compute on registers; the destination must always be a register.

Important Questions:

  • What is the load/store model? Instructions operate only on registers, so values in memory must first be loaded into registers and results stored back afterwards.
  • How do x and w registers differ? xN is the full 64-bit register and wN its lower 32 bits; the compiler typically uses w for int and x for pointers and long.

Chapter 10: Key Assembly Takeaways

Difficulty: Medium · Key topics: ISA comparison, registers, instructions, stack frames, buffer overflow defenses, safer C functions

Chapter 10 pulls the three assembly chapters together so a reader can see what is common and what is not. It restates that the ISA defines the assembly language on a machine, that registers are either general purpose or reserved for stack and base pointers, and that every instruction is an opcode plus operands. It reminds the reader that stack frames are created per call and that old data left in them can cause hard-to-debug behaviour and exploits. It ends with the security lesson that no ISA is invulnerable, with a table of safer C functions that take length limits.

Key Points:

  • Run uname -m to find a Linux machine’s architecture.
  • Special registers such as the stack pointer are reserved for the compiler; the instruction pointer is read-only.
  • Core instruction types are data movement, arithmetic, conditionals, branches and memory access.
  • The stack grows toward lower addresses on modern systems, and each call gets a frame.
  • Use fgets instead of gets, strncpy instead of strcpy, and snprintf instead of sprintf.

Memory Tip: Treat the chapter as a checklist. For any ISA, ask: where are the registers, what is the instruction format, how are arguments passed, how does the stack grow?

Common Mistake: Believing a newer ISA makes buffer overflows impossible. The chapter says ARMv8-A learned from older Intel designs, but programmers are still the first line of defence.

Important Questions:

  • Which C functions are unsafe and what replaces them? gets, scanf("%s"), strcpy, strcat and sprintf are replaced by fgets, scanf("%12s"), strncpy, strncat and snprintf, each with a length limit.
  • What do all the assembly languages in this book share? Registers, an opcode-plus-operands instruction format, data-movement and arithmetic instructions, conditional branches and stack frames for function calls.

Chapter 11: Storage and the Memory Hierarchy

Difficulty: Medium · Key topics: memory hierarchy, SRAM and DRAM, SSD and HDD, locality, cache hits and misses, associativity, write policies, Cachegrind

Chapter 11 argues that memory behaviour often matters as much as the algorithm. It opens with two matrix-averaging functions that differ only in index order, mat[i][j] against mat[j][i], yet one runs about five times faster. It then lays out the hierarchy from registers and cache to main memory, flash, disk and remote storage, and describes each device type. Temporal and spatial locality explain the speed gap. The caching section works through hits and misses, direct-mapped and set-associative caches, LRU replacement and write policies, and Cachegrind measures the example.

Key Points:

  • Faster storage costs more per byte and holds less: registers, cache, RAM, flash, disk, remote storage.
  • SRAM (registers, caches) is fast but costly; DRAM (main memory) stores charge in capacitors and must be refreshed.
  • Temporal locality means reusing recent data; spatial locality means using data near recent addresses.
  • An address splits into tag, index and block offset; a 32-byte block needs 5 offset bits.
  • Miss types are compulsory (first access), capacity and conflict; associativity reduces conflict misses.
  • Write-back caches use a dirty bit and update memory only on eviction; write-through updates memory on every write.
  • Cachegrind: valgrind --tool=cachegrind --cache-sim=yes ./prog, then cg_annotate.

Memory Tip: Think of Fiona’s desk, shelf and library: keep the books you use often on the desk (temporal locality) and fetch neighbouring volumes when you walk to the library (spatial locality).

Common Mistake: Looping over a 2D array column by column. C stores rows contiguously, so the inner loop should run along a row, as the five-times speed difference in the chapter shows.

Important Questions:

  • Why is mat[i][j] faster than mat[j][i] in C? Matrices are stored in row-major order, so [i][j] reads consecutive addresses and reuses each cache block, while [j][i] jumps between rows and misses far more often.
  • What are compulsory, capacity and conflict misses? Compulsory misses are first-time accesses, capacity misses come from a working set larger than the cache, and conflict misses come from placement rules even when the cache is not full.

Chapter 12: Code Optimization

Difficulty: Medium · Key topics: gcc -O levels, loop-invariant code motion, inlining, loop unrolling, loop interchange, Callgrind, Massif

Chapter 12 shows how to make a program faster in an order that works. It begins with what GCC already does at -O1, -O2 and -O3, then uses a prime-finding program that takes 3.86 seconds unoptimized. Profiling with Callgrind shows that most instructions are spent in isPrime, where a repeated sqrt call can be moved out of the loop. The chapter then covers inlining and loop unrolling, and memory-level fixes such as loop interchange, loop fission and fusion, and Massif for heap profiling. Its message is to measure first and not optimize prematurely.

Key Points:

  • Compilers already do constant folding, constant propagation, dead code elimination and expression simplification.
  • A bad algorithm or data structure is the top cause of slow code; the compiler cannot turn bubble sort into quicksort.
  • -g is incompatible with -O flags for debugging, and -O3 can be slower than -O2.
  • Loop-invariant code motion cut the example’s unoptimized time from 3.86 s to 1.83 s.
  • -funroll-loops performs about as well as hand unrolling, so use the flag.
  • Loop interchange made a 10,000 x 10,000 matrix-vector program nearly eight times faster unoptimized.
  • Profile first with valgrind --tool=callgrind and callgrind_annotate.

Practice Tip: Time one of your own programs with time -p at no flag, -O1, -O2 and -O3. The table you build shows how much the compiler really gives you.

Common Mistake: Optimizing by hand before profiling. The chapter warns against premature optimization: find the hot spot with a profiler first, and keep code readable.

Important Questions:

  • What is loop-invariant code motion? Moving a computation that does not change between iterations, like sqrt(x) in a loop condition, outside the loop so it runs once.
  • Why can’t the compiler always optimize across pointer parameters? If two pointers may refer to the same variable (aliasing), the optimization could change the result, so the compiler stays safe and skips it.

Chapter 13: The Operating System

Difficulty: Medium · Key topics: booting, user and kernel mode, system calls, processes, fork and exec, virtual memory, paging, TLB, signals, IPC

Chapter 13 explains what the operating system does for every running program. It covers booting from firmware to the OS, loading a program, user and kernel mode, interrupts and traps, and system calls. The process section defines the process control block, the Ready, Running, Blocked and Exited states, context switching and the fork, exec, exit and wait calls. Virtual memory follows with pages, frames, page tables, page faults, swapping and the TLB. The chapter ends with three forms of interprocess communication: signals, message passing and shared memory.

Key Points:

  • Mechanisms decide how something is done; policies decide what, when and for whom.
  • fork returns 0 to the child, the child’s PID to the parent and -1 on failure.
  • An exited child stays a zombie until its parent reaps it with wait or waitpid.
  • Many systems use 4 KB pages; a virtual address is a page number plus an offset, and the page table maps page to frame.
  • A page fault happens when the valid bit is 0; the OS loads the page from disk and restarts the instruction.
  • The TLB caches page-to-frame mappings so most accesses skip the page table.
  • SIGKILL and SIGSTOP cannot be overridden; handlers have the form void handler(int signum).

Memory Tip: Remember the process states as R-R-B-E: Ready, Running, Blocked, Exited. A blocked process goes back to Ready, never straight to Running.

Common Mistake: Thinking fork starts a new program. It only copies the current process; exec is the call that replaces the process with a different program.

Important Questions:

  • How does paging translate a virtual address? The MMU splits the address into page number and offset, indexes the process’s page table, checks the valid bit, and combines the frame number with the same offset.
  • What is a zombie process? A child that has exited but whose termination information is kept by the OS until the parent calls wait or waitpid.

Chapter 14: Leveraging Shared Memory in the Multicore Era

Difficulty: Hard · Key topics: threads, Pthreads, mutex, race condition, deadlock, speedup, Amdahl’s law, false sharing, thread safety, OpenMP

Chapter 14 teaches shared-memory programming for multicore CPUs. It explains the memory wall and power wall, how to count cores with lscpu, and why a thread shares the process’s heap and data but has its own stack. POSIX threads come first: pthread_create and pthread_join. Then synchronization covers data races, critical sections, mutexes, semaphores and deadlock, using a countElems program that is timed three ways. Later sections cover speedup, efficiency and Amdahl’s law, cache coherence and false sharing, thread-safe functions like strtok_r, and OpenMP.

Key Points:

  • Threads share the process’s code, data and heap; each thread has its own call stack.
  • pthread_create(&tid, NULL, func, arg) starts a thread running void *func(void *); compile with -pthread.
  • A data race occurs when threads read-modify-write the same location without mutual exclusion; counts[v]++ is not atomic.
  • Speedup is serial time divided by parallel time; efficiency is speedup divided by cores.
  • Amdahl’s law: with serial fraction S, speedup is limited to 1/S, so 90% parallel code never exceeds 10x.
  • False sharing makes threads slow down because they write different elements on the same cache line.
  • OpenMP: compile with -fopenmp and use #pragma omp parallel for and critical.

Practice Tip: Run a counting program with five threads and no mutex, several times. The answer changes on each run, which makes the race condition visible before you fix it.

Common Mistake: Locking inside the loop for every element. In the chapter this made two threads take 10.97 s against 1.92 s for one; give each thread a private array and merge once under the lock.

Important Questions:

  • What is Amdahl’s law? Runtime on c cores is S x T1 + (P x T1) / c, so the serial fraction S caps speedup at 1/S no matter how many cores you add.
  • How can two threads deadlock? Each holds one lock while waiting for the other, like two bank transfers locking accounts in opposite order; holding only one lock at a time removes the cycle.

Chapter 15: Looking Ahead: Other Parallel Systems

Difficulty: Medium · Key topics: Flynn’s taxonomy, GPUs and CUDA, thread blocks, MPI, message passing, cloud computing, MapReduce, exascale

The last main chapter widens the view beyond one multicore CPU. It classifies machines with Flynn’s taxonomy: SISD, MISD, SIMD and MIMD. The GPU section introduces CUDA, with kernels marked __global__, threads grouped into blocks and grids, and explicit copies between host and device memory. Distributed-memory systems come next, with the MPI model of ranks, MPI_Send and MPI_Recv, and collectives such as broadcast, scatter and gather. The final section contrasts HPC with cloud computing, explains MapReduce, Hadoop and Spark, and lists the exascale challenges of failures and power.

Key Points:

  • SIMD applies one instruction to many data items (GPUs); MIMD runs multiple instruction streams on multiple data and covers most multicore CPUs.
  • CUDA threads find their position with blockIdx.x * blockDim.x + threadIdx.x.
  • The programmer must cudaMalloc device memory and cudaMemcpy data between host and device.
  • Each MPI process has a rank from 0 to N-1; MPI_Bcast, MPI_Scatter and MPI_Gather are collective operations.
  • MapReduce needs only a map and a reduce function; Spark keeps intermediate data in memory and can be up to 100 times faster than Hadoop on some applications.
  • With a 2% failure chance per node, a 1,000-node data center almost certainly has a failed node at any time.

Memory Tip: Match each model to its memory: Pthreads and OpenMP share memory on one machine, MPI sends messages between separate machines, and CUDA copies data to a separate GPU memory.

Common Mistake: Assuming the order in which distributed processes run. The chapter says never to assume it; enforce any needed order explicitly.

Important Questions:

  • What are the four categories of Flynn’s taxonomy? SISD, MISD, SIMD and MIMD, based on whether the instruction stream and data stream are single or multiple.
  • How does a CUDA program run a computation? It allocates device memory, copies the data to the GPU, launches a kernel with <<<grid, block>>>, copies results back and frees the memory.

Appendix 1 (Chapter 16): Chapter 1 for Java Programmers

Appendix · Difficulty: Easy · Key topics: C compared with Java, no objects, no garbage collection, no exceptions, printf and scanf, arrays, structs

This appendix is Chapter 1 rewritten for students who know Java instead of Python, added in the 2023 online version. It keeps the same sections, from getting started to structs, but compares each C feature with its Java counterpart. The introduction lists what C lacks compared with Java: object-oriented programming, high-level types like String and ArrayList, large class libraries, garbage collection and exceptions. It repeats the trade-off that C is less abstracted from machine language, which gives more control over execution and memory and makes C the standard language for systems programming.

Key Points:

  • C is procedural: a program is a sequence of statements organized into functions, starting at main.
  • There are no classes, no String or ArrayList, no garbage collection and no exceptions.
  • A data structure like a dictionary must be implemented by the programmer.
  • The sections mirror Chapter 1: I/O, conditionals and loops, functions, arrays and strings, structs.

Practice Tip: Rewrite one of your Java programs that uses an array in C. Seeing how much bookkeeping (lengths, null terminators) you now do yourself is the lesson of this appendix.

Common Mistake: Expecting memory to clean itself up. With no garbage collector, every malloc needs a matching free (Chapter 2).

Important Questions:

  • What does C lack that Java has? Objects and classes, built-in high-level types such as String and ArrayList, a large class library, garbage collection and exceptions.
  • Who should read this instead of Chapter 1? Students whose first language was Java; the sections match Chapter 1 one-to-one, so you can continue with Chapter 2 afterwards.

Appendix 2 (Chapter 17): Using Unix

Appendix · Difficulty: Easy · Key topics: command line, man pages, ssh and scp, vim, make and Makefiles, grep and find, chmod, tar, pipes, bash scripts

Appendix 2 is a practical Linux command-line guide for the rest of the book. It begins with the file system and basic commands such as ls, cp and mv, and goes on to man pages, remote access with ssh and scp, and the vim editor. Longer sections explain Makefiles, searching with grep and find, file permissions with chmod, archiving with tar, and process control. It ends with timing, command history, I/O redirection and pipes, dot files like .bashrc, shell programming and system information with top and /proc.

Key Points:

  • A Makefile rule has a target, dependencies and a command line that must begin with a tab.
  • make rebuilds a target only when a dependency is newer; make -j12 runs up to 12 recipes at once.
  • chmod 644 notes gives the owner read and write, and the group and others read only.
  • Permission bits are r, w, x for user, group and other; a directory needs x to enter.
  • Redirection operators include <, >> and 2>; a pipe | chains commands.
  • Job control uses &, fg, bg, CTRL-Z and kill.

Practice Tip: Write a two-line Makefile for one of your C programs with an all and a clean target, and run make twice. The second run does nothing because nothing changed.

Common Mistake: Indenting a Makefile recipe with spaces. The line before the command must begin with a tab, or make fails with a confusing error.

Important Questions:

  • What does chmod 750 projects do? It gives the owner read, write and execute (7), the group read and execute (5) and others no access (0), giving drwxr-x--- for a directory.
  • When does make rebuild a target? When the target file does not exist or any of its dependencies has a newer modification date.

How to Study This Book

The authors wrote the chapters to be as independent as possible, so you do not have to read in strict order. Chapters 1 and 4 stand alone. Chapters 2, 3, 5 and 6 to 10 depend on Chapter 1, and Chapters 5 and 13 also lean on Chapter 4. If you are new to C, start with Chapter 1 (or Appendix 1 if you know Java) and keep a compiler open while you read, because the book wants you to type the examples.

For a Computer Organization and Architecture course, follow the order used at West Point: Chapters 1, 4, 2 (with some of 3), 6, 7, 10, 11, 12, 13, 14 and 15. For an Introduction to Computer Systems course, the Swarthmore order is 4, 1 (some of 3), 5, 6, 7, 10, 2 (more of 3), 11, 13 and 14. Chapter 5 is the hardest early step, so read Chapter 4 on binary and signed numbers carefully first.

Read one assembly chapter, not all three. Chapters 7, 8 and 9 cover the same topics for x86-64, 32-bit IA32 and ARMv8, and the repetition is deliberate so each can be used alone. Choose the one that matches your lab machine (uname -m tells you), then read Chapter 10 for the takeaways. Chapter 8 is the one to skip unless your course specifically uses 32-bit x86, since most Intel and AMD processors made since 2007 are 64-bit.

For an Operating Systems course, the book gives you Chapters 11, 13 and 14 as background. Treat Chapter 13 as an introduction to processes, virtual memory and signals, not a full OS syllabus: it does not cover scheduling policies, file systems or security in depth. For a Parallel Computing course use Chapters 11, 13, 14 and 15; for Networking, the preface points to Chapters 4, 13 and 14.

Chapters 2 and 3 work as a C and debugging reference. Come back to the GDB commands and the Valgrind workflow whenever a lab crashes. Appendix 2 (Using Unix) is worth reading in the first week if the command line is new to you, especially the Makefile and permissions sections.

Most chapters end with an Exercises section that links to a separate exercises page, and the authors say interactive exercises are still being built. Keep expectations realistic: the book is a gentle introduction, not a reference for every x86 instruction and not a full compiler or CPU-design course. It also does not teach C++ or Java.


Used In These Programs

This book is used for the Computer Organization and Architecture course in: BS Computer Science and BS Software Engineering. Browse all Computer Architecture books, Operating Systems books or all Computer Science books.

Who Should Read This

This book is written for BSCS and BSSE students who know the basics of programming (CS1 level, with Python or Java) and are taking Computer Organization and Architecture, Introduction to Computer Systems or a first systems-programming course, usually in the second or third year. It is also a good fit if you want to understand how C, assembly, the cache and the operating system fit together before an Operating Systems paper. It is not a book for complete beginners to programming, and it is not an entry-test guide. For deeper operating-system theory such as scheduling and file systems, pair it with Operating Systems: Three Easy Pieces, which is the authors’ own recommendation for further reading.


Applicable Universities

This book is useful for students at Pakistani universities offering BS Computer Science and BS Software Engineering, including Punjab University, Virtual University, COMSATS, FAST, UET, NUST, and other HEC-recognized institutions.

Official Copy of Dive into Systems

This book is free from its official site. There is no official PDF, so the legitimate free way to read it is the complete online edition. Click the button below to open the book on diveintosystems.org, where you can read it chapter by chapter or as a single page. A low-cost print edition is also sold by No Starch Press.

→ Visit Official Page

FAQs

Is Dive into Systems free?

Yes. The authors publish the full online edition free at diveintosystems.org under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International licence (CC BY-NC-ND 4.0), and say it will always remain free online. That licence allows reading and sharing the book with credit for non-commercial use, but not changing it.

Is there a PDF of Dive into Systems?

The official site does not offer a PDF download. The legitimate free versions are the multi-page online book and the single-page online version, and a printed edition can be bought from No Starch Press. This page links only to the official site and does not host any file.

Which version is this?

The online book is Version 1.2, updated on August 25, 2023, which added two appendices and links to early interactive exercises. The copyright is 2020 Dive into Systems, LLC, and No Starch Press published the print edition in August 2022 (ISBN-13 9781718501362).

How many chapters does it have, and are there exercises?

There is an introduction, 15 chapters and 2 appendices (Chapter 1 for Java programmers, and Using Unix). Most chapters end with an Exercises section that links to a separate exercises page, and the authors say interactive exercises are still in progress.

Do I need to know C before reading it?

No, but you need a first programming course. Chapter 1 teaches C by comparing it with Python, and Appendix 1 does the same for Java. Readers who already know C can start with Chapter 4 on binary representation or Chapter 2 on advanced C.

Do I have to read all three assembly chapters?

No. Chapter 7 covers 64-bit x86, Chapter 8 covers 32-bit IA32 and Chapter 9 covers ARMv8, and each repeats the same topics so it can be used on its own. Pick the one that matches your lab machine, and read Chapter 10 for the shared takeaways.

Related Books

Dive into Systems is a gentle, practical way to see what happens between your C code and the hardware, with GDB, Valgrind, assembly, caches, processes and threads all in one free book. It is not a full operating systems or compiler course, but it gives you the foundation those courses assume. Browse more Computer Science books for the rest of your semester.

Dive into Systems by Suzanne J. Matthews, Tia Newhall and Kevin C. Webb. Copyright 2020 Dive into Systems, LLC. Licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License (CC BY-NC-ND 4.0). Official source: https://diveintosystems.org/. Print edition: No Starch Press. This page links to the official site and does not host or copy the book.

Leave a Comment