Think OS PDF Download – Allen B. Downey (Version 0.7.4) BSCS

BSCS, BSIT and BSSE students can download the complete Operating Systems textbook “Think OS: A Brief Introduction to Operating Systems” by Allen B. Downey, published free by Green Tea Press. The book is free from its official source and covers the systems-programming half of the Operating Systems course taught at Pakistani universities.

Think OS is deliberately different from a standard OS textbook. Downey wrote it for a Software Systems course, so instead of asking how you would build an operating system, it asks what a programmer needs to know about the one already running. Eleven short chapters take you from how a C program is compiled, through processes, virtual memory, file systems, bit-level representation, dynamic memory and caching, to multitasking, threads, condition variables and semaphores — each explained with small C programs you can compile and run. It assumes you can program but assumes no computer architecture background.

Book Overview

CourseOperating Systems / Systems Programming
Degree ProgramsBSCS, BSIT, BSSE
LevelUniversity / Undergraduate
VersionVersion 0.7.4 (early draft — figures not yet included)
AuthorAllen B. Downey
PublisherGreen Tea Press
LanguageEnglish
Total Chapters11 chapters, plus a preface
Programming LanguageC (POSIX / Linux)
LicenseCC BY-NC-SA 4.0 — Model: Link-only
FormatFree PDF and free online HTML edition

Chapter List

Chapter 1: Compilation

Difficulty: Easy · Key topics: compiled vs interpreted, static vs dynamic types, the compilation steps, object code, assembly, gcc flags

The book opens with what actually happens between your source file and a running program. Downey first shows that “compiled” and “interpreted” describe implementations, not languages — C interpreters and Python compilers both exist, and Java sits in between with bytecode and a virtual machine. He then contrasts static and dynamic types, and explains why compile-time type declarations catch errors in code that has never run and let the compiler drop variable names from the final program. The rest of the chapter walks the six steps of compilation and shows each one with a real gcc flag, ending with a guide to reading error messages by working out which stage produced them.

Key Points:

  • Being compiled or interpreted is a property of the implementation, not of the language itself.
  • Static means it happens at compile time; dynamic means it happens at run time.
  • The compilation steps are preprocessing, parsing, static checking, code generation, linking and optimization.
  • In a compiled language, variable names exist at compile time only — at run time a variable is just an address.
  • gcc flags: -c stops at object code, -S produces assembly, -E runs only the preprocessor, -o names the output.
  • The UNIX command nm lists the names an object file defines and uses.
  • Turn optimization off while developing; turn it on only after the tests pass.

Practice Tip: Compile the same hello.c four times with -E, -S, -c and no flag, and look at each output. The compilation stages stop being a memorised list once you have seen the four different files they produce.

Common Mistake: Reading every build failure as a “compiler error”. The stage named in the message matters — a missing header is a preprocessor error, a missing semicolon is a compiler error, and an undefined reference is a linker error, and each has a different fix.

Important Questions:

  • What is the difference between a compiled and an interpreted language? A compiled program is translated into machine language and then run by hardware; an interpreted program is read and executed by a software interpreter. Many languages can be implemented either way.
  • What are the steps of the compilation process? Preprocessing, parsing, static checking, code generation, linking and optimization.

Chapter 2: Processes

Difficulty: Easy · Key topics: abstraction, virtualization, isolation, what a process contains, multitasking, device abstraction, the ps command

This chapter defines the two words the whole book rests on. An abstraction is a simplified representation of something complicated; virtualization is a kind of abstraction that creates a useful illusion — the way an inter-library loan system makes a small library appear to hold every book. The operating system’s central job is isolation, and the object that provides it is the process. Downey lists exactly what a process contains: the program text, its static and dynamic data, the state of pending I/O, and the hardware state including the registers and the program counter. He then names the three capabilities that keep processes apart — multitasking, virtual memory and device abstraction — and closes with a real ps listing, explaining init, kthreadd, ksoftirqd and what the “d” in daemon means.

Key Points:

  • Virtualization is abstraction that creates a desirable illusion; the physical reality can be far smaller than the virtual one.
  • A process contains the program text, its static and dynamic data, pending I/O state, and the hardware state.
  • The same program can run in several processes; they share the text but have separate data and hardware state.
  • The three isolation mechanisms are multitasking, virtual memory and device abstraction.
  • ps lists processes; ps -e lists every process on the system.
  • init is the first process created at boot; a daemon is a background process providing OS services.

Practice Tip: Run ps -e | wc -l on your own machine and then look up three process names you do not recognise. Seeing 200-plus processes on an idle laptop makes the need for isolation obvious in a way the definition does not.

Common Mistake: Treating “abstraction” and “virtualization” as the same word. Every virtualization is an abstraction, but not every abstraction creates an illusion of something that is not physically there.

Important Questions:

  • What is a process and what does it contain? A software object representing a running program, containing the program text, its static and dynamic data, the state of any pending I/O, and the hardware state including registers and the program counter.
  • Which operating system capabilities isolate processes from each other? Multitasking, virtual memory and device abstraction.

Chapter 3: Virtual Memory

Difficulty: Hard · Key topics: bits and information, physical vs virtual addresses, the five memory segments, static local variables, MMU, page table, TLB

The longest chapter in the first half, and the one that carries the most exam marks. It opens with a short piece of information theory — b bits encode 2 to the power b values — because that arithmetic is needed for every address calculation that follows. Downey then separates volatile main memory from non-volatile storage, and physical addresses from virtual ones, explaining why a 64-bit virtual address space can be about a billion times larger than physical memory. The heart of the chapter is the five memory segments (code, static, global, heap, stack) and a small C program, aspace.c, that prints the address of a function, a global, a local, a malloc’d pointer and a string literal so you can see the layout on your own machine. It ends with address translation: the MMU splits a virtual address into a page number and an offset, looks the page number up in the TLB, and combines the physical page number with the offset. Downey then works out why a flat page table would need 16 MiB per process, and concludes that page tables are sparse, which is why multilevel and associative implementations exist.

Key Points:

  • With b bits you can encode 2 to the power b values; to encode N values you need b ≥ log₂N bits.
  • Memory is volatile and measured in binary units (GiB); storage is non-volatile and sold in decimal units (GB, TB).
  • The five segments are code, static, global, heap and stack; the heap grows up and the stack grows down.
  • A static local variable lives in the global segment, is initialised once, and keeps its value between calls.
  • The MMU splits a virtual address into a page number and an offset; only the page number is translated.
  • The TLB caches page-table entries; the page table itself lives in kernel memory, one per process.
  • Page tables are sparse, which is why a flat array of entries is the wrong implementation.

Practice Tip: Compile and run aspace.c from the book’s GitHub repository. Add a second malloc and a function that prints a local address, then check for yourself that the heap grows toward larger addresses and the stack toward smaller ones. The segment diagram is far easier to remember once your own machine has printed it.

Common Mistake: Assuming a large virtual address space means the machine has that much memory. Virtual addresses are just numbers a process may generate; only the pages it actually uses ever occupy physical memory.

Important Questions:

  • What are the five segments of a process’s memory? The code segment, the static segment, the global segment, the heap and the stack.
  • How is a virtual address translated into a physical address? The MMU splits it into a page number and an offset, looks the page number up in the TLB (backed by the page table) to get a physical page number, and joins that to the unchanged offset.

Chapter 4: Files and File Systems

Difficulty: Medium · Key topics: file system as a key-value mapping, blocks vs bytes, open file table, disk performance, inodes and indirection blocks, block allocation

Downey defines a file system as a mapping from names to contents — effectively a key-value database — and a file as a sequence of bytes. The gap the OS has to close is that files are byte-based while storage is block-based, with typical blocks of 1–8 KiB. He traces fopen, fgetc and fclose step by step, introducing the open file table entry and the file position. The performance section is the one worth memorising: a disk read takes 5–25 ms while a CPU completes an instruction in about 0.5 ns, so the CPU could run 40 million instructions while waiting on a hard disk. Four mechanisms close that gap — block transfers, prefetching, buffering and caching. The chapter then covers inodes: metadata plus the first 12 block numbers, then an indirection block, a double indirection block and finally a triple indirection block, giving a maximum file size of 8 TiB. It closes on the UNIX idea that pipes and sockets reuse the same stream-of-bytes interface as files.

Key Points:

  • A file system maps names to contents; a file is a sequence of bytes, but storage is organised in blocks.
  • An HDD read takes 5–25 ms; an SSD takes about 25 µs to read a 4 KiB block and 250 µs to write one.
  • The four gap-filling mechanisms are block transfers, prefetching, buffering and caching.
  • An inode holds ownership, permissions and timestamps plus the block numbers of the first 12 blocks.
  • Indirection, double indirection and triple indirection blocks extend the maximum file size to about 8 TiB.
  • FAT takes the other approach: a File Allocation Table chains clusters together like a linked list.
  • Block allocation aims for speed, low space overhead, minimal fragmentation and maximum contiguity — goals that conflict.

Practice Tip: Write a program that prints a value and then deliberately crashes before fclose. Watching the printed value disappear because it was still in a buffer teaches buffering better than any definition, and explains a whole class of confusing debugging sessions.

Common Mistake: Believing data is on disk as soon as your program wrote it. Writes are buffered in memory and only flushed later, which is why a power failure can lose data your program already “wrote”.

Important Questions:

  • What is an inode? The on-disk metadata structure for a file, holding the owner, permission flags, timestamps and the block numbers where the file’s data is stored.
  • Why are indirection blocks needed? An inode has room for only about a dozen direct block numbers, so larger files need blocks that contain nothing but pointers to further blocks.

Chapter 5: More Bits and Bytes

Difficulty: Medium · Key topics: two’s complement, sign extension, bitwise AND OR XOR, shifts, IEEE floating point, unions, ASCII and null-terminated strings

This is the bit-level chapter, and it is the one that most directly improves your C. Negative integers are represented in two’s complement: take the positive value, flip every bit and add one, which makes the leftmost bit behave like a sign bit and makes sign extension a matter of copying that bit. The bitwise operators are then given a purpose each — AND with a mask clears bits, OR sets them, XOR toggles them — and shifts are shown as multiplication and division by two. Floating point is unpacked into sign, exponent and coefficient, with the 32-bit standard using a bias of 127 and dropping the leading 1 because a normalised binary number always has one. Downey then demonstrates reading a float’s bits through a union, and shows what happens when you read past the end of an array: you see whatever the previous function call left on the stack. The chapter ends on strings: they are null-terminated, ASCII “0” is 48, and upper and lower case differ by a single bit.

Key Points:

  • Two’s complement of −x: write x in binary, flip all bits, add 1. The leftmost bit acts as the sign bit.
  • Sign extension copies the sign bit into the new high bits; unsigned types do not sign-extend.
  • AND with a mask clears bits, OR sets bits, XOR toggles bits.
  • Left shift by 1 doubles a value; right shift by 1 halves it, rounding down.
  • A 32-bit float is 1 sign bit, 8 exponent bits (bias 127) and 23 coefficient bits, with the leading 1 implied.
  • C strings are null-terminated; the ASCII code for “0” is 48, for “A” is 65 and for “a” is 97.
  • Upper and lower case letters differ only in the sixth bit, so case can be flipped with one XOR.

Practice Tip: Take the book’s exercise and write a function that converts a string to upper case by flipping the sixth bit of each character. It is three lines, it works, and it turns the ASCII table from something you look up into something you understand.

Common Mistake: Confusing the digit character with the digit value. The character ‘7’ is ASCII 55, not 7, which is why reading digits from a string without subtracting ‘0’ gives wrong numbers.

Important Questions:

  • How is a negative integer represented in two’s complement? Write the positive value in binary, flip every bit, then add one; the leftmost bit then acts as a sign bit.
  • What do the bitwise operators &, | and ^ do? AND clears the bits not selected by the mask, OR sets bits, and XOR toggles them.

Chapter 6: Memory Management

Difficulty: Medium · Key topics: malloc, calloc, free, realloc, memory errors, memory leaks, sbrk, boundary tags, fragmentation, binning

Four functions carry all of C’s dynamic memory management, and this chapter covers both how to use them and how they work. malloc returns a pointer or NULL, calloc also zeroes the chunk, free releases it and realloc resizes it. Downey then lists the five things that earn “a paddling” — accessing an unallocated chunk, using a chunk after freeing it, freeing something never allocated, double-freeing, and reallocating a freed chunk — and explains why these bugs are so hard to find: the symptoms are unpredictable and often appear far from the cause. Memory leaks get their own section, including when a leak is acceptable (a short program that exits) and when it is not. The implementation section explains sbrk and the program break, boundary tags that let malloc walk from one chunk to its neighbours, the doubly-linked free list, why the minimum chunk size is about 16 bytes, and how binning keeps allocation fast.

Key Points:

  • malloc returns NULL when it cannot satisfy a request — always check the return value before using the pointer.
  • calloc clears the chunk, so its run time depends on the chunk size; malloc’s usually does not.
  • A memory leak is allocated memory that is never freed; in a long-running program it grows without limit.
  • malloc requests more memory from the OS with sbrk, which moves the program break at the end of the heap.
  • Boundary tags store each chunk’s size and state at its start and end, so malloc can reach neighbouring chunks.
  • Those tags sit between your data, which is why writing past the end of a chunk corrupts malloc itself.
  • The minimum chunk size is about 16 bytes, so many tiny allocations waste space — use an array instead.

Practice Tip: Wrap malloc in your own check_malloc that calls perror and exits on NULL, and use it everywhere. Downey does exactly this in the book, and it removes the error-checking clutter that makes people skip the check in the first place.

Common Mistake: Assuming a crash points at the broken line. When you write past the end of a chunk you damage malloc’s own boundary tags, and the failure appears later inside an unrelated call to malloc or free.

Important Questions:

  • What is the difference between malloc and calloc? Both allocate a chunk of heap memory, but calloc also sets every byte in it to zero, so its run time depends on the size of the chunk.
  • What is a memory leak and when does it matter? Memory that is allocated and never freed. It is harmless in a short program that exits, but in a long-running program it makes memory use grow until allocation fails.

Chapter 7: Caching

Difficulty: Hard · Key topics: registers, the instruction cycle, the memory bottleneck, hit and miss rates, temporal and spatial locality, the memory hierarchy, cache policy, paging and thrashing

The longest chapter in the book, and the one that ties the others together. It starts with the registers — program counter, instruction register, stack pointer, general-purpose and status registers — and the fetch, decode, execute cycle. That sets up the memory bottleneck: a core executes an instruction in under 1 ns but a memory access takes about 100 ns, so memory, not the CPU, is often the speed limit. Caching is the answer, with average access time expressed as the hit time plus the miss rate times the miss penalty. Downey then explains temporal and spatial locality, and describes a measurement experiment that infers the real cache size and block size of a machine by varying array size and stride. The memory hierarchy section gives a table of access times from registers (0.5 ns) down to tape (minutes) and frames caching as four questions: who moves the data, what moves, when, and where does it go. The chapter ends with paging — how a victim page is chosen and swapped out — and thrashing, when two processes keep evicting each other’s pages and the system becomes unresponsive.

Key Points:

  • The instruction cycle is fetch, decode, execute; instructions fall into load, arithmetic/logic, store and jump/branch.
  • The memory bottleneck: about 1 ns to execute an instruction versus about 100 ns to reach memory.
  • Average access time = hit time + (miss rate × miss penalty), so a low miss rate makes memory feel as fast as cache.
  • Temporal locality is reusing the same data; spatial locality is using data in nearby locations.
  • Traverse a 2-D array row-wise, not column-wise — row-wise access has spatial locality and is much faster.
  • Caches are small because making them big makes them slow and expensive; that trade-off is the whole reason caching exists.
  • LRU replacement rests on the idea that recently used data will be used again soon.
  • Thrashing is when processes keep evicting each other’s pages and the system spends its time swapping.

Memory Tip: Remember the four caching questions as who, what, when and where — who moves the data, what gets moved, when it moves, and where in the cache it goes. Those four answers together are the cache policy, and they apply at every level from registers down to tape.

Common Mistake: Thinking cache performance is the hardware’s problem alone. The same loop over the same array can run several times faster or slower depending only on the order in which your code walks it.

Important Questions:

  • What is the difference between temporal and spatial locality? Temporal locality is the tendency to use the same data more than once; spatial locality is the tendency to use data stored in nearby locations.
  • What is thrashing? A state in which processes together need more memory than is physically available, so they keep swapping each other’s pages out and the system spends its time paging instead of working.

Chapter 8: Multitasking

Difficulty: Medium · Key topics: the kernel, hardware and software interrupts, system calls, context switching, time slice, the process life cycle, priority scheduling, real-time scheduling

This chapter explains how one core creates the illusion of many programs running at once. The kernel’s most basic job is handling interrupts: hardware interrupts come from devices and timers, software interrupts come from running programs, and a system call is a special instruction that deliberately triggers an interrupt so the kernel can act on the program’s behalf. Downey then separates interrupt handling from context switching — a handler saves only the registers it will use and is fast, while a context switch saves everything, may clear MMU data, and costs thousands of cycles. The process life cycle names four states, running, ready, blocked and done, and lists every event that moves a process between them. The scheduling section explains CPU-bound versus I/O-bound processes, gives the apple-pie analogy for why short blocking jobs should run first, and lists the five factors that raise or lower a process’s priority. It ends with real-time scheduling and the changes an OS needs to guarantee deadlines.

Key Points:

  • The kernel is the lowest layer of the OS; its most basic job is handling interrupts.
  • A system call is a special instruction that triggers an interrupt so the kernel can perform a privileged operation.
  • Interrupt handlers are fast because they save only the registers they use; context switches save everything.
  • A context switch costs thousands of cycles — a few microseconds — because of register saving, MMU work and cold caches.
  • The four process states are running, ready, blocked and done.
  • A CPU-bound process runs faster with more CPU time; an I/O-bound process does not.
  • Priority goes up for a process that blocks early, and down for one that uses its whole time slice.
  • The nice system call lets a process lower — but never raise — its own priority.

Memory Tip: Use the book’s apple-pie analogy for scheduling. Start the crust (5 minutes of work, then 30 minutes of chilling) before the filling and the pie takes 35 minutes; do it the other way and it takes 55. Short jobs that then block should go first — that single picture explains most scheduling heuristics.

Common Mistake: Using “interrupt” and “context switch” as if they were the same event. Every context switch begins with an interrupt, but most interrupts are handled and the same process simply resumes.

Important Questions:

  • What are the four states in the process life cycle? Running, ready, blocked and done.
  • Why is a context switch slower than handling an interrupt? An interrupt handler saves only the registers it uses, while a context switch must save the entire hardware state, may have to update the memory management unit, and leaves the new process running with a cold cache.

Chapter 9: Threads

Difficulty: Hard · Key topics: what threads share and what they do not, Pthreads, pthread_create, pthread_join, synchronization errors, mutual exclusion, mutex

A thread is defined precisely here: creating a process makes a new address space and a new thread of execution, while creating a thread adds a second thread of execution inside the same address space. Threads share the text segment, the static segment and the heap, but each gets its own stack, which is why they can call functions independently but see each other’s global changes. The chapter is a working POSIX Threads tutorial — the headers to include, linking with -lpthread, pthread_create with its awkward void-pointer entry function, and pthread_join to wait for children. Downey then runs a counter program with five child threads and gets the output 0, 0, 1, 0, 3. He walks through an interleaving that produces exactly that, which makes the race condition concrete rather than theoretical, and fixes it with a mutex so the counter finally prints 0 through 4.

Key Points:

  • Threads in one process share the text, static and heap segments; each thread has its own stack and registers.
  • Pthreads is the POSIX threading standard; compile with -lpthread to link it.
  • pthread_create returns 0 on success and an error code on failure — it does not use errno the usual way.
  • The thread entry function must take a void pointer and return a void pointer, so shared data is cast to and from it.
  • pthread_join blocks until the named thread finishes; joining children in creation order still works if they finish out of order.
  • Unsynchronized access to a shared variable gives different output on every run.
  • A mutex guarantees mutual exclusion: only one thread runs the protected block at a time.

Practice Tip: Compile and run the book’s counter program with five children, then run it five more times. Getting different output each run — and never the output you expected — is the fastest possible proof that a race condition is real, and it takes about a minute.

Common Mistake: Passing a pthread_mutex_t by value. It behaves like a structure, so passing it as an argument copies it and the copy no longer provides mutual exclusion — always pass a pointer, which is exactly why Downey’s wrapper returns one.

Important Questions:

  • What do threads in the same process share, and what do they not share? They share the text, static and heap segments, so they see the same code and the same globals; each thread has its own stack and its own registers.
  • What is a mutex? An object that provides mutual exclusion for a block of code, so only one thread can execute that block at a time.

Chapter 10: Condition Variables

Difficulty: Hard · Key topics: producer-consumer, circular buffer, thread-safe queue, cond_wait and cond_signal, why wait goes in a while loop, intercepted signals

The producer-consumer problem is built up here in three stages, which is what makes the chapter so useful. First a plain circular-buffer queue, with the neat trick that next_in == next_out means empty, so the queue must stop one slot short of full to keep the two cases distinguishable. Then a mutex is added, making the queue thread safe but leaving the real problem: a consumer that finds the queue empty still has to exit. Finally the condition variable solves it. Downey explains carefully that cond_wait unlocks the mutex before blocking — if it did not, no producer could ever add an item — and re-locks it before returning. He then answers the two questions students always ask: why the wait must sit inside a while loop rather than an if (because a third thread can consume the item between the signal and your thread waking, an intercepted signal), and how a condition variable knows its condition (it does not — the connection is purely in how you use it).

Key Points:

  • In a circular buffer, next_in == next_out marks empty, so the queue stops one slot short of full.
  • cond_wait takes both the condition variable and the mutex, because it must unlock the mutex before blocking.
  • On waking, cond_wait re-acquires the mutex before returning, so the queue is safe to touch.
  • Always put cond_wait inside a while loop, never an if — the condition can become false again before you run.
  • Signalling a condition variable with no waiters has no effect at all.
  • A condition variable has no built-in link to its condition; the connection exists only in how the code uses it.
  • A bounded queue needs two condition variables, one for “not empty” and one for “not full”.

Memory Tip: Downey’s own definition is the one to memorise: the condition associated with a condition variable is the thing that is false when you call wait and true when you call signal. Every producer-consumer question becomes easier once you can name that condition out loud.

Common Mistake: Using if instead of while around cond_wait. It passes every two-thread test and fails the moment a third thread can consume the item between the signal and your thread actually running.

Important Questions:

  • Why does cond_wait unlock the mutex before blocking? Because the waiting thread holds the mutex, and if it kept it while blocked no other thread could change the condition it is waiting for — the program would deadlock.
  • Why must the condition be re-checked in a while loop? Because another thread can lock the mutex and undo the condition between the signal and the waiting thread resuming, so the condition may be false again by the time wait returns.

Chapter 11: Semaphores in C

Difficulty: Hard · Key topics: POSIX semaphores, sem_wait and sem_post, binary semaphore as a mutex, producer-consumer with three semaphores, implementing a semaphore

The final chapter presents semaphores as a teaching tool rather than the tool most code uses. The POSIX API is small — sem_init, sem_wait and sem_post — and Downey wraps each with error checking. A semaphore initialised to 1 behaves as a mutex, and the choice of initial value is the whole trick. The producer-consumer solution is then rewritten with three semaphores instead of a mutex and two condition variables: mutex starting at 1 for exclusive access, items starting at 0 counting what is in the queue, and spaces starting at length−1 counting the free slots. Because the semaphores count, the code no longer needs to test whether the queue is full or empty at all. The chapter closes with the book’s final challenge: implementing a semaphore from a mutex and a condition variable, using a wakeups counter and a do-while loop so a signalling thread cannot catch its own signal.

Key Points:

  • The POSIX semaphore API is sem_init, sem_wait and sem_post; the type is sem_t.
  • A semaphore initialised to 1 is a binary semaphore and behaves like a mutex.
  • In the queue solution: mutex starts at 1, items starts at 0, spaces starts at length−1.
  • Because the semaphores count slots, queue_full and queue_empty are no longer needed.
  • In queue_push, wait on spaces before waiting on mutex — reversing the order deadlocks.
  • Anything solvable with semaphores is solvable with mutexes and condition variables, and the reverse.
  • Downey’s own semaphore uses a wakeups counter and a do-while loop so a thread cannot catch its own signal.

Memory Tip: Fix the initial values by asking what the semaphore counts. Exclusive access means one thread may pass, so start at 1. Items in an empty queue means none available, so start at 0. Free spaces means the whole capacity, so start at length−1. Get the meaning first and the number follows.

Common Mistake: Waiting on the mutex before waiting on spaces in queue_push. A producer then holds the mutex while blocking on a full queue, no consumer can get in to remove an item, and the program deadlocks.

Important Questions:

  • How is a semaphore used as a mutex? Initialise it to 1 so exactly one thread can pass without blocking, then call wait before the protected code and signal after it.
  • Which semaphores does the producer-consumer solution use, and what are their initial values? mutex initialised to 1 for exclusive access, items initialised to 0 for the number of items in the queue, and spaces initialised to length−1 for the number of free slots.

Download Think OS PDF (Free)

This book is free from its official source at Green Tea Press. Click below to download the complete PDF. A free online HTML edition of the same book is also available if you would rather read it in the browser, and all the example C programs are on GitHub.

↓ Download PDF

How to Study This Book

Read Chapters 1 and 2 first, in one sitting. They are the shortest chapters in the book and they define the vocabulary — static versus dynamic, abstraction versus virtualization, what a process contains — that every later chapter uses without re-explaining.

Chapter 3 on virtual memory is the most important chapter for exams and the hardest one to read passively. Give it the most time, run the aspace.c program while you read it, and do not move on until the five memory segments and the page-number-plus-offset split are clear. Chapter 7 on caching later assumes all of it.

Chapters 4, 5 and 6 — file systems, bit representation and memory management — can be read in any order. Chapter 5 is the one that most improves your practical C, and Chapter 6 explains the memory bugs you will actually hit in your programming assignments.

Chapter 7 is the longest and best chapter in the book, but read it after Chapter 3, because paging and thrashing at the end depend on virtual memory. Chapter 8 on multitasking then follows naturally, and the two together cover most of what a standard OS paper asks about scheduling.

Chapters 9, 10 and 11 are a single sequence on concurrency and must be read in order: threads and mutexes, then condition variables, then semaphores. Do not read these passively. Compile and run the counter and queue programs from the book’s GitHub repository — concurrency questions in exams are usually “what output is possible and why”, and you cannot answer those from reading alone.

One thing to know before you start: the author describes this version as an early draft and the figures are not yet included, so a few explanations refer to diagrams you will not see. It does not affect the text, but for the address translation diagram in Chapter 3 you may want a second reference alongside it.


Used In These Programs

This book is used in the Operating Systems and Systems Programming courses in: BS Computer Science · BS Information Technology · BS Software Engineering. Browse all Operating Systems books or all Computer Science books.

Who Should Read This

Think OS is written for students who already program but have not studied computer architecture, which describes most BSCS and BSIT students in their third or fourth semester. It suits you if you want to understand what happens when your C programs run — why a segmentation fault appears, where a memory leak goes, why one loop is faster than another — rather than how to design a kernel. Because it is short and every concept comes with runnable C code, it also works well as a companion to a heavier OS textbook: read Think OS for the intuition, then go to the standard textbook for the formal treatment and the algorithms. Students preparing for systems-programming interviews will find Chapters 3, 5, 6, 7 and 9 especially useful. If your paper focuses on scheduling algorithms, deadlock analysis and file system implementation in detail, pair this book with a fuller OS text such as Operating Systems: Three Easy Pieces, which covers exactly those topics in depth and is also free.


Applicable Universities

This book is useful for students at Pakistani universities offering BSCS, BSIT and BSSE including Punjab University, Virtual University, COMSATS, FAST, UET, NUST, and other HEC-recognized institutions.

FAQs

Is Think OS free to download?

Yes. Allen B. Downey publishes it free through Green Tea Press under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 licence, as a PDF and as a free online HTML edition.

How many chapters does Think OS have?

Eleven chapters plus a preface: Compilation, Processes, Virtual memory, Files and file systems, More bits and bytes, Memory management, Caching, Multitasking, Threads, Condition variables, and Semaphores in C.

Which version is this?

Version 0.7.4. The author describes it as an early draft and notes that the figures are not yet included, so a few explanations refer to diagrams that are still missing.

Do I need to know C to read Think OS?

Some C helps, but the book does not assume much. It was written for students who learned Python first, and it explains C ideas such as pointers, unions, bitwise operators and malloc as it goes. It assumes no computer architecture background at all.

Is Think OS enough for a university Operating Systems paper?

It covers virtual memory, file systems, caching, scheduling and concurrency very clearly, but it is short and does not go deep into scheduling algorithms, deadlock analysis or distributed systems. Use it alongside a fuller Operating Systems textbook for a theory-heavy paper.

Where is the example code?

All the C programs used in the book — aspace.c, the cache measurement program, the counter and the producer-consumer queue — are in the author’s public GitHub repository, linked from the official book page.

Related Books

Think OS is short, honest and unusually practical: eleven chapters that explain what a working programmer needs to know about the operating system, with C code you can run for every idea. It will not replace a full Operating Systems textbook for a theory-heavy paper, but it will make that textbook far easier to read. Browse more Computer Science books for the rest of your semester.

Think OS: A Brief Introduction to Operating Systems by Allen B. Downey, Green Tea Press. Free under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. Official source: https://greenteapress.com/wp/think-os/

Leave a Comment