Computer & OS Fundamentals · beginner · ~10 min
**What you will learn** - Trace the full path a program takes from source code to a running process the CPU executes. - Describe the CPU's fetch-decode-execute cycle and the role of the *program counter*. - Explain what *machine code* is and how compiled and interpreted code both end up as machine instructions. - Define the *von Neumann architecture* and explain why storing code and data in the same memory has consequences for safety and security. - Read a simple text diagram of CPU + memory and predict which instruction runs next. - Connect this mental model to everyday tasks: debugging, performance, and understanding why memory bugs can change program behavior.
Every app you use — a browser, a game, a chat program — is ultimately a list of tiny instructions that a chip called the CPU (central processing unit) carries out one at a time. Before any of that can happen, the program you wrote as readable text has to be turned into a form the hardware understands, loaded into memory, and handed to the CPU.
This lesson follows that journey end to end. It is the foundation for everything else in this track: once you understand that a running program is just data being read and acted on by a simple, fast loop, ideas like compiling, memory, processes, and even security bugs stop feeling like magic.
The CPU itself does something surprisingly simple. It repeats one loop billions of times per second: read the next instruction, figure out what it means, do it, repeat. That loop is called fetch-decode-execute. A small storage slot inside the CPU, the program counter, remembers the address of the next instruction so the CPU always knows where to look.
The instructions the CPU reads are machine code — raw numbers that encode operations like "add these two values" or "jump to this address." You almost never write machine code by hand. Instead, you write in a higher-level language. A compiled language like C is translated ahead of time into machine code by a compiler. An interpreted language is read and run by another program, an interpreter, while it executes. Either way, what the hardware actually runs is always machine code.
Finally, this lesson introduces the von Neumann architecture: the design, used by nearly every computer you will ever touch, where instructions and data live together in the same memory. That single design choice is why programs are flexible — and why a bug that corrupts data can sometimes corrupt instructions too.
This mental model pays off constantly, even far from security work.
Definition. The CPU is the chip that executes instructions. Its core behavior is a repeating loop called the fetch-decode-execute cycle.
Plain language. The CPU is like a very fast, very literal worker who can only follow one instruction at a time off a list. It reads the next instruction, understands it, does it, and immediately reaches for the next one.
How it works internally. Each pass through the loop has three steps:
After execute, the program counter normally advances to the following instruction, and the loop repeats — billions of times per second.
+-------------------- CPU --------------------+
| |
| [ Program Counter ] --> address of next |
| | |
| v |
| 1. FETCH instruction from memory |
| | |
| v |
| 2. DECODE what it means |
| | |
| v |
| 3. EXECUTE it --> may change PC (jump) |
| | |
| +--------> loop back to FETCH |
+---------------------------------------------+
When this model matters. Any time you reason about order of execution, jumps, loops, or why a crash happened at a specific point. When not to over-apply it: for everyday coding you usually think in higher-level terms (loops, functions); you do not trace single CPU instructions unless you are debugging at a very low level.
Common pitfall. Beginners imagine the CPU "sees the whole program at once." It does not. It only ever knows the one address in the program counter and works strictly step by step.
Knowledge check (explain in your own words): In your own words, what are the three steps of the cycle, and which one can change where the program goes next?
Definition. The program counter (PC) is a small, fast register inside the CPU that holds the memory address of the next instruction to fetch.
Plain language. It is the CPU's bookmark. It does not hold the instruction itself — it holds where the instruction is.
How it works. After each instruction, the PC usually moves forward to the next instruction. But some instructions (jumps, calls, returns, branches from if/loops) deliberately set the PC to a different address. That is how programs make decisions and repeat.
Memory addresses: 100 104 108 112 116
Instructions: [ADD] [SUB] [JMP] [...] [...]
|
PC = 100 -> fetch ADD, PC -> 104
PC = 104 -> fetch SUB, PC -> 108
PC = 108 -> fetch JMP 100 (sets PC back to 100!)
PC = 100 -> fetch ADD again ... (a loop)
When to think about it. Whenever control flow is involved: loops, conditionals, function calls. Pitfall: confusing the PC's contents (an address) with the instruction stored at that address. The PC holds the address, not the operation.
Knowledge check (predict): In the diagram above, the JMP at address 108 sends the PC back to 100. What kind of program structure does this create?
Definition. Machine code is the set of raw numeric instructions a specific CPU can execute directly. It is the only language the hardware truly understands.
Plain language. You write friendly text like x = a + b;. The CPU cannot read that. Something must translate it into numbers that mean "add."
Two ways to get there:
| Compiled (e.g., C) | Interpreted (e.g., Python) | |
|---|---|---|
| When translation happens | Ahead of time, by a compiler | While running, by an interpreter |
| What ships to the user | Machine code (an executable) | The source, plus an interpreter |
| Typical speed | Faster (already machine code) | Slower (translated as it runs) |
| What the CPU runs | The machine code directly | The interpreter's machine code, which reads your source |
The key insight: either way, the CPU only ever executes machine code. With a compiled language, your code becomes machine code. With an interpreted language, the interpreter is the machine-code program, and it reads your source as data.
Compiled path:
source.c --(compiler)--> machine code --> loaded into memory --> CPU runs it
Interpreted path:
source.py --read by--> interpreter (itself machine code) --> CPU runs interpreter
When to use which is usually decided by the language, not by you per-program. Pitfall: thinking interpreted languages "don't use machine code." They do — the interpreter is machine code; your script is its input.
Knowledge check (find the misconception): A friend says, "Python doesn't run on the CPU, it runs in the interpreter, so machine code isn't involved." What is wrong with this statement?
Definition. The von Neumann architecture is a computer design in which both instructions (code) and the values they work on (data) are stored in the same memory.
Plain language. There is one big pool of numbered storage slots. Some slots happen to hold instructions; others hold data. The hardware does not paint them different colors — a slot is just a slot.
How it works / why it is powerful. Because code is stored like any other data, a program can be loaded, copied, and replaced easily. That flexibility is why you can install new software without rewiring anything. The CPU simply points the program counter at wherever the new code lives.
Single shared memory
+------------------------------------------+
| addr 100: instruction (code) |
| addr 104: instruction (code) |
| addr 108: instruction (code) |
| ... |
| addr 500: data (e.g., your input) |
| addr 504: data |
+------------------------------------------+
^ ^
code lives here data lives here
(same memory, just different addresses)
The trade-off (why it matters for robustness and safety). Since code and data live together, a bug that writes data into the wrong place can, in extreme cases, corrupt memory the CPU later treats as instructions or as the address of the next instruction. This is exactly why writing carefully within the bounds of your buffers matters in C — a theme this track returns to often.
Pitfall: assuming the computer "knows" which bytes are code and which are data. In a pure von Neumann machine it does not inherently; correctness depends on the program (and the operating system's protections) keeping them straight.
Knowledge check (concept): Name one benefit and one risk of storing code and data in the same memory.
This is a concept lesson, so there is no programming syntax to memorize. Instead, fix this vocabulary, because every later lesson reuses it:
A handy one-line summary of the whole pipeline:
source code --> (compiler or interpreter) --> machine code in memory --> CPU fetch-decode-execute
A program is just data until the CPU executes it. Understanding that path is the foundation for everything from debugging to exploitation.
The CPU repeats one loop billions of times per second:
For a compiled language like C, the path is:
source -> compiler -> machine code -> loaded into memory -> executed
For an interpreted language, an interpreter reads and runs the source directly. The interpreter is itself a compiled program.
Either way, what the CPU runs is always machine code.
Memory-corruption bugs, shellcode, and reverse engineering all live at this layer.
Two facts matter most:
Put those together and you see why overwriting a return address can hijack execution. That is the core idea behind classic exploitation.
Because this is a concept lesson, the "code" here is a plain-text trace of the fetch-decode-execute cycle for a tiny made-up program. It is pseudo-assembly (not a real CPU's instruction set) chosen so you can read it without prior assembly knowledge. The goal is to see the loop run.
Program (stored in memory, one instruction per address):
addr 100: LOAD R1, [500] ; copy the data at address 500 into register R1
addr 104: LOAD R2, [504] ; copy the data at address 504 into register R2
addr 108: ADD R3, R1, R2 ; R3 = R1 + R2
addr 112: STORE [508], R3 ; copy R3 into memory at address 508
addr 116: HALT ; stop the CPU
Data (also in memory, same address space):
addr 500: 7
addr 504: 5
addr 508: (empty, will receive the result)
Start: program counter (PC) = 100
What it does. This program adds two numbers that live in memory (7 and 5), then stores the result (12) back into memory. Notice that the instructions (addresses 100-116) and the data (addresses 500-508) sit in the same numbered memory — that is the von Neumann idea in action.
Expected result. After the program runs, memory address 508 holds 12, and the CPU stops at the HALT instruction.
Key edge cases to keep in mind:
Let's walk the cycle step by step, watching the program counter (PC) and registers change. Each row is one full fetch-decode-execute pass.
| Step | PC before | Instruction fetched | What execute does | PC after |
|---|---|---|---|---|
| 1 | 100 | LOAD R1, [500] |
R1 = value at addr 500 = 7 | 104 |
| 2 | 104 | LOAD R2, [504] |
R2 = value at addr 504 = 5 | 108 |
| 3 | 108 | ADD R3, R1, R2 |
R3 = 7 + 5 = 12 | 112 |
| 4 | 112 | STORE [508], R3 |
memory[508] = R3 = 12 | 116 |
| 5 | 116 | HALT |
CPU stops | — |
Narration of what happens:
Memory at the end: addresses 500 and 504 are unchanged (7 and 5); address 508 now holds 12. The result is produced because each instruction did exactly one small, predictable step, and the PC marched through them in order — the entire essence of the fetch-decode-execute loop.
Mistake 1: Thinking the program counter holds the instruction.
ADD R3, R1, R2."Mistake 2: Believing interpreted languages avoid machine code.
Mistake 3: Assuming the computer knows which bytes are code and which are data.
Mistake 4: Imagining the CPU runs many instructions "at once" because computers are fast.
Even for a concept this foundational, here are the practical signs that your mental model (or, later, your real code) is off, and how to recover.
Logic-error symptoms in your reasoning:
if, loop, or function call), explicitly write the new PC value before continuing.When real programs misbehave (looking ahead to the C track):
Questions to ask when it does not work:
This is a non-security concept lesson, so the focus here is on robustness and building correct intuition rather than attacks.
The single most important takeaway is a consequence of the von Neumann architecture: code and data share one memory. Two robustness implications follow.
Modern operating systems add protections (for example, marking the regions that hold code as read-only and refusing to execute regions meant only for data). These protections exist precisely because the underlying von Neumann design would otherwise let code and data be confused. Knowing why those protections exist makes the rest of this track easier to understand.
Concrete real-world uses of this model:
gdb let you watch the program counter, single-step instructions, and inspect registers — they are literally exposing the loop you learned here. Profilers sample which instruction the PC is on to find slow spots.Professional best-practice habits this lesson seeds:
Beginner habits:
More advanced habits:
Beginner 1 — Trace the cycle.
Beginner 2 — Address vs. value.
Intermediate 1 — Compiled vs. interpreted.
Intermediate 2 — Add a loop.
DEC, JNZ (jump if not zero); state what each means.Challenge — Predict the crash.