Course Summary
#
CS2470: Systems Programming in C/C++
The Plot: Programs need hardware resources. The OS mediates access via system calls. This course is about writing programs that use system calls on AMD64 Linux.
Part 1: C Fundamentals (Weeks 1-4)
#
Types and Memory
#
- Built-in types:
char, short, int, long
sizeof(type) gives byte size
- Pointers: variables storing memory addresses with associated type
- Pointer arithmetic:
p + 1 steps by type size, not 1 byte
Arrays
#
- Contiguous memory sequence starting at an address
- Array variable = pointer to start
arr[i] equivalent to *(arr + i)
- Can’t pass arrays to functions - pass pointer + size
Memory Layout
#
+------------------+ High address
| Stack | (local variables, grows down)
+------------------+
| (free) |
+------------------+
| Heap | (malloc, grows up)
+------------------+
| Data | (globals)
+------------------+
| Text | (code)
+------------------+ Low address
Dynamic Memory
#
malloc(size) - allocate on heap
free(ptr) - deallocate
- Use malloc when size unknown at compile time or to return from function
- Caller must free - part of function interface
Structs
#
typedef struct goat {
char* name;
int age;
} goat;
- Stack allocation:
goat g; - dot access: g.name
- Heap allocation:
goat* g = malloc(...) - arrow access: g->name
- Can pass structs by value (copies entire struct)
- Can pass structs by reference (pointer)
Linked Lists
#
typedef struct cell {
int head;
struct cell* tail;
} cell;
- Recursive data structure
- O(1) insertion
- Ownership: who frees?
- Header files, include guards, modules
Slices
#
typedef struct slice {
const char* ptr;
int len;
} slice;
- View into existing memory (no allocation)
- Safe string handling without null terminator dependence
printf("%.*s", sl.len, sl.ptr);
Part 2: System Calls (Weeks 5-6)
#
C Stdlib vs Syscalls
#
| Layer |
Example |
Description |
| Stdlib |
fopen, fread, fgets |
Buffered, human-friendly |
| Syscall |
open, read |
Unbuffered, kernel interface |
- Stdlib functions are user code that makes syscalls
- Syscalls require kernel privilege (syscall instruction)
- Man pages: section 2 = syscalls, section 3 = stdlib
File Descriptors
#
- Integer handle for open file/socket
- 0 = stdin, 1 = stdout, 2 = stderr
open(path, flags) returns new fd
read(fd, buf, count) reads bytes
write(fd, buf, count) writes bytes
close(fd) releases handle
I/O Patterns
#
- Line-based:
fgets - scans for newline, needs buffering
- Block-based:
fread / read - fixed chunks, no buffering needed
Part 3: Parsing and Interpreters (Week 7)
#
Calculator Pipeline
#
- Read line:
fgets from stdin or file
- Tokenize: Split text into tokens (state machine)
- Parse: Build Abstract Syntax Tree
- Evaluate: Recursive tree traversal
Tokenizer
#
list* tokenize(const char* text) {
// Scan character by character
// Numbers: accumulate digits
// Operators: single char tokens
// Skip whitespace
}
Abstract Syntax Tree
#
typedef struct calc_ast {
char op; // '+', '-', '*', '/', '='
calc_ast* arg0; // left subtree
calc_ast* arg1; // right subtree
int value; // for '=' nodes
} calc_ast;
- Numbers are leaf nodes (op = ‘=’)
- Operators are internal nodes
- Order of operations encoded in structure
Evaluation
#
int ast_eval(calc_ast* ast) {
switch (ast->op) {
case '=': return ast->value;
case '+': return ast_eval(ast->arg0) + ast_eval(ast->arg1);
// ...
}
}
Part 4: Networking (Week 8)
#
TCP/IP Layers
#
| Layer |
Description |
| IP |
Unreliable, unordered packets |
| TCP |
Reliable stream (retransmission, buffering) |
| Port |
0-65535, identifies service |
Socket Client
#
int sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, (struct sockaddr*)&addr, sizeof(addr));
send(sock, msg, len, 0);
recv(sock, buf, size, 0);
close(sock);
socket() - create socket fd
connect() - establish connection
send()/recv() - communicate
- HTTP: text protocol over TCP
Part 5: Processes (Weeks 9-10)
#
fork()
#
if ((cpid = fork())) {
// Parent: cpid = child's PID
wait(&status);
}
else {
// Child: cpid = 0
// Copy of parent's memory
}
- Creates child process with copy of memory
- Parent and child run concurrently
- File descriptor table copied (shares open files)
exec()
#
execlp("echo", "echo", "hello", NULL);
// Never returns (on success)
// Replaces process memory with new program
- Replace current process with new program
- Keeps PID and open file descriptors
wait()
#
int status;
waitpid(cpid, &status, 0);
// Blocks until child exits
// status contains exit code
- Prevents zombie processes
- Retrieves child exit status
Pipes
#
int pp[2];
pipe(pp);
// pp[0] = read end, pp[1] = write end
- Unidirectional byte stream
- Used for process communication
- Close unused ends (for EOF detection)
Prime Sieve Pipeline
#
- Chain of processes filtering primes
- Each process: receive numbers, filter multiples of first, pass survivors
- Dynamic: spawn next filter on first survivor
- Illustrates: pipes, fork, process coordination
Part 6: C++ (Weeks 11-12)
#
C++ = C + Three Things
#
- Templates (generics)
- Classes (constructors, destructors, methods)
- Standard Library (containers, algorithms, smart pointers)
Templates
#
template <typename T>
struct cell {
T head;
cell<T>* tail;
};
cell<int>* xs = cons(3, cons(4, nullptr));
- Compiler generates type-specific code at compile time
- No runtime overhead
Classes
#
class Cell {
public:
int head;
Cell* tail;
Cell(int h, Cell* t) : head(h), tail(t) {}
~Cell() { if (tail) delete tail; }
int len() { return tail ? 1 + tail->len() : 1; }
};
- Constructor: initialization
- Destructor: cleanup (called on delete or stack unwind)
- Methods: functions associated with type
RAII (Resource Acquisition Is Initialization)
#
- Acquire resource in constructor
- Release in destructor
- Works for: memory, files, locks, sockets
- Exception-safe cleanup
Smart Pointers
#
| Type |
Ownership |
Use Case |
unique_ptr |
Single owner |
Most heap allocations |
shared_ptr |
Shared, ref-counted |
Multiple owners |
weak_ptr |
Non-owning reference |
Break cycles |
auto p = std::make_unique<int>(42);
// No manual delete needed
Modern C++ Features
#
- Range-based for:
for (auto& x : vec)
- Lambdas:
[capture](args) { body }
[] nothing, [=] by value, [&] by reference
- Structured bindings:
auto [a, b] = pair
- std::optional: nullable without pointers
- constexpr: compile-time evaluation
- C++20 Ranges:
vec | filter | transform
Part 7: Shell Implementation (Week 13)
#
Shell Operators
#
| Operator |
Example |
Behavior |
< |
cmd < file |
Redirect stdin |
> |
cmd > file |
Redirect stdout |
| ` |
` |
`cmd1 |
& |
cmd & |
Background execution |
&& |
cmd1 && cmd2 |
Run cmd2 if cmd1 succeeds |
| ` |
|
` |
; |
cmd1; cmd2 |
Run sequentially |
Shell Evaluation Strategy
#
- Simple command: fork + exec + wait
- Semicolon/And/Or: split, execute left, conditionally execute right
- Background: fork child, parent doesn’t wait immediately
- Redirect: fork, child modifies fd table (
close, dup), exec
- Pipe: fork twice, connect with pipe, each child execs
Redirect Implementation
#
int fd = open("output.txt", O_WRONLY);
close(1); // Close stdout
dup(fd); // fd becomes new stdout (fd 1)
close(fd);
execlp("cmd", "cmd", NULL);
Part 8: Threads and Concurrency (Week 14)
#
Threads vs Processes
#
| Aspect |
Process |
Thread |
| Memory |
Separate (copy on fork) |
Shared |
| Communication |
Pipes, mmap |
Direct access |
| Overhead |
Higher (copy) |
Lower |
| Data races |
None by default |
Possible on all shared data |
pthreads
#
pthread_t tid;
pthread_create(&tid, NULL, thread_func, arg);
pthread_join(tid, &result);
Data Races
#
- Multiple threads accessing same memory
- At least one write
- No synchronization
- Result: undefined behavior
Fixing Data Races
#
Option 1: Locks
sem_t lock;
sem_init(&lock, 0, 1);
sem_wait(&lock); // Acquire
sum += value; // Critical section
sem_post(&lock); // Release
Option 2: Local sums
// Each thread computes local sum
long local_sum = 0;
for (...) { local_sum += ...; }
return local_sum; // Thread returns result
// Main thread combines results
long total = 0;
for each thread:
pthread_join(tid, &result);
total += *(long*)result;
Deadlock
#
- Two threads, two locks
- Thread A: lock aa, then bb
- Thread B: lock bb, then aa
- Both wait for other - stuck forever
- Prevention: always lock in same order
Key Takeaways
#
- Memory matters: stack, heap, malloc/free, ownership
- System calls: the boundary between user code and kernel
- Processes: fork/exec/wait for concurrency, pipes for communication
- C++: safer abstractions over C (RAII, smart pointers, containers)
- Concurrency: threads share memory, need synchronization
For Further Study
#
- Operating Systems (how the kernel works)
- Compilers (parsing, ASTs, optimization)
- Distributed Systems (networking beyond TCP)
- Parallel Computing (locks, barriers, atomics)