<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Wesley Bertipaglia</title><description>Software engineer from Brazil, specialized in back-end development.</description><link>https://www.wesleybertipaglia.com/</link><category>Technology</category><category>Programming</category><category>Software Engineering</category><category>Science</category><item><title>Putting It Together: A Small C Program</title><link>https://www.wesleybertipaglia.com/blog/z5foj1ba/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/z5foj1ba/</guid><description>The capstone of the C Explained series: building one small, complete, tested C program that uses structs, dynamic memory, pointers, file I/O, and error handling.</description><pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The series covered each part of C on its own. This last post uses all of it at once to build a small, complete command-line program: it reads numbers from a file, keeps them in a dynamically growing array, and prints the count, sum, average, minimum, and maximum. It is the &lt;code&gt;wc&lt;/code&gt; of numbers, and along the way you will see every earlier topic earn its place.&lt;/p&gt;
&lt;h2&gt;What the program does&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ ./stats numbers.txt
count:   5
sum:     150.00
average: 30.00
min:     10.00
max:     50.00
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The design is split into a &lt;strong&gt;library&lt;/strong&gt; (&lt;code&gt;stats.c&lt;/code&gt; with a header &lt;code&gt;stats.h&lt;/code&gt;) and a thin &lt;strong&gt;main&lt;/strong&gt; (&lt;code&gt;main.c&lt;/code&gt;). The library holds all the logic and knows nothing about the terminal; &lt;code&gt;main&lt;/code&gt; handles the command line. That split is deliberate: it is what the testing post called keeping the pure middle away from the I/O edges, and it lets us test the logic without running the program.&lt;/p&gt;
&lt;h2&gt;The header: the contract&lt;/h2&gt;
&lt;p&gt;The header is where the functions post&amp;#39;s &amp;quot;prototype&amp;quot; idea lives for a whole file. It also defines the struct that holds the numbers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#ifndef STATS_H
#define STATS_H

#include &amp;lt;stddef.h&amp;gt;

typedef struct {
    double *values;      // heap array of the numbers
    size_t count;        // how many are stored
    size_t capacity;     // how many fit before growing
} Numbers;

int    numbers_init(Numbers *n, size_t capacity);
int    numbers_push(Numbers *n, double value);
void   numbers_free(Numbers *n);
int    numbers_read(Numbers *n, const char *path);

double numbers_average(const Numbers *n);
double numbers_min(const Numbers *n);
double numbers_max(const Numbers *n);

#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two things to notice. The &lt;code&gt;#ifndef&lt;/code&gt;/&lt;code&gt;#define&lt;/code&gt;/&lt;code&gt;#endif&lt;/code&gt; guard prevents the header from being included twice, which would otherwise re-declare the struct and fail to compile. And every function that can fail returns &lt;code&gt;int&lt;/code&gt;, with &lt;code&gt;0&lt;/code&gt; for success and &lt;code&gt;-1&lt;/code&gt; for failure, the convention from the error-handling post. The &lt;code&gt;Numbers&lt;/code&gt; struct is exactly the &amp;quot;group different fields under one name&amp;quot; idea from the structs post: a pointer to the array, plus how many slots are used and how many exist.&lt;/p&gt;
&lt;h2&gt;Growing memory&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;init&lt;/code&gt;, &lt;code&gt;push&lt;/code&gt;, and &lt;code&gt;free&lt;/code&gt; manage the heap array. Growth doubles capacity when full, so the total work stays linear instead of copying on every insert:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;quot;stats.h&amp;quot;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;

int numbers_init(Numbers *n, size_t capacity) {
    n-&amp;gt;values = malloc(capacity * sizeof(double));
    if (n-&amp;gt;values == NULL) {
        n-&amp;gt;count = 0;
        n-&amp;gt;capacity = 0;
        return -1;
    }
    n-&amp;gt;count = 0;
    n-&amp;gt;capacity = capacity;
    return 0;
}

int numbers_push(Numbers *n, double value) {
    if (n-&amp;gt;count == n-&amp;gt;capacity) {
        size_t next = n-&amp;gt;capacity == 0 ? 4 : n-&amp;gt;capacity * 2;
        double *grown = realloc(n-&amp;gt;values, next * sizeof(double));
        if (grown == NULL) {
            return -1;              // original block is still valid
        }
        n-&amp;gt;values = grown;
        n-&amp;gt;capacity = next;
    }
    n-&amp;gt;values[n-&amp;gt;count] = value;
    n-&amp;gt;count++;
    return 0;
}

void numbers_free(Numbers *n) {
    free(n-&amp;gt;values);
    n-&amp;gt;values = NULL;
    n-&amp;gt;count = 0;
    n-&amp;gt;capacity = 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every memory rule from the pointers post is here. &lt;code&gt;malloc&lt;/code&gt; counts bytes, so we multiply by &lt;code&gt;sizeof(double)&lt;/code&gt;. Every allocation is checked against &lt;code&gt;NULL&lt;/code&gt;. &lt;code&gt;realloc&lt;/code&gt; returns the new block (which may have moved) and must not overwrite the old pointer until it succeeds; that is why we store into &lt;code&gt;grown&lt;/code&gt; first. And &lt;code&gt;free&lt;/code&gt; sets the pointer back to &lt;code&gt;NULL&lt;/code&gt; so a later accidental use is a clean crash, not silent corruption. The struct is always passed as a pointer (&lt;code&gt;Numbers *n&lt;/code&gt;), because the functions must modify its fields and a by-value copy would edit a doomed duplicate.&lt;/p&gt;
&lt;h2&gt;Reading the file&lt;/h2&gt;
&lt;p&gt;Reading numbers from a file shows pointers, strings, loops, and error handling together:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int numbers_read(Numbers *n, const char *path) {
    FILE *f = fopen(path, &amp;quot;r&amp;quot;);
    if (f == NULL) {
        return -1;                  // caller decides how to report it
    }

    double value;
    while (fscanf(f, &amp;quot;%lf&amp;quot;, &amp;amp;value) == 1) {
        if (numbers_push(n, value) != 0) {
            fclose(f);              // free the resource before leaving
            return -1;
        }
    }

    fclose(f);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;fopen&lt;/code&gt; returns &lt;code&gt;NULL&lt;/code&gt; on failure; the &lt;code&gt;&amp;amp;value&lt;/code&gt; passed to &lt;code&gt;fscanf&lt;/code&gt; is the address-of operator, because the function writes through it. The loop condition is a &lt;code&gt;fscanf&lt;/code&gt; call: it returns &lt;code&gt;1&lt;/code&gt; for each successfully read number and stops at end of file, which is a compact &amp;quot;read until there is nothing left&amp;quot; pattern. The &lt;code&gt;const char *path&lt;/code&gt; parameter means &amp;quot;I read this string, I never modify it&amp;quot;, matching the string post. On the one failure path, the open file is closed before returning, so nothing leaks.&lt;/p&gt;
&lt;h2&gt;The statistics functions&lt;/h2&gt;
&lt;p&gt;These are the pure functions, taking &lt;code&gt;const Numbers *&lt;/code&gt; and returning a double:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;double numbers_average(const Numbers *n) {
    if (n-&amp;gt;count == 0) {
        return 0.0;
    }
    double total = 0.0;
    for (size_t i = 0; i &amp;lt; n-&amp;gt;count; i++) {
        total += n-&amp;gt;values[i];
    }
    return total / (double)n-&amp;gt;count;
}

double numbers_min(const Numbers *n) {
    if (n-&amp;gt;count == 0) return 0.0;
    double best = n-&amp;gt;values[0];
    for (size_t i = 1; i &amp;lt; n-&amp;gt;count; i++) {
        if (n-&amp;gt;values[i] &amp;lt; best) best = n-&amp;gt;values[i];
    }
    return best;
}

double numbers_max(const Numbers *n) {
    if (n-&amp;gt;count == 0) return 0.0;
    double best = n-&amp;gt;values[0];
    for (size_t i = 1; i &amp;lt; n-&amp;gt;count; i++) {
        if (n-&amp;gt;values[i] &amp;gt; best) best = n-&amp;gt;values[i];
    }
    return best;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They take a pointer so the whole array is not copied, and &lt;code&gt;const&lt;/code&gt; so a bug inside cannot modify it. The empty-input guard is the defensive habit from the error-handling post: handle the boundary case instead of dividing by zero.&lt;/p&gt;
&lt;h2&gt;The main function&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; owns the command-line arguments, wires the pieces, and reports errors to &lt;code&gt;stderr&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;quot;stats.h&amp;quot;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;

int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, &amp;quot;usage: %s &amp;lt;file-with-numbers&amp;gt;\n&amp;quot;, argv[0]);
        return EXIT_FAILURE;
    }

    Numbers numbers;
    if (numbers_init(&amp;amp;numbers, 8) != 0) {
        fprintf(stderr, &amp;quot;out of memory\n&amp;quot;);
        return EXIT_FAILURE;
    }

    if (numbers_read(&amp;amp;numbers, argv[1]) != 0) {
        fprintf(stderr, &amp;quot;could not read %s\n&amp;quot;, argv[1]);
        numbers_free(&amp;amp;numbers);
        return EXIT_FAILURE;
    }

    printf(&amp;quot;count:   %zu\n&amp;quot;, numbers.count);
    printf(&amp;quot;sum:     %.2f\n&amp;quot;, numbers_average(&amp;amp;numbers) * numbers.count);
    printf(&amp;quot;average: %.2f\n&amp;quot;, numbers_average(&amp;amp;numbers));
    printf(&amp;quot;min:     %.2f\n&amp;quot;, numbers_min(&amp;amp;numbers));
    printf(&amp;quot;max:     %.2f\n&amp;quot;, numbers_max(&amp;amp;numbers));

    numbers_free(&amp;amp;numbers);
    return EXIT_SUCCESS;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;argc&lt;/code&gt;/&lt;code&gt;argv&lt;/code&gt; are how every C program reads its command line: &lt;code&gt;argc&lt;/code&gt; is the count, and &lt;code&gt;argv&lt;/code&gt; is an array of strings, with &lt;code&gt;argv[0]&lt;/code&gt; the program name. The first check rejects the wrong number of arguments. Then memory is allocated, the file is read, results are printed, and the memory is freed on the way out. If reading fails, the program frees before returning, the same cleanup-first habit from the error post.&lt;/p&gt;
&lt;h2&gt;Build it&lt;/h2&gt;
&lt;p&gt;Compile all the &lt;code&gt;.c&lt;/code&gt; files together and link the math library:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;gcc -Wall -Wextra -g -fsanitize=address,undefined -o stats stats.c main.c -lm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With a &lt;code&gt;numbers.txt&lt;/code&gt; containing &lt;code&gt;10 20 30 40 50&lt;/code&gt;, running &lt;code&gt;./stats numbers.txt&lt;/code&gt; prints the block at the top of this post. &lt;code&gt;-Wall -Wextra&lt;/code&gt; raise the warnings, and the sanitizers turn any memory mistake into a named error.&lt;/p&gt;
&lt;h2&gt;Test it&lt;/h2&gt;
&lt;p&gt;The header from the library is what makes testing possible: the test file includes &lt;code&gt;stats.h&lt;/code&gt; and calls the same functions, no &lt;code&gt;main&lt;/code&gt; needed from the program:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;quot;stats.h&amp;quot;
#include &amp;lt;math.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;

static int failures = 0;

#define CHECK(expr)                                                        \
    do {                                                                   \
        if (!(expr)) {                                                     \
            printf(&amp;quot;FAIL %s:%d: %s\n&amp;quot;, __FILE__, __LINE__, #expr);         \
            failures++;                                                    \
        }                                                                  \
    } while (0)

static int approx(double a, double b) {
    return fabs(a - b) &amp;lt; 1e-9;
}

int main(void) {
    Numbers n;
    CHECK(numbers_init(&amp;amp;n, 2) == 0);

    CHECK(numbers_push(&amp;amp;n, 10.0) == 0);
    CHECK(numbers_push(&amp;amp;n, 20.0) == 0);
    CHECK(numbers_push(&amp;amp;n, 30.0) == 0);        // forces a realloc
    CHECK(n.count == 3);

    CHECK(approx(numbers_average(&amp;amp;n), 20.0));
    CHECK(approx(numbers_min(&amp;amp;n), 10.0));
    CHECK(approx(numbers_max(&amp;amp;n), 30.0));

    numbers_free(&amp;amp;n);
    CHECK(n.values == NULL);

    Numbers empty;
    CHECK(numbers_init(&amp;amp;empty, 4) == 0);
    CHECK(approx(numbers_average(&amp;amp;empty), 0.0));   // boundary case
    numbers_free(&amp;amp;empty);

    if (failures &amp;gt; 0) {
        printf(&amp;quot;%d test(s) failed\n&amp;quot;, failures);
        return EXIT_FAILURE;
    }
    printf(&amp;quot;all tests passed\n&amp;quot;);
    return EXIT_SUCCESS;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;gcc -Wall -Wextra -g -fsanitize=address,undefined -o test_stats stats.c test_stats.c -lm
./test_stats
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the test runs under AddressSanitizer, the realloc path is verified for leaks and invalid access at the same time the results are checked. Every memory rule from the whole series is enforced by the tooling.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;This tiny program is the series in miniature. The &lt;code&gt;Numbers&lt;/code&gt; struct groups fields and is passed by pointer so it can change. &lt;code&gt;malloc&lt;/code&gt;/&lt;code&gt;realloc&lt;/code&gt;/&lt;code&gt;free&lt;/code&gt; manage a runtime-sized array. Strings reach the file functions as &lt;code&gt;const char *&lt;/code&gt;. Loops and conditionals do the work; functions package it into named, testable units; error codes and &lt;code&gt;if (f == NULL)&lt;/code&gt; handle the failures; sanitizers and a &lt;code&gt;CHECK&lt;/code&gt; harness prove it correct. Nothing here is larger than the parts you already learned.&lt;/p&gt;
&lt;p&gt;That is C[^1]: a small language whose pieces connect directly to the machine. You now have the whole toolkit, from &lt;code&gt;int main(void)&lt;/code&gt; to manual memory and tests. The reward is the one property no other language hands you for free: you always know what your program is doing.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/C_(programming_language)&quot;&gt;C (programming language) - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>project</category><category>how-to</category></item><item><title>Testing C Code</title><link>https://www.wesleybertipaglia.com/blog/9jz4zhcl/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/9jz4zhcl/</guid><description>How to test C with no framework required: a tiny assert-based harness, table-driven tests, compile-time checks, and the sanitizers and tools that catch what tests miss.</description><pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;C gives you no test runner, no test annotations, and no package manager. What it gives you is a language you already understand and an &lt;code&gt;exit&lt;/code&gt; code. That is enough to build a real test suite, and understanding the bare mechanism is the fastest way to use the frameworks later. This post builds a small harness from scratch, then shows the tools professionals add on top.&lt;/p&gt;
&lt;h2&gt;The idea of a unit test&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;unit test&lt;/strong&gt;[^1] checks one piece of behavior in isolation: build a function with known inputs, compare the result to what you expect, and fail loudly if they differ. Tests live in their own &lt;code&gt;.c&lt;/code&gt; file, call your functions, and report pass or fail. The main function returns &lt;code&gt;0&lt;/code&gt; only if everything passed, which is exactly what continuous integration and &lt;code&gt;make test&lt;/code&gt; expect.&lt;/p&gt;
&lt;h2&gt;A tiny harness, no dependencies&lt;/h2&gt;
&lt;p&gt;Start with a function to test, in &lt;code&gt;math_utils.c&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int add(int a, int b) {
    return a + b;
}

double average(const int *values, int n) {
    int total = 0;
    for (int i = 0; i &amp;lt; n; i++) {
        total += values[i];
    }
    return (double)total / n;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now a test file, &lt;code&gt;test_math_utils.c&lt;/code&gt;, that counts failures:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;math.h&amp;gt;

int add(int a, int b);
double average(const int *values, int n);

static int failures = 0;

#define CHECK(expr)                                        \
    do {                                                   \
        if (!(expr)) {                                     \
            printf(&amp;quot;FAIL %s:%d: %s\n&amp;quot;, __FILE__, __LINE__, #expr); \
            failures++;                                    \
        } else {                                           \
            printf(&amp;quot;ok   %s\n&amp;quot;, #expr);                    \
        }                                                  \
    } while (0)

int main(void) {
    CHECK(add(2, 3) == 5);
    CHECK(add(-1, 1) == 0);
    CHECK(add(0, 0) == 0);

    int values[] = {2, 4, 6};
    CHECK(fabs(average(values, 3) - 4.0) &amp;lt; 1e-9);

    if (failures &amp;gt; 0) {
        printf(&amp;quot;\n%d test(s) failed\n&amp;quot;, failures);
        return EXIT_FAILURE;
    }
    printf(&amp;quot;\nall tests passed\n&amp;quot;);
    return EXIT_SUCCESS;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;CHECK&lt;/code&gt; macro is the whole framework: it evaluates an expression, prints &lt;code&gt;ok&lt;/code&gt; or &lt;code&gt;FAIL&lt;/code&gt; with the file and line, and increments a counter. &lt;code&gt;#expr&lt;/code&gt; turns the expression into its own text so the output explains itself. Running &lt;code&gt;main&lt;/code&gt; returns a non-zero exit code when anything failed, which is all a build pipeline needs.&lt;/p&gt;
&lt;p&gt;Compile and run both files together:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;gcc -Wall -Wextra -o test_math math_utils.c test_math_utils.c -lm
./test_math
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note &lt;code&gt;-lm&lt;/code&gt;: the math library (&lt;code&gt;fabs&lt;/code&gt;) needs to be linked explicitly on many systems. Comparing floats with a tolerance (&lt;code&gt;&amp;lt; 1e-9&lt;/code&gt;) instead of &lt;code&gt;==&lt;/code&gt; follows the rule from the data types post: decimal arithmetic is never exact.&lt;/p&gt;
&lt;h2&gt;Table-driven tests&lt;/h2&gt;
&lt;p&gt;When a function has many input/output pairs, write them as data and loop. This keeps one piece of test logic and dozens of cases:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct {
    int a;
    int b;
    int expected;
} AddCase;

int main(void) {
    AddCase cases[] = {
        {2, 3, 5},
        {-1, 1, 0},
        {0, 0, 0},
        {100, 200, 300},
    };

    for (int i = 0; i &amp;lt; 4; i++) {
        int got = add(cases[i].a, cases[i].b);
        if (got != cases[i].expected) {
            printf(&amp;quot;FAIL add(%d, %d): expected %d, got %d\n&amp;quot;,
                   cases[i].a, cases[i].b, cases[i].expected, got);
            return EXIT_FAILURE;
        }
    }
    printf(&amp;quot;all %d cases passed\n&amp;quot;, 4);
    return EXIT_SUCCESS;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding a case is one line, and structs plus arrays (from the earlier posts) pay off immediately here.&lt;/p&gt;
&lt;h2&gt;Test what is easy to test&lt;/h2&gt;
&lt;p&gt;The functions above are &lt;strong&gt;pure&lt;/strong&gt;: same inputs, same output, no side effects. They are trivial to test. Functions that print, read input, allocate, or touch files are harder, so experienced C programmers isolate the pure logic from the I/O:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// hard to test: mixes parsing and printing
void print_upper(const char *s);

// easy to test
int to_upper(char *dst, const char *src, size_t cap);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Push &lt;code&gt;printf&lt;/code&gt;, &lt;code&gt;scanf&lt;/code&gt;, and &lt;code&gt;fopen&lt;/code&gt; to the edges of the program; keep the middle in pure functions that take values and return values. This is the single biggest factor in whether a C project is testable.&lt;/p&gt;
&lt;h2&gt;Catching memory bugs with sanitizers&lt;/h2&gt;
&lt;p&gt;Tests prove behavior; &lt;strong&gt;sanitizers&lt;/strong&gt; find undefined behavior. Compile the test binary with the address and undefined-behavior sanitizers and the tool runtime checks every memory access and arithmetic operation, reporting file and line on failure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;gcc -Wall -Wextra -g -fsanitize=address,undefined \
    -o test_math math_utils.c test_math_utils.c -lm
./test_math
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AddressSanitizer[^2] catches buffer overflows, use-after-free, and double frees; UndefinedBehaviorSanitizer catches integer overflow, invalid shifts, and null dereferences. Run the whole test suite under them in CI; they turn silent corruption into a named error. For leaks specifically, &lt;code&gt;valgrind&lt;/code&gt; is the classic tool:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;valgrind --leak-check=full ./test_math
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Neither is a substitute for tests, and tests are not a substitute for them. Use both.&lt;/p&gt;
&lt;h2&gt;A real framework, when the harness grows&lt;/h2&gt;
&lt;p&gt;The hand-rolled &lt;code&gt;CHECK&lt;/code&gt; harness is fine for a small project. When you want test discovery, setup/teardown, and nicer reports, the lightweight C frameworks all follow the same shape:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unity&lt;/strong&gt; - tiny, popular in embedded, one &lt;code&gt;.c&lt;/code&gt; and one &lt;code&gt;.h&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CMocka&lt;/strong&gt; - supports mocks and setup/teardown.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Criterion&lt;/strong&gt; - compiles tests into separate processes, so a crash in one test does not kill the run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check&lt;/strong&gt; - the older, unit-test classic.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because they are just C libraries, you add the files, link them into your test binary, and run it. Nothing about your understanding of the harness changes; the framework only saves typing.&lt;/p&gt;
&lt;h2&gt;One test, one behavior&lt;/h2&gt;
&lt;p&gt;A few habits make the difference between a suite you trust and one you ignore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Test the boundaries: zero, one, negative, the maximum, and the empty case.&lt;/li&gt;
&lt;li&gt;Test the error paths too, not only the happy path. Feed &lt;code&gt;malloc&lt;/code&gt; a size it can refuse, pass a &lt;code&gt;NULL&lt;/code&gt; pointer, an out-of-range value.&lt;/li&gt;
&lt;li&gt;Keep tests fast and independent. A test that depends on another test&amp;#39;s leftovers is worse than no test.&lt;/li&gt;
&lt;li&gt;Run the tests automatically on every change. A test you have to remember to run is a test you will forget.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Testing C needs no magic: a separate test file, a &lt;code&gt;CHECK&lt;/code&gt; macro, a failure counter, and an exit code. Table-driven cases keep many inputs cheap; pure functions keep testing easy; sanitizers and &lt;code&gt;valgrind&lt;/code&gt; catch the memory crimes that tests cannot see; and real frameworks only automate this same loop. In the final post we put the whole series together and build one small program from scratch, tests included.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Unit_testing&quot;&gt;Unit testing - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/AddressSanitizer&quot;&gt;AddressSanitizer - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>testing</category><category>how-to</category></item><item><title>Error Handling in C</title><link>https://www.wesleybertipaglia.com/blog/dqwh44fk/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/dqwh44fk/</guid><description>C has no exceptions, so errors travel by return value: status codes, errno and perror, assert, exit, setjmp/longjmp, and cleaning up when things go wrong.</description><pubDate>Sun, 11 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most modern languages throw &lt;strong&gt;exceptions&lt;/strong&gt;: an error interrupts the normal flow and a handler far away catches it. C has no exceptions at all. Instead, every function that can fail tells you so through its return value, and &lt;em&gt;you&lt;/em&gt; decide whether and how to handle it. That sounds primitive, and it is, but it is also why C code can be reasoned about precisely: nothing jumps out of nowhere.&lt;/p&gt;
&lt;p&gt;The golden rule of C error handling[^1]: &lt;strong&gt;check the return value of every function that can fail, and handle it immediately or pass it up.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Error codes as return values&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;main&lt;/code&gt; function already showed the idea: &lt;code&gt;return 0&lt;/code&gt; means success and a non-zero value means failure, and that convention propagates everywhere. Many functions return an &lt;code&gt;int&lt;/code&gt; status where &lt;code&gt;0&lt;/code&gt; is success and negative values name the specific failure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef enum {
    OK = 0,
    ERR_OPEN,
    ERR_READ,
    ERR_NOMEM
} Status;

Status load(const char *path) {
    FILE *f = fopen(path, &amp;quot;r&amp;quot;);
    if (f == NULL) {
        return ERR_OPEN;
    }
    // ... use f ...
    fclose(f);
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Callers test the result and decide:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Status s = load(&amp;quot;data.txt&amp;quot;);
if (s != OK) {
    fprintf(stderr, &amp;quot;load failed with code %d\n&amp;quot;, s);
    return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two conventions to recognize in the wild: return &lt;code&gt;0&lt;/code&gt; for success (borrowed from Unix) and &lt;code&gt;-1&lt;/code&gt;/&lt;code&gt;NULL&lt;/code&gt; for failure; or return a boolean-like &lt;code&gt;1&lt;/code&gt;/&lt;code&gt;0&lt;/code&gt;. The enum version above scales best because each error has a name.&lt;/p&gt;
&lt;h2&gt;Pointers as failure signals&lt;/h2&gt;
&lt;p&gt;Functions that return a pointer signal failure with &lt;code&gt;NULL&lt;/code&gt;. &lt;code&gt;fopen&lt;/code&gt;, &lt;code&gt;malloc&lt;/code&gt;, and &lt;code&gt;strchr&lt;/code&gt; all do this, which is why the pointers post insisted on the &lt;code&gt;!= NULL&lt;/code&gt; check. &lt;code&gt;NULL&lt;/code&gt; is the closest thing C has to &amp;quot;no result&amp;quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;FILE *f = fopen(&amp;quot;notes.txt&amp;quot;, &amp;quot;r&amp;quot;);
if (f == NULL) {
    perror(&amp;quot;fopen&amp;quot;);
    return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;errno, perror, and strerror&lt;/h2&gt;
&lt;p&gt;When a system function like &lt;code&gt;fopen&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, or &lt;code&gt;malloc&lt;/code&gt; fails, it also records &lt;em&gt;why&lt;/em&gt; in the global &lt;code&gt;errno&lt;/code&gt; variable, declared in &lt;code&gt;&amp;lt;errno.h&amp;gt;&lt;/code&gt;. Two helpers turn that number into a message:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;perror(&amp;quot;fopen&amp;quot;)&lt;/code&gt; prints &lt;code&gt;fopen: &amp;lt;message&amp;gt;&lt;/code&gt; to stderr, based on the current &lt;code&gt;errno&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strerror(errno)&lt;/code&gt; returns the message string so you can format it yourself.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;errno.h&amp;gt;
#include &amp;lt;string.h&amp;gt;

FILE *f = fopen(&amp;quot;missing.txt&amp;quot;, &amp;quot;r&amp;quot;);
if (f == NULL) {
    fprintf(stderr, &amp;quot;could not open file: %s\n&amp;quot;, strerror(errno));
    return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Print errors to &lt;strong&gt;stderr&lt;/strong&gt;, not stdout. &lt;code&gt;stderr&lt;/code&gt; is the standard error stream, and printing there keeps diagnostics out of the program&amp;#39;s normal output (so they still appear when output is redirected to a file). &lt;code&gt;errno&lt;/code&gt; is set only when a function documents that it sets it, and only on failure, so set it to &lt;code&gt;0&lt;/code&gt; before a call if you want to be sure the value you see came from that call.&lt;/p&gt;
&lt;h2&gt;assert: bugs vs errors&lt;/h2&gt;
&lt;p&gt;Some conditions are programming mistakes that should never happen, like &lt;code&gt;malloc&lt;/code&gt; returning &lt;code&gt;NULL&lt;/code&gt; in a teaching program or an index going negative. &lt;code&gt;assert&lt;/code&gt; from &lt;code&gt;&amp;lt;assert.h&amp;gt;&lt;/code&gt; checks such &amp;quot;this must be true&amp;quot; claims and aborts loudly if they are false:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;assert.h&amp;gt;

int divide(int a, int b) {
    assert(b != 0);   // a caller bug, not a runtime user error
    return a / b;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the assertion fails, the program prints the file and line and stops. Assertions are for &lt;strong&gt;invariants&lt;/strong&gt; that separate a broken program from valid input. They are compiled out when you define &lt;code&gt;NDEBUG&lt;/code&gt;, so never put code with side effects inside &lt;code&gt;assert&lt;/code&gt;, and never use them for things a user can trigger (like bad input) because not all builds check them.&lt;/p&gt;
&lt;h2&gt;exit and atexit&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;exit(status)&lt;/code&gt; ends the whole program immediately, running cleanup handlers registered with &lt;code&gt;atexit&lt;/code&gt; on the way out:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;stdlib.h&amp;gt;

FILE *f = fopen(&amp;quot;out.txt&amp;quot;, &amp;quot;w&amp;quot;);
if (f == NULL) {
    fprintf(stderr, &amp;quot;cannot write output\n&amp;quot;);
    exit(EXIT_FAILURE);   // 1
}
// ...
exit(EXIT_SUCCESS);        // 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;EXIT_SUCCESS&lt;/code&gt; and &lt;code&gt;EXIT_FAILURE&lt;/code&gt; are the portable values for &lt;code&gt;return 0&lt;/code&gt; and &lt;code&gt;return 1&lt;/code&gt;. Prefer returning from &lt;code&gt;main&lt;/code&gt; when you can, and reach for &lt;code&gt;exit&lt;/code&gt; only when the error is unrecoverable deep inside nested calls. &lt;code&gt;atexit(fn)&lt;/code&gt; registers a function to run when the program exits, useful for flushing files or freeing global resources.&lt;/p&gt;
&lt;h2&gt;setjmp and longjmp&lt;/h2&gt;
&lt;p&gt;C does have a low-level escape hatch that mimics a non-local jump. &lt;code&gt;setjmp&lt;/code&gt; saves the current point, and &lt;code&gt;longjmp&lt;/code&gt; jumps back to it from anywhere, which some libraries use to simulate exceptions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;setjmp.h&amp;gt;

jmp_buf env;

void deep(void) {
    longjmp(env, 1);   // jump back to where setjmp was called
}

int main(void) {
    if (setjmp(env) == 0) {
        deep();
    } else {
        printf(&amp;quot;recovered\n&amp;quot;);
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reach for it only when you understand it fully: &lt;code&gt;longjmp&lt;/code&gt; skips stack cleanup, so any local automatic variables and unfinished cleanup in the frames it skips are lost, and resources leak. It is powerful and sharp; most code should not use it.&lt;/p&gt;
&lt;h2&gt;Cleaning up: the goto idiom&lt;/h2&gt;
&lt;p&gt;Because you must free and close things by hand, code that grows progressively and then fails leaves a trail of resources to release. The idiomatic C solution is a single cleanup path at the bottom and &lt;code&gt;goto&lt;/code&gt; to reach it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int process(const char *path) {
    int result = -1;
    FILE *f = NULL;
    char *buffer = NULL;

    f = fopen(path, &amp;quot;r&amp;quot;);
    if (f == NULL) {
        goto cleanup;
    }

    buffer = malloc(1024);
    if (buffer == NULL) {
        goto cleanup;
    }

    // ... read and process ...

    result = 0;

cleanup:
    free(buffer);
    if (f != NULL) {
        fclose(f);
    }
    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each failure jumps to the one place that releases everything; &lt;code&gt;free(NULL)&lt;/code&gt; and skipping the close when &lt;code&gt;f&lt;/code&gt; is &lt;code&gt;NULL&lt;/code&gt; are both safe, so the cleanup path needs no error tracking of its own. This backward &lt;code&gt;goto&lt;/code&gt; is one of the few uses the C community universally approves.&lt;/p&gt;
&lt;h2&gt;Defensive programming&lt;/h2&gt;
&lt;p&gt;The mindset matters more than any single function: assume input is hostile, assume calls can fail, and handle the first error at the point it happens. Validate arguments at the top of a function, check every allocation, close every file, and never ignore a return value. Compile with &lt;code&gt;-Wall -Wextra&lt;/code&gt; and fix warnings, because the compiler catches a surprising number of error-handling mistakes, such as a return value that is discarded.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;C turns error handling into explicit control flow. Functions return status codes or &lt;code&gt;NULL&lt;/code&gt;; &lt;code&gt;errno&lt;/code&gt;/&lt;code&gt;perror&lt;/code&gt;/&lt;code&gt;strerror&lt;/code&gt; explain system failures; &lt;code&gt;assert&lt;/code&gt; catches programmer bugs while &lt;code&gt;exit&lt;/code&gt; handles the unrecoverable; &lt;code&gt;setjmp&lt;/code&gt;/&lt;code&gt;longjmp&lt;/code&gt; exist but are rarely the right tool; and a shared &lt;code&gt;cleanup:&lt;/code&gt; path with &lt;code&gt;goto&lt;/code&gt; keeps resources from leaking on failure. Every one of these choices can be wrong, which is exactly why the next post exists: testing, so you find the failures before your users do.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/C_%28programming_language%29#Error_handling&quot;&gt;C error handling - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>error-handling</category><category>how-to</category></item><item><title>Pointers in C</title><link>https://www.wesleybertipaglia.com/blog/lyssjdvh/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/lyssjdvh/</guid><description>Every variable has an address: what pointers are, the address and dereference operators, pointer arithmetic, pointers to arrays and functions, and dynamic memory with malloc and free.</description><pubDate>Sat, 10 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Throughout the series a variable was described as &amp;quot;a named box in memory&amp;quot;. A &lt;strong&gt;pointer&lt;/strong&gt; is the C feature that deals with those boxes directly: it is a variable that stores the &lt;em&gt;address&lt;/em&gt; of another variable. Pointers are why you can write &lt;code&gt;scanf(&amp;quot;%d&amp;quot;, &amp;amp;age)&lt;/code&gt; and why arrays seem to turn into pointers when passed to functions. They are also, honestly, the hardest mental step in C, and that is mostly because programmers are not told what they are for. This post takes it slowly: what an address is, what pointers do, and the two places they earn their keep (changing caller variables, and dynamic memory).&lt;/p&gt;
&lt;h2&gt;Addresses&lt;/h2&gt;
&lt;p&gt;Every variable lives at a memory address, like a house on a street. The address is just a number. The &lt;code&gt;&amp;amp;&lt;/code&gt; operator (address-of) gives you a variable&amp;#39;s address:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int x = 42;
printf(&amp;quot;%p\n&amp;quot;, (void *)&amp;amp;x);   // prints something like 0x7ffee4a3b8c0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;%p&lt;/code&gt; is the format specifier for an address. The address is not random: it is where &lt;code&gt;x&lt;/code&gt; sits in memory, decided by the system at runtime. Notice the &lt;code&gt;&amp;amp;x&lt;/code&gt; in &lt;code&gt;scanf&lt;/code&gt;: that is why &lt;code&gt;scanf(&amp;quot;%d&amp;quot;, &amp;amp;age)&lt;/code&gt; works. The function must write to your &lt;code&gt;age&lt;/code&gt; box, so you hand it the box&amp;#39;s address instead of a copy of its value.&lt;/p&gt;
&lt;h2&gt;Declaring and using pointers&lt;/h2&gt;
&lt;p&gt;A pointer variable is declared with &lt;code&gt;*&lt;/code&gt; after the base type; it declares that the variable &lt;em&gt;holds an address of&lt;/em&gt; something of that type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int x = 42;
int *p = &amp;amp;x;    // p holds the address of x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read &lt;code&gt;int *p&lt;/code&gt; as &amp;quot;&lt;code&gt;p&lt;/code&gt; is a pointer to an &lt;code&gt;int&lt;/code&gt;&amp;quot;. The address is stored, but to reach the value &lt;em&gt;through&lt;/em&gt; the address you dereference with &lt;code&gt;*&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;printf(&amp;quot;%d\n&amp;quot;, *p);   // 42: the int that p points to
*p = 99;              // writes THROUGH p, changing x
printf(&amp;quot;%d\n&amp;quot;, x);    // 99
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Spelling matters: in a declaration, &lt;code&gt;int *p&lt;/code&gt; declares a pointer; in an expression, &lt;code&gt;*p&lt;/code&gt; means &amp;quot;the thing p points to&amp;quot;. The &lt;code&gt;&amp;amp;&lt;/code&gt; and &lt;code&gt;*&lt;/code&gt; are inverse operations: &lt;code&gt;&amp;amp;&lt;/code&gt; produces an address from a variable, &lt;code&gt;*&lt;/code&gt; produces the variable from an address, and applying one after the other gets you back where you started.&lt;/p&gt;
&lt;h2&gt;NULL: the empty pointer&lt;/h2&gt;
&lt;p&gt;Every pointer should either point at something real or at the special value &lt;code&gt;NULL&lt;/code&gt;. Dereferencing &lt;code&gt;NULL&lt;/code&gt; crashes your program, so check before use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int *p = NULL;
if (p != NULL) {
    printf(&amp;quot;%d\n&amp;quot;, *p);   // only runs if p is real
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;NULL&lt;/code&gt; exists so pointers can say &amp;quot;nothing here&amp;quot; instead of holding a garbage address. The check &lt;code&gt;p != NULL&lt;/code&gt; (or the shorthand &lt;code&gt;if (p)&lt;/code&gt; when treating presence as truth) is the single most common defensive pattern in C, and it is where the error-handling post takes over later.&lt;/p&gt;
&lt;h2&gt;Pointers as function parameters&lt;/h2&gt;
&lt;p&gt;Recall from the functions post that C is pass-by-value: a function gets a copy and cannot change the caller&amp;#39;s variable. Pass a pointer and it can, because a copy of an address still names the original box:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

int main(void) {
    int x = 1, y = 2;
    swap(&amp;amp;x, &amp;amp;y);
    printf(&amp;quot;%d %d\n&amp;quot;, x, y);   // 2 1
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;swap&lt;/code&gt; never touches &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; by name; it writes through their addresses. Any function that must return a modified argument uses this pattern. It is the same reason &lt;code&gt;scanf(&amp;quot;%d&amp;quot;, &amp;amp;age)&lt;/code&gt; asks for the address: it needs to write into your variable, not into a copy.&lt;/p&gt;
&lt;h2&gt;Pointers and arrays&lt;/h2&gt;
&lt;p&gt;C arrays and pointers are close relatives. The name of an array, used in an expression, is a pointer to its first element:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int scores[] = {92, 85, 88};
int *p = scores;        // p points to scores[0]
printf(&amp;quot;%d\n&amp;quot;, *p);     // 92
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is why the arrays post warned that passing an array to a function passes a pointer, and why &lt;code&gt;sizeof&lt;/code&gt; inside the function fails: the compiler only sees a pointer there. Array indexing is defined in terms of pointers: &lt;code&gt;scores[i]&lt;/code&gt; is just syntax for &amp;quot;the element at address &lt;code&gt;scores + i&lt;/code&gt;&amp;quot;.&lt;/p&gt;
&lt;h3&gt;Pointer arithmetic&lt;/h3&gt;
&lt;p&gt;Adding an integer to a pointer moves by whole elements, not bytes. &lt;code&gt;p + 1&lt;/code&gt; points to the next element of the array:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;printf(&amp;quot;%d\n&amp;quot;, *(p + 1));   // 85
printf(&amp;quot;%d\n&amp;quot;, p[1]);       // same thing: 85
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;p[k]&lt;/code&gt; and &lt;code&gt;*(p + k)&lt;/code&gt; are interchangeable. This is the whole secret behind array indexing, and it means you can walk an array with a pointer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int total = 0;
int *p = scores;
for (int i = 0; i &amp;lt; 3; i++) {
    total += *p;   // read current element
    p++;           // move to the next one
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Strings rely on the same idea: &lt;code&gt;strlen&lt;/code&gt; is just &amp;quot;count characters while the current byte is not &lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt;&amp;quot;. A &lt;code&gt;char *&lt;/code&gt; pointing into a string and stepping one byte at a time until zero is the loop behind every string function.&lt;/p&gt;
&lt;h2&gt;Pointers to structs&lt;/h2&gt;
&lt;p&gt;The structs post showed &lt;code&gt;-&amp;gt;&lt;/code&gt;, and now it makes sense. If &lt;code&gt;s&lt;/code&gt; is a pointer to a struct, &lt;code&gt;s-&amp;gt;field&lt;/code&gt; is &lt;code&gt;(*s).field&lt;/code&gt;; both dereference and pick the field:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct { int x; int y; } Point;

void move(Point *p, int dx, int dy) {
    p-&amp;gt;x += dx;
    p-&amp;gt;y += dy;
}

int main(void) {
    Point p = {1, 1};
    move(&amp;amp;p, 3, 2);
    printf(&amp;quot;(%d, %d)\n&amp;quot;, p.x, p.y);   // (4, 3)
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Pointer to pointer and const pointers&lt;/h2&gt;
&lt;p&gt;Pointers are variables, so they too have addresses, producing a pointer to a pointer: &lt;code&gt;int **&lt;/code&gt; (&amp;quot;pointer to pointer to int&amp;quot;). You rarely need one early on, but you will meet it when a function must return a pointer through a parameter, like the allocator functions below.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const&lt;/code&gt; in front of a pointer means &amp;quot;the data it points at cannot change through this pointer&amp;quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void print(const int *p) {
    // *p = 1;   // error: read-only
    printf(&amp;quot;%d\n&amp;quot;, *p);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Putting &lt;code&gt;const&lt;/code&gt; after the star means the pointer itself cannot be reassigned: &lt;code&gt;int *const p&lt;/code&gt;. The first form, &lt;code&gt;const int *&lt;/code&gt;, is the common one for function parameters, and it is how the string helpers say &amp;quot;I will read this, never modify it&amp;quot;. The string literals you pass have type &lt;code&gt;const char *&lt;/code&gt;, which is why &lt;code&gt;printf(&amp;quot;...&amp;quot;, ...)&lt;/code&gt; and friends expect &lt;code&gt;const char *&lt;/code&gt; parameters.&lt;/p&gt;
&lt;h2&gt;Function pointers&lt;/h2&gt;
&lt;p&gt;Because functions live in memory too, you can store a pointer to a function and call through it. The syntax is ugly and worth learning by pattern, not by heart:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }

int apply(int (*op)(int, int), int a, int b) {
    return op(a, b);
}

int main(void) {
    printf(&amp;quot;%d\n&amp;quot;, apply(add, 3, 4));   // 7
    printf(&amp;quot;%d\n&amp;quot;, apply(mul, 3, 4));   // 12
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;int (*op)(int, int)&lt;/code&gt; declares &lt;code&gt;op&lt;/code&gt;: a pointer to a function taking two ints and returning an int. Passing a function selected at runtime is how you write callbacks and tables of operations. If you see &lt;code&gt;typedef int (*Operation)(int, int);&lt;/code&gt; in real code, it is this idea given a readable name.&lt;/p&gt;
&lt;h2&gt;Dynamic memory&lt;/h2&gt;
&lt;p&gt;Everything so far lived on the stack, sized at compile time. Real programs need memory whose size is only known at runtime, or that outlives the current function. That memory comes from the &lt;strong&gt;heap&lt;/strong&gt;, and C hands it out with &lt;code&gt;malloc&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int *p = malloc(10 * sizeof(int));   // 10 ints, on the heap
if (p == NULL) {
    // the request failed; handle it
}
for (int i = 0; i &amp;lt; 10; i++) {
    p[i] = i * i;
}
free(p);   // give the memory back
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The three function names to know, all from &lt;code&gt;&amp;lt;stdlib.h&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;malloc(n)&lt;/code&gt; - returns a pointer to &lt;code&gt;n&lt;/code&gt; bytes of uninitialized memory.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;calloc(count, size)&lt;/code&gt; - like &lt;code&gt;malloc&lt;/code&gt; but zero-initialized, and takes count and element size separately.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;realloc(p, newSize)&lt;/code&gt; - grows or shrinks a previously allocated block, moving it if needed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;free(p)&lt;/code&gt; - returns the block to the system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;malloc&lt;/code&gt; returns &lt;code&gt;void *&lt;/code&gt;, a pointer with no element type; C converts it to your pointer type automatically, so in C you do not need to cast the result. Always multiply by &lt;code&gt;sizeof&lt;/code&gt;, because &lt;code&gt;malloc&lt;/code&gt; counts bytes and &lt;code&gt;int&lt;/code&gt; is not 1 byte.&lt;/p&gt;
&lt;h3&gt;The rules that keep memory sane&lt;/h3&gt;
&lt;p&gt;Dynamic memory has no garbage collector, so you are responsible for every byte:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Check the result.&lt;/strong&gt; &lt;code&gt;malloc&lt;/code&gt; returns &lt;code&gt;NULL&lt;/code&gt; when out of memory. Treat &lt;code&gt;p == NULL&lt;/code&gt; as the error check above.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Free exactly what you allocated, once.&lt;/strong&gt; A &lt;strong&gt;memory leak&lt;/strong&gt; is memory you allocated and never freed; the program slowly eats RAM. A &lt;strong&gt;double free&lt;/strong&gt; is calling &lt;code&gt;free(p)&lt;/code&gt; twice, which corrupts the allocator&amp;#39;s bookkeeping. A &lt;strong&gt;use-after-free&lt;/strong&gt; is reading &lt;code&gt;*p&lt;/code&gt; after &lt;code&gt;free(p)&lt;/code&gt;; the bytes may be reused for something else. Leaks, double frees, and use-after-free are the classic C bugs, and the error-handling and testing posts will show how to catch them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Never use freed memory.&lt;/strong&gt; A pointer that was freed and then used is called a &lt;strong&gt;dangling pointer&lt;/strong&gt;[^1]:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char *p = malloc(16);
free(p);
strcpy(p, &amp;quot;boom&amp;quot;);   // use-after-free: undefined behavior
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A useful discipline: set a freed pointer to &lt;code&gt;NULL&lt;/code&gt; so a later accidental dereference crashes loudly in debuggers instead of silently corrupting data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;free(p);
p = NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Lifetime, in one rule&lt;/h3&gt;
&lt;p&gt;Stack memory belongs to the function that declared it and vanishes when it returns. Heap memory belongs to you until &lt;code&gt;free&lt;/code&gt;. If a function must return data that survives the call, the memory must be heap-allocated (or a pointer to something the caller owns). The stack vs heap distinction is reasoned about concretely in the &lt;a href=&quot;/blog/5atxjdiq&quot;&gt;Computers Explained&lt;/a&gt; series; here the practical rule is: stack sizes are known at compile time, heap sizes can be decided at runtime, and every heap byte must be freed exactly once.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;A pointer is a variable holding an address. &lt;code&gt;&amp;amp;&lt;/code&gt; produces addresses, &lt;code&gt;*&lt;/code&gt; dereferences them, &lt;code&gt;NULL&lt;/code&gt; marks &amp;quot;points at nothing&amp;quot;, &lt;code&gt;p[k]&lt;/code&gt; and &lt;code&gt;*(p+k)&lt;/code&gt; are the same thing, passing pointers lets functions modify caller variables and avoid copying big structs, &lt;code&gt;const&lt;/code&gt; tames what a pointer may change, and function pointers store whole functions in variables. &lt;code&gt;malloc&lt;/code&gt;/&lt;code&gt;free&lt;/code&gt; at the end bring the heap into play, with the lifetime rules that keep memory safe. Pointers connect everything in C, and every later post in this series builds on them. With pointers mastered, the next stop is what happens when all of this goes wrong: error handling.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Dangling_pointer&quot;&gt;Dangling pointer - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>pointers</category><category>memory</category><category>how-to</category></item><item><title>Structs, Unions, and Enums in C</title><link>https://www.wesleybertipaglia.com/blog/7hrxyq2q/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/7hrxyq2q/</guid><description>C built-in data structures for grouping: structs that combine different types, unions that share memory, and enums that name integers.</description><pubDate>Fri, 09 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Arrays hold many values of one type. This post covers the other three built-in structures the language gives you: &lt;strong&gt;structs&lt;/strong&gt;[^1] group values of &lt;em&gt;different&lt;/em&gt; types under one name, &lt;strong&gt;unions&lt;/strong&gt;[^2] let different types share the same memory, and &lt;strong&gt;enums&lt;/strong&gt;[^3] give readable names to integers.&lt;/p&gt;
&lt;h2&gt;Structs&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;struct&lt;/code&gt; is a group of named fields, possibly of different types, stored back to back in memory. Define the shape once, then make variables of that shape:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct Student {
    char name[50];
    int age;
    double grade;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That defines a type. Now declare variables of it, with &lt;code&gt;struct Student&lt;/code&gt; spelling out the full type name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct Student s1;

s1.age = 20;
s1.grade = 8.5;
strncpy(s1.name, &amp;quot;Ada&amp;quot;, 49);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each field is set and read with a dot:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;printf(&amp;quot;%s is %d years old.\n&amp;quot;, s1.name, s1.age);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can initialize a struct at declaration with brace syntax, filling fields in order:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct Student s2 = {&amp;quot;Grace&amp;quot;, 22, 9.0};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;typedef: giving the type a short name&lt;/h2&gt;
&lt;p&gt;Writing &lt;code&gt;struct Student&lt;/code&gt; everywhere is verbose. &lt;code&gt;typedef&lt;/code&gt; creates an alias, so a single word names the type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct {
    char name[50];
    int age;
    double grade;
} Student;

Student s1 = {&amp;quot;Ada&amp;quot;, 20, 8.5};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;Student&lt;/code&gt; is a complete type name. Reading other people&amp;#39;s C, you will see both styles; &lt;code&gt;typedef&lt;/code&gt; is the convention for avoiding repetition.&lt;/p&gt;
&lt;h2&gt;Arrays of structs&lt;/h2&gt;
&lt;p&gt;A struct is a type like any other, so &lt;code&gt;Student students[3]&lt;/code&gt; is an array of students:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Student students[] = {
    {&amp;quot;Ada&amp;quot;, 20, 8.5},
    {&amp;quot;Grace&amp;quot;, 22, 9.0},
    {&amp;quot;Alan&amp;quot;, 21, 7.5},
};

double total = 0;
for (int i = 0; i &amp;lt; 3; i++) {
    total += students[i].grade;
}
printf(&amp;quot;average: %.2f\n&amp;quot;, total / 3);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Access follows the rule you already know: &lt;code&gt;students[i]&lt;/code&gt; is a &lt;code&gt;Student&lt;/code&gt;, and &lt;code&gt;.grade&lt;/code&gt; picks its field. Combining arrays and structs is how you model real collections of related data.&lt;/p&gt;
&lt;h2&gt;Nested structs&lt;/h2&gt;
&lt;p&gt;A struct can contain other structs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct {
    int day;
    int month;
    int year;
} Date;

typedef struct {
    char name[50];
    Date birth;
} Person;

Person p = {&amp;quot;Ada&amp;quot;, {10, 12, 1815}};
printf(&amp;quot;%d\n&amp;quot;, p.birth.year);   // 1815
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Chains of dots peel off one level at a time.&lt;/p&gt;
&lt;h2&gt;Passing structs to functions&lt;/h2&gt;
&lt;p&gt;A struct passed to a function is passed &lt;strong&gt;by value&lt;/strong&gt;, exactly like an &lt;code&gt;int&lt;/code&gt;: the function gets a full copy, and changes stay inside. For a small struct that is cheap and fine; for a big one the copy costs time and memory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void celebrate(Student s) {
    s.age++;            // only the copy changes
    printf(&amp;quot;Happy birthday, %s!\n&amp;quot;, s.name);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the struct is large, or the function must modify the original, C uses pointers instead. The arrow operator &lt;code&gt;-&amp;gt;&lt;/code&gt; is just a dot through a pointer, and passed dereferencing by hand is a shortcut. The full explanation is the next post&amp;#39;s job; here is the shape you will see all over real C:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void grantBonus(Student *s) {
    s-&amp;gt;grade = 10.0;    // modifies the original
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-&amp;gt;&lt;/code&gt; means &amp;quot;the field of the struct this pointer points to&amp;quot;. If &lt;code&gt;s&lt;/code&gt; is a pointer, &lt;code&gt;s-&amp;gt;grade&lt;/code&gt; equals &lt;code&gt;(*s).grade&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Unions&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;union&lt;/code&gt; declares several fields that all share the same memory. Only one field is meaningful at a time, because writing any of them overwrites the others. Its size is the size of its largest member:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;union Value {
    int    i;
    double d;
    char   c;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;union Value v;
v.d = 3.14;   // writing the double
printf(&amp;quot;%f\n&amp;quot;, v.d);
v.i = 7;      // now the int; the double is gone
printf(&amp;quot;%d\n&amp;quot;, v.i);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unions exist to squeeze memory when only one interpretation applies at a time, and to reinterpret bytes, which is what reading raw hardware or network data asks for. They are usually paired with a separate field or enum that records which member is active, a pattern sometimes called a &amp;quot;tagged union&amp;quot;. Use them deliberately; a union used by accident is a source of subtle bugs.&lt;/p&gt;
&lt;h2&gt;Padding and alignment&lt;/h2&gt;
&lt;p&gt;A struct&amp;#39;s size is not simply the sum of its fields. The compiler inserts &lt;strong&gt;padding&lt;/strong&gt; after some fields so that each one lands on a memory address the CPU reads efficiently, a property called &lt;strong&gt;alignment&lt;/strong&gt;[^4]. In practice for this series: an &lt;code&gt;int&lt;/code&gt; usually must sit on a 4-byte boundary, a &lt;code&gt;double&lt;/code&gt; on an 8-byte one, so fields can be spaced out:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;printf(&amp;quot;%zu\n&amp;quot;, sizeof(Student));   // usually bigger than 50 + 4 + 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Getting the exact padding rules right matters only for binary file formats and networking, where a struct&amp;#39;s layout gets written to disk or the wire; for everything else, trust &lt;code&gt;sizeof&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Enums&lt;/h2&gt;
&lt;p&gt;An &lt;code&gt;enum&lt;/code&gt; names a set of integer constants. It exists so your code reads as words, not magic numbers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;enum Color { RED, GREEN, BLUE };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default &lt;code&gt;RED&lt;/code&gt; is 0, &lt;code&gt;GREEN&lt;/code&gt; is 1, &lt;code&gt;BLUE&lt;/code&gt; is 2. You assign explicit values if the numbers matter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;enum Color { RED = 1, GREEN = 2, BLUE = 3 };
enum Color c = GREEN;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Print it as the underlying integer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;printf(&amp;quot;%d\n&amp;quot;, c);   // 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You have already used enums indirectly: &lt;code&gt;switch&lt;/code&gt; cases prefer them, and they are the idiomatic way to represent a small set of options like states or error codes. A function that returns a status often &lt;code&gt;typedef&lt;/code&gt;s an enum so callers can test readable names instead of bare numbers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef enum { OK = 0, TOO_SMALL, TOO_BIG } Status;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That pattern leads directly into error handling later in the series.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;struct&lt;/code&gt; bundles different types into one meaningful record, &lt;code&gt;union&lt;/code&gt; lets several types share one block of memory, and &lt;code&gt;enum&lt;/code&gt; turns integers into readable names. Combined with arrays they model real data: a list of students, a grid of points, a packet of fields. Structs also mark the moment where &amp;quot;copy the value&amp;quot; and &amp;quot;point to the original&amp;quot; diverge, which is exactly where the next post lives: pointers, the subject C is most famous and most feared for.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Struct_(C_programming_language)&quot;&gt;Struct (C programming language) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Union_type&quot;&gt;Union type - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Enumerated_type&quot;&gt;Enumerated type - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Data_structure_alignment&quot;&gt;Data structure alignment - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>structs</category><category>enums</category><category>how-to</category></item><item><title>Arrays and Strings in C</title><link>https://www.wesleybertipaglia.com/blog/0k6lp8kn/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/0k6lp8kn/</guid><description>C built-in data structures: arrays of values, multidimensional arrays, and strings as null-terminated character arrays, including safe input and the string.h helpers.</description><pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So far every variable held exactly one value. Real programs deal in lists: grades, names, coordinates. C&amp;#39;s built-in structures for a &lt;em&gt;sequence of the same type&lt;/em&gt; are &lt;strong&gt;arrays&lt;/strong&gt;, and a &lt;strong&gt;string&lt;/strong&gt; is just an array of characters. This is the first of two posts on the data structures the language gives you ready-made; structs, unions, and enums are the second.&lt;/p&gt;
&lt;h2&gt;Arrays&lt;/h2&gt;
&lt;p&gt;An array is a contiguous run of values of one type, each with an index. Declare it with the element type, the name, and the size in square brackets:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int scores[5];          // 5 ints, indices 0..4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Indexing starts at zero, so the first element is &lt;code&gt;scores[0]&lt;/code&gt; and the last is &lt;code&gt;scores[4]&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;scores[0] = 92;
scores[1] = 85;
scores[2] = 88;
printf(&amp;quot;%d\n&amp;quot;, scores[2]);   // 88
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You usually initialize at declaration and skip the size, letting the compiler count:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int scores[] = {92, 85, 88, 70, 91};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The size is the number of elements, not the byte size. &lt;code&gt;sizeof(scores)&lt;/code&gt; gives total bytes; divide by the element size to get the count:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int n = sizeof(scores) / sizeof(scores[0]);   // 5
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Iterating&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;for&lt;/code&gt; loop from the loops post is the standard companion; index variables and arrays were made for each other:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int total = 0;
for (int i = 0; i &amp;lt; 5; i++) {
    total += scores[i];
}
printf(&amp;quot;average: %d\n&amp;quot;, total / 5);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A common beginner error is looping one too far and reading past the last element. That is legal to write but &lt;strong&gt;undefined behavior&lt;/strong&gt;: C does not check array bounds. &lt;code&gt;scores[5]&lt;/code&gt; is a memory error, not an exception. There are no error messages, just garbage values or a crash, and compilers warn about obvious cases only with extra flags. The array size is your responsibility, so the &lt;code&gt;sizeof&lt;/code&gt;-based count above is worth keeping in a variable.&lt;/p&gt;
&lt;h2&gt;Multidimensional arrays&lt;/h2&gt;
&lt;p&gt;An array of arrays is written with two sizes. &lt;code&gt;int grid[3][4]&lt;/code&gt; means &amp;quot;3 rows, 4 columns&amp;quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int grid[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 1, 2, 3},
};
printf(&amp;quot;%d\n&amp;quot;, grid[1][2]);   // row 1, column 2 -&amp;gt; 7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The nested loops from the loops post walk rows and columns. Underneath it is still one contiguous block of memory, laid out row by row.&lt;/p&gt;
&lt;h2&gt;Strings&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;string&lt;/strong&gt; in C has no type of its own. It is a &lt;code&gt;char&lt;/code&gt; array whose characters end with a &lt;strong&gt;null terminator&lt;/strong&gt;: the byte &lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt; (value zero). Every string function reads until it finds that byte, so the rule is: the buffer holds the visible characters &lt;em&gt;plus one&lt;/em&gt; for &lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char name[] = &amp;quot;Ada&amp;quot;;   // actually 4 chars: &amp;#39;A&amp;#39;,&amp;#39;d&amp;#39;,&amp;#39;a&amp;#39;,&amp;#39;\0&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sizeof(name)&lt;/code&gt; is 4, even though &amp;quot;Ada&amp;quot; has three letters. A string literal like &lt;code&gt;&amp;quot;hello&amp;quot;&lt;/code&gt; is stored by the compiler as &lt;code&gt;h e l l o \0&lt;/code&gt;. When you declare an array large enough, you can copy a string into it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char buffer[32];
printf(&amp;quot;What is your name? &amp;quot;);
fgets(buffer, sizeof(buffer), stdin);
printf(&amp;quot;Hello, %s\n&amp;quot;, buffer);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;fgets(buffer, size, stdin)&lt;/code&gt; reads a line and respects the buffer size, stopping at the newline or filling the buffer and always null-terminating. Prefer it over &lt;code&gt;scanf(&amp;quot;%s&amp;quot;, ...)&lt;/code&gt;, which reads without a size limit and overflows any buffer you hand it. &lt;code&gt;fgets&lt;/code&gt; leaves the trailing newline in the buffer, so you will often strip it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;buffer[strcspn(buffer, &amp;quot;\n&amp;quot;)] = &amp;#39;\0&amp;#39;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;strcspn&lt;/code&gt; (from &lt;code&gt;string.h&lt;/code&gt;) returns the index of the first newline, and this line replaces it with the terminator.&lt;/p&gt;
&lt;h2&gt;The string.h toolbox&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;string.h&amp;gt;&lt;/code&gt; header provides the string functions. The ones to know first, with their footguns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;strlen(s)&lt;/code&gt; - returns the number of characters up to &lt;code&gt;\0&lt;/code&gt; (the &amp;quot;length&amp;quot;, not counting the terminator).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strcpy(dst, src)&lt;/code&gt; - copies &lt;code&gt;src&lt;/code&gt; into &lt;code&gt;dst&lt;/code&gt;, no size check: classic overflow source. Prefer the bounded &lt;code&gt;strncpy(dst, src, size)&lt;/code&gt; (and remember to ensure &lt;code&gt;dst&lt;/code&gt; ends in &lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt; yourself) or &lt;code&gt;snprintf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strcat(dst, src)&lt;/code&gt; - appends &lt;code&gt;src&lt;/code&gt; to &lt;code&gt;dst&lt;/code&gt;; same overflow risk. Build with &lt;code&gt;snprintf&lt;/code&gt; instead when safety matters.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strcmp(a, b)&lt;/code&gt; - compares and returns &lt;code&gt;0&lt;/code&gt; if equal, negative/positive by order. Use &lt;code&gt;==&lt;/code&gt; on strings and you are comparing addresses, not text (an easy trap; addresses become first-class in the pointers post).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strchr(s, c)&lt;/code&gt; - finds the first occurrence of a character, returning a pointer or &lt;code&gt;NULL&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;string.h&amp;gt;

char combined[64];
snprintf(combined, sizeof(combined), &amp;quot;%s-%s&amp;quot;, &amp;quot;Ada&amp;quot;, &amp;quot;Lovelace&amp;quot;);
printf(&amp;quot;%s\n&amp;quot;, combined);            // Ada-Lovelace
printf(&amp;quot;len: %zu\n&amp;quot;, strlen(combined));   // 12
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;snprintf(dst, size, format, ...)&lt;/code&gt; is the safe workhorse: it writes at most &lt;code&gt;size&lt;/code&gt; characters including the terminator. Prefer it anytime you build text.&lt;/p&gt;
&lt;h2&gt;The array-to-parameter surprise&lt;/h2&gt;
&lt;p&gt;When you pass an array to a function, it does not arrive as a copy. Arrays are passed as a pointer to their first element, and inside the function &lt;code&gt;sizeof&lt;/code&gt; no longer gives the array size. Functions that take an array therefore also take its length:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int sum(int arr[], int n) {
    int total = 0;
    for (int i = 0; i &amp;lt; n; i++) {
        total += arr[i];
    }
    return total;
}

int main(void) {
    int scores[] = {92, 85, 88};
    printf(&amp;quot;%d\n&amp;quot;, sum(scores, 3));   // 265
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full story behind &amp;quot;arrays decay to pointers&amp;quot; is one of the main events of the pointers post. For now, remember the two rules stated above: pass the length, and do not trust &lt;code&gt;sizeof&lt;/code&gt; on a parameter.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Arrays are contiguous runs of one type, indexed from zero, sized by you, with no bounds checking. Strings are &lt;code&gt;char&lt;/code&gt; arrays terminated by &lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt;, and every string function follows that contract. &lt;code&gt;fgets&lt;/code&gt; and &lt;code&gt;snprintf&lt;/code&gt; read and build text safely; &lt;code&gt;strlen&lt;/code&gt;, &lt;code&gt;strcmp&lt;/code&gt;, and friends do the rest; and when an array crosses into a function it becomes a pointer to its first element. That pointer connection is where the next two posts go: structs and enums first, then pointers themselves.&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>arrays</category><category>strings</category><category>how-to</category></item><item><title>Functions in C</title><link>https://www.wesleybertipaglia.com/blog/3by3xt1z/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/3by3xt1z/</guid><description>How to package code into reusable functions: definitions, prototypes, parameters, return values, pass by value, scope, and recursion.</description><pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;strong&gt;function&lt;/strong&gt; is a named block of code you can call from anywhere else. It takes inputs, returns an output, and lets you write a piece of logic once and reuse it. &lt;code&gt;main&lt;/code&gt; is itself a function, so you have been writing functions since the first post; this one teaches the full picture.&lt;/p&gt;
&lt;h2&gt;Anatomy of a function&lt;/h2&gt;
&lt;p&gt;A function definition has a &lt;strong&gt;return type&lt;/strong&gt;, a &lt;strong&gt;name&lt;/strong&gt;, a &lt;strong&gt;parameter list&lt;/strong&gt; in parentheses, and a &lt;strong&gt;body&lt;/strong&gt; in braces:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int add(int a, int b) {
    return a + b;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it as: &amp;quot;function &lt;code&gt;add&lt;/code&gt; takes two &lt;code&gt;int&lt;/code&gt;s, returns an &lt;code&gt;int&lt;/code&gt;, and its body returns their sum.&amp;quot; To use it, &lt;strong&gt;call&lt;/strong&gt; it with matching arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int result = add(3, 4);   // result is 7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pieces, in order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;int&lt;/code&gt; - the type of the value returned. A function that returns nothing uses &lt;code&gt;void&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add&lt;/code&gt; - the identifier.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(int a, int b)&lt;/code&gt; - parameters: the inputs, each a type plus a name. A function with no inputs writes &lt;code&gt;void&lt;/code&gt; inside: &lt;code&gt;int main(void)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{ return a + b; }&lt;/code&gt; - the body, with &lt;code&gt;return&lt;/code&gt; sending a value back to the caller.&lt;/li&gt;
&lt;li&gt;The caller&amp;#39;s values are &lt;strong&gt;arguments&lt;/strong&gt;; the function&amp;#39;s names for them are &lt;strong&gt;parameters&lt;/strong&gt;. The two often use different names; only positions matter.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Prototypes: declaring before using&lt;/h2&gt;
&lt;p&gt;In C you cannot call a function before the compiler has seen its definition, because the compiler needs to know its signature to type-check the call. If &lt;code&gt;add&lt;/code&gt; is defined &lt;em&gt;after&lt;/em&gt; &lt;code&gt;main&lt;/code&gt;, this fails:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int main(void) {
    int r = add(3, 4);    // error: &amp;#39;add&amp;#39; was not declared
    return 0;
}

int add(int a, int b) {
    return a + b;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fix is a &lt;strong&gt;prototype&lt;/strong&gt; (also called forward declaration): the signature with a &lt;code&gt;;&lt;/code&gt; instead of a body, placed before the call. Real programs put these in header files, but for now, top of the file works:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int add(int a, int b);    // prototype

int main(void) {
    int r = add(3, 4);
    return 0;
}

int add(int a, int b) {   // definition
    return a + b;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a strict C99 project the function must be declared before every use, which is why headers exist: the prototype lives in a &lt;code&gt;.h&lt;/code&gt; file shared by every &lt;code&gt;.c&lt;/code&gt; file that calls the function.&lt;/p&gt;
&lt;h2&gt;Pass by value&lt;/h2&gt;
&lt;p&gt;C passes arguments &lt;strong&gt;by value&lt;/strong&gt;: the parameter is a &lt;em&gt;copy&lt;/em&gt; of the argument. Changes inside the function never touch the caller&amp;#39;s variable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void change(int x) {
    x = 99;
}

int main(void) {
    int n = 5;
    change(n);
    printf(&amp;quot;%d\n&amp;quot;, n);   // still 5
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the single most important rule about functions in C, and it explains why the next post&amp;#39;s arrays and the pointers post&amp;#39;s &lt;code&gt;&amp;amp;&lt;/code&gt; operator are indispensable: to let a function modify a caller&amp;#39;s variable, you must pass a pointer to it, because a copy of an address still names the original.&lt;/p&gt;
&lt;h2&gt;return and void&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;return&lt;/code&gt; exits the function and hands back a value of the declared return type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;double average(double a, double b) {
    return (a + b) / 2.0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ending the function without a &lt;code&gt;return&lt;/code&gt; when the type is not &lt;code&gt;void&lt;/code&gt; is undefined behavior in most cases; the compiler may help you catch it only sometimes. &lt;code&gt;void&lt;/code&gt; functions return no value; use a bare &lt;code&gt;return;&lt;/code&gt; to leave early, or none at all:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void greet(const char *name) {
    if (name == NULL) {   // guard clause
        return;
    }
    printf(&amp;quot;Hello, %s\n&amp;quot;, name);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Scope and lifetime&lt;/h2&gt;
&lt;p&gt;Where a variable exists is its &lt;strong&gt;scope&lt;/strong&gt;, and how long it survives is its &lt;strong&gt;lifetime&lt;/strong&gt;. The blocks from the syntax post are the boundaries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A variable declared inside a function is &lt;strong&gt;local&lt;/strong&gt;: visible only inside its block, and destroyed when the block ends.&lt;/li&gt;
&lt;li&gt;The parameters are local to the function body.&lt;/li&gt;
&lt;li&gt;A variable declared outside any function is &lt;strong&gt;global&lt;/strong&gt;: visible everywhere after its declaration, and alive for the whole program.&lt;/li&gt;
&lt;li&gt;A block &lt;code&gt;{}&lt;/code&gt; can open its own scope for a temporary variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int global = 10;                 // global, lives forever

void f(void) {
    int local = 5;               // local to f
    {
        int temp = local + 1;    // block scope: only inside these braces
    }
    // temp does not exist here
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Prefer local variables. Globals make functions hard to reason about, because any part of the program can change them.&lt;/p&gt;
&lt;h2&gt;Storage classes&lt;/h2&gt;
&lt;p&gt;Four keywords control &lt;strong&gt;storage class&lt;/strong&gt;: where a variable lives and how long it lasts.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;auto&lt;/code&gt; - the default for locals; you practically never write it.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;register&lt;/code&gt; - a hint to keep the value in a CPU register; the compiler largely ignores it today.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;static&lt;/code&gt; - on a local: the variable keeps its value between calls (and is initialized only once). On a global or function: it becomes visible only within its own file, hiding it from other files.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;extern&lt;/code&gt; - declares that the variable or function is defined in another file, letting you use it across files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;static&lt;/code&gt; local is the useful one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int nextId(void) {
    static int id = 1000;   // keeps its value across calls
    return id++;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each call returns &lt;code&gt;1000&lt;/code&gt;, then &lt;code&gt;1001&lt;/code&gt;, and so on, because the variable survives between calls even though it is local. &lt;code&gt;const&lt;/code&gt; (from the syntax post) is not a storage class but is often grouped with these: it marks a value read-only.&lt;/p&gt;
&lt;h2&gt;Recursion&lt;/h2&gt;
&lt;p&gt;A function can call itself; that is &lt;strong&gt;recursion&lt;/strong&gt;[^1]. Its natural home is problems that break into smaller versions of themselves. The classic is factorial: &lt;code&gt;n! = n * (n - 1)!&lt;/code&gt;, with &lt;code&gt;0! = 1&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int factorial(int n) {
    if (n &amp;lt;= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;factorial(4)
  -&amp;gt; 4 * factorial(3)
    -&amp;gt; 3 * factorial(2)
      -&amp;gt; 2 * factorial(1)
        -&amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every recursive function needs a &lt;strong&gt;base case&lt;/strong&gt; (the &lt;code&gt;n &amp;lt;= 1&lt;/code&gt; branch) that stops the chain; without it, the calls never end and the program crashes with a stack overflow (the recursion story connects to the pointers/memory post later). Compared to loops, recursion shines on nested data like trees; use a loop for plain counting.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;A function is a named, reusable block: a return type, parameters passed &lt;em&gt;by value&lt;/em&gt;, a body, and &lt;code&gt;return&lt;/code&gt; to send results back. Prototypes let later definitions be called earlier; &lt;code&gt;void&lt;/code&gt; means &amp;quot;returns nothing&amp;quot;; scope rules decide what each name means; storage classes tune lifetime and visibility; and recursion handles problems that fold into themselves. Functions combine with conditionals and loops to express any logic, and the next post applies them to the first built-in data structure: arrays and strings.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Recursion_(computer_science)&quot;&gt;Recursion (computer science) - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>functions</category><category>how-to</category></item><item><title>Loops in C</title><link>https://www.wesleybertipaglia.com/blog/w2t8bzin/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/w2t8bzin/</guid><description>How to repeat code: while, do-while, and for loops, plus break, continue, and the infinite-loop traps beginners hit.</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;strong&gt;loop&lt;/strong&gt; runs the same block of code repeatedly, as long as a condition stays true. It is the feature that makes programs useful, because most real work is repeating an operation a certain number of times or until a goal is reached. C has three loops: &lt;code&gt;while&lt;/code&gt;, &lt;code&gt;do&lt;/code&gt;/&lt;code&gt;while&lt;/code&gt;, and &lt;code&gt;for&lt;/code&gt;. All three run a block; they differ only in when the condition is checked.&lt;/p&gt;
&lt;h2&gt;while&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;while&lt;/code&gt; loop checks its condition &lt;em&gt;before&lt;/em&gt; each run. If the condition is false at the start, the body never runs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int n = 3;
while (n &amp;gt; 0) {
    printf(&amp;quot;%d\n&amp;quot;, n);
    n = n - 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;3
2
1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same truthiness rule from conditionals applies: any non-zero value loops, zero stops.&lt;/p&gt;
&lt;h2&gt;The infinite loop and how to escape&lt;/h2&gt;
&lt;p&gt;If the condition never becomes false, the loop never ends. That is the &lt;strong&gt;infinite loop&lt;/strong&gt;. Sometimes it is intentional and broken with &lt;code&gt;break&lt;/code&gt;; usually it is a bug. The most common causes: forgetting to update the counter, and comparing with &lt;code&gt;=&lt;/code&gt; instead of &lt;code&gt;==&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int n = 3;
while (n &amp;gt; 0) {   // bug: n never changes
    printf(&amp;quot;%d\n&amp;quot;, n);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This prints &lt;code&gt;3&lt;/code&gt; forever and never terminates. If your program hangs, look for a loop whose condition never flips. &lt;code&gt;break&lt;/code&gt; exits the innermost loop immediately; &lt;code&gt;continue&lt;/code&gt; skips straight to the next condition check:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int i = 0;
while (1) {              // intentional infinite loop
    i++;
    if (i == 3) continue;   // skip printing 3
    if (i &amp;gt; 5) break;       // leave the loop at 6
    printf(&amp;quot;%d\n&amp;quot;, i);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1
2
4
5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;while (1)&lt;/code&gt; or &lt;code&gt;for (;;)&lt;/code&gt; loop paired with &lt;code&gt;break&lt;/code&gt; is the standard way to write &amp;quot;loop forever until something special happens&amp;quot;, common when reading input until the end is reached.&lt;/p&gt;
&lt;h2&gt;do / while&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;do&lt;/code&gt;/&lt;code&gt;while&lt;/code&gt; loop runs the body first and checks the condition &lt;em&gt;after&lt;/em&gt;. It always executes at least once:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int n = 0;
do {
    printf(&amp;quot;n = %d\n&amp;quot;, n);
    n++;
} while (n &amp;lt; 3);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;n = 0
n = 1
n = 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The body runs with &lt;code&gt;n = 0&lt;/code&gt;, then tests &lt;code&gt;0 &amp;lt; 3&lt;/code&gt;, runs again, and stops when &lt;code&gt;n&lt;/code&gt; is 3. Use &lt;code&gt;do&lt;/code&gt;/&lt;code&gt;while&lt;/code&gt; when the body must run once before any condition makes sense, like asking for input at least once, or processing one menu choice before checking whether to continue. Note the &lt;code&gt;;&lt;/code&gt; after the &lt;code&gt;while (n &amp;lt; 3)&lt;/code&gt; line; a &lt;code&gt;do&lt;/code&gt;/&lt;code&gt;while&lt;/code&gt; is the one loop that ends with a semicolon.&lt;/p&gt;
&lt;h2&gt;for&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;for&lt;/code&gt; loop packs counting into one line: an initialization, a condition, and an update, separated by semicolons:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;for (int i = 0; i &amp;lt; 5; i++) {
    printf(&amp;quot;%d\n&amp;quot;, i);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0
1
2
3
4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reading order matters: &lt;code&gt;int i = 0&lt;/code&gt; runs once before the loop; the condition &lt;code&gt;i &amp;lt; 5&lt;/code&gt; is checked before each run, like a &lt;code&gt;while&lt;/code&gt;; &lt;code&gt;i++&lt;/code&gt; runs at the end of each iteration before the next check. The same loop in &lt;code&gt;while&lt;/code&gt; form shows the relationship:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int i = 0;
while (i &amp;lt; 5) {
    printf(&amp;quot;%d\n&amp;quot;, i);
    i++;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use &lt;code&gt;for&lt;/code&gt; when you know the number of iterations, because the count is visible in one line: start, stop, step. It is the natural tool for iterating over arrays, which the arrays post will use constantly.&lt;/p&gt;
&lt;p&gt;The three parts are individually optional. &lt;code&gt;for (;;)&lt;/code&gt; is an infinite loop. You can declare the counter inside the heading (&lt;code&gt;int i = 0&lt;/code&gt;), in which case it only exists inside the loop, or declare it before and let the loop update it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int i;
for (i = 0; i &amp;lt; 10; i++) {
    // ...
}
// i is still usable here, holding 10
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Count down and step by more than one&lt;/h2&gt;
&lt;p&gt;The update step is any expression, so the loop is flexible:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;for (int i = 10; i &amp;gt; 0; i--) { ... }     // 10, 9, ..., 1
for (int i = 0; i &amp;lt; 10; i += 2) { ... }  // 0, 2, 4, 6, 8
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Nested loops&lt;/h2&gt;
&lt;p&gt;A loop body can contain another loop. Nested loops run &amp;quot;all of the inner loop for each step of the outer loop&amp;quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;for (int row = 1; row &amp;lt;= 3; row++) {
    for (int col = 1; col &amp;lt;= 3; col++) {
        printf(&amp;quot;%d%d &amp;quot;, row, col);
    }
    printf(&amp;quot;\n&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;11 12 13
21 22 23
31 32 33
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The inner loop completes fully before the outer loop advances. This is the shape you will use later to walk rows and columns of matrices.&lt;/p&gt;
&lt;h2&gt;Accumulating a result&lt;/h2&gt;
&lt;p&gt;A loop plus a variable that changes each iteration is the workhorse of programming. The accumulator starts at a neutral value and absorbs each iteration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int total = 0;
for (int i = 1; i &amp;lt;= 10; i++) {
    total += i;
}
printf(&amp;quot;sum of 1..10 = %d\n&amp;quot;, total);   // 55
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Zero for sums, one for products. Counters, sums, and best-so-far values all follow this pattern.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;while&lt;/code&gt; checks before running, &lt;code&gt;do&lt;/code&gt;/&lt;code&gt;while&lt;/code&gt; checks after running, and &lt;code&gt;for&lt;/code&gt; collects the count into one heading for the common &amp;quot;start at A, go to B, step by C&amp;quot; case. &lt;code&gt;break&lt;/code&gt; leaves a loop early, &lt;code&gt;continue&lt;/code&gt; skips one iteration, and infinite loops are either intentional (with &lt;code&gt;break&lt;/code&gt;) or a bug from a condition that never flips. With conditionals and loops you can now control any program; the next post packages code into reusable units: functions.&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>loops</category><category>how-to</category></item><item><title>Conditionals in C</title><link>https://www.wesleybertipaglia.com/blog/ees4c5bm/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/ees4c5bm/</guid><description>How a program decides what to run: if, else if, else, and switch, plus C truthiness and the traps that come with it.</description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Up to now every statement in a program ran in order, top to bottom. Real programs need branches: run this code only if something is true. That is what &lt;strong&gt;conditionals&lt;/strong&gt; do. This post covers &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt;, the &lt;code&gt;else if&lt;/code&gt; chain, &lt;code&gt;switch&lt;/code&gt;, and the truthiness rule that underpins all of them.&lt;/p&gt;
&lt;h2&gt;Truthiness in C&lt;/h2&gt;
&lt;p&gt;A condition is any expression whose value decides the branch. C does not require a real boolean here: &lt;strong&gt;any non-zero value is true, and zero is false&lt;/strong&gt;. That means comparisons, which produce &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt;, work naturally, and so do plain numbers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (5) { ... }      // always true
if (0) { ... }      // never true
if (-1) { ... }     // true! (-1 is non-zero)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This rule is everywhere in C: in &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;, &lt;code&gt;for&lt;/code&gt;, and even in return values. A function that returns &lt;code&gt;0&lt;/code&gt; for &amp;quot;all good&amp;quot; is describing failure, because in C zero means false.&lt;/p&gt;
&lt;h2&gt;if / else&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;if&lt;/code&gt; statement runs its block when the condition is true. An optional &lt;code&gt;else&lt;/code&gt; runs when it is not:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int temperature = 35;

if (temperature &amp;gt; 30) {
    printf(&amp;quot;It is hot.\n&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int score = 55;

if (score &amp;gt;= 60) {
    printf(&amp;quot;Passed.\n&amp;quot;);
} else {
    printf(&amp;quot;Did not pass.\n&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a single statement, the braces are optional:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (score &amp;gt;= 60)
    printf(&amp;quot;Passed.\n&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But always use braces anyway; adding one line later without braces silently changes the logic. Braces are how you say &amp;quot;this whole block belongs to the branch&amp;quot;.&lt;/p&gt;
&lt;h2&gt;else if: multiple options&lt;/h2&gt;
&lt;p&gt;Chain as many alternatives as you need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int grade = 85;

if (grade &amp;gt;= 90) {
    printf(&amp;quot;A\n&amp;quot;);
} else if (grade &amp;gt;= 80) {
    printf(&amp;quot;B\n&amp;quot;);
} else if (grade &amp;gt;= 70) {
    printf(&amp;quot;C\n&amp;quot;);
} else {
    printf(&amp;quot;Below C\n&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Only one branch runs: C tests each condition in order and stops at the first true one. The final &lt;code&gt;else&lt;/code&gt; runs if none matched; it is optional.&lt;/p&gt;
&lt;p&gt;Guard clauses are the other common pattern: check for the bad cases first and return early, leaving the main logic un-nested:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (items &amp;lt;= 0) {
    return 0;
}
// ... main logic ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Nested conditionals&lt;/h2&gt;
&lt;p&gt;An &lt;code&gt;if&lt;/code&gt; can contain another &lt;code&gt;if&lt;/code&gt;. Nesting is sometimes unavoidable, but deep nesting is hard to read. Prefer early returns and combining conditions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (age &amp;gt;= 18) {
    if (hasID) {
        printf(&amp;quot;Welcome in.\n&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same logic reads better as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (age &amp;gt;= 18 &amp;amp;&amp;amp; hasID) {
    printf(&amp;quot;Welcome in.\n&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The dangling else&lt;/h2&gt;
&lt;p&gt;When you write &lt;code&gt;else&lt;/code&gt; after nested &lt;code&gt;if&lt;/code&gt;s without braces, the &lt;code&gt;else&lt;/code&gt; binds to the &lt;em&gt;nearest&lt;/em&gt; unmatched &lt;code&gt;if&lt;/code&gt;. That is the &amp;quot;dangling else&amp;quot; rule, and it trips everyone at least once:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (a)
    if (b)
        printf(&amp;quot;both\n&amp;quot;);
else
    printf(&amp;quot;a only\n&amp;quot;);   // misleading: this else belongs to the inner if
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Braces fix it. This is a general rule of the language: never be clever with optional braces.&lt;/p&gt;
&lt;h2&gt;switch: choosing from a list&lt;/h2&gt;
&lt;p&gt;When you compare one integer value against many possible constants, &lt;code&gt;switch&lt;/code&gt; is cleaner than a long &lt;code&gt;else if&lt;/code&gt; chain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int day = 3;

switch (day) {
    case 1:
        printf(&amp;quot;Monday\n&amp;quot;);
        break;
    case 2:
        printf(&amp;quot;Tuesday\n&amp;quot;);
        break;
    case 3:
        printf(&amp;quot;Wednesday\n&amp;quot;);
        break;
    default:
        printf(&amp;quot;Unknown day\n&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How it works: the expression selects a matching &lt;code&gt;case&lt;/code&gt;, and execution starts there. The &lt;code&gt;break&lt;/code&gt; is what stops execution from continuing into the next case. If you forget it, execution &lt;em&gt;falls through&lt;/em&gt; to the next case, which is usually a bug but occasionally useful for sharing code between cases:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;switch (month) {
    case 4:
    case 6:
    case 9:
    case 11:
        printf(&amp;quot;30 days\n&amp;quot;);
        break;
    case 2:
        printf(&amp;quot;28 or 29 days\n&amp;quot;);
        break;
    default:
        printf(&amp;quot;31 days\n&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;default&lt;/code&gt; case catches anything unmatched, like &lt;code&gt;else&lt;/code&gt;. &lt;code&gt;switch&lt;/code&gt; works with &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;char&lt;/code&gt;, and &lt;code&gt;enum&lt;/code&gt; values, one of the places the upcoming structs post will mention again.&lt;/p&gt;
&lt;h2&gt;The assignment trap&lt;/h2&gt;
&lt;p&gt;The operators post warned about &lt;code&gt;=&lt;/code&gt; vs &lt;code&gt;==&lt;/code&gt;. Inside a condition it turns from a bug into a trap with no compiler complaint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (x = 5) { ... }   // assigns 5 to x, then tests if 5 is non-zero: always true!
if (x == 5) { ... }  // what you meant
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compile with &lt;code&gt;-Wall&lt;/code&gt; and GCC warns about most of these (&lt;code&gt;-Wparentheses&lt;/code&gt;). Some codebases write &lt;code&gt;if (5 == x)&lt;/code&gt; to make a mistaken &lt;code&gt;=&lt;/code&gt; a compile error, because you cannot assign to a literal. If you ever see constants on the left, that is why.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;if&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt; branches on a condition, &lt;code&gt;else if&lt;/code&gt; chains alternatives, &lt;code&gt;switch&lt;/code&gt; picks from a list of constants, and the truthiness rule (zero is false, everything else is true) connects it all to the operators from the previous post. Braces keep branches honest, and &lt;code&gt;break&lt;/code&gt; keeps &lt;code&gt;switch&lt;/code&gt; cases separate. Now that the program can decide, the next step is repeating: loops.&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>conditionals</category><category>how-to</category></item><item><title>Operators in C</title><link>https://www.wesleybertipaglia.com/blog/04q6fhv8/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/04q6fhv8/</guid><description>The operators C provides: arithmetic, assignment, comparison, logical, bitwise, and shorthand, plus how precedence decides the order of evaluation.</description><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;An &lt;strong&gt;operator&lt;/strong&gt; is a symbol that computes a new value from one or more values. C has a small set of them, and once you know them you can express almost any calculation. This post goes through them group by group. The conditionals and loops that &lt;em&gt;use&lt;/em&gt; these comparisons have their own posts, so they are not repeated here.&lt;/p&gt;
&lt;h2&gt;Arithmetic operators&lt;/h2&gt;
&lt;p&gt;The five basic ones should look familiar from math:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int sum  = 10 + 5;    // 15  addition
int diff = 10 - 5;    // 5   subtraction
int prod = 10 * 5;    // 50  multiplication
int quot = 10 / 3;    // 3   division (integers cut the fraction)
int rem  = 10 % 3;    // 1   remainder (modulo)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember from the data types post: &lt;code&gt;/&lt;/code&gt; on two integers is integer division and &lt;code&gt;%&lt;/code&gt; needs integers. If at least one operand is a decimal, &lt;code&gt;/&lt;/code&gt; does floating-point division.&lt;/p&gt;
&lt;h2&gt;Assignment operators&lt;/h2&gt;
&lt;p&gt;The plain &lt;code&gt;=&lt;/code&gt; assigns a value. C also provides compound operators that combine assignment with arithmetic. &lt;code&gt;a += 5&lt;/code&gt; means &amp;quot;add 5 to &lt;code&gt;a&lt;/code&gt;&amp;quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int a = 10;
a += 5;   // a is 15
a -= 3;   // a is 12
a *= 2;   // a is 24
a /= 4;   // a is 6
a %= 4;   // a is 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are matching versions for every binary operator, including the bitwise ones below. Read &lt;code&gt;a += 5&lt;/code&gt; as shorthand for &lt;code&gt;a = a + 5&lt;/code&gt;; the value is stored back into the same variable.&lt;/p&gt;
&lt;h2&gt;Increment and decrement&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;++&lt;/code&gt; adds one, &lt;code&gt;--&lt;/code&gt; subtracts one. Where you put it matters:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int x = 5;
x++;          // x is now 6 (post-increment: returns old value)
++x;          // x is now 7 (pre-increment: returns new value)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int x = 5;
int a = x++;  // a = 5, x = 6
int b = ++x;  // x = 7, b = 7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;x++&lt;/code&gt; gives you the value &lt;em&gt;before&lt;/em&gt; the increment; &lt;code&gt;++x&lt;/code&gt; gives the value &lt;em&gt;after&lt;/em&gt;. When the result is not used (a standalone &lt;code&gt;x++;&lt;/code&gt; line) the two are identical. C programmers use them heavily in loops.&lt;/p&gt;
&lt;h2&gt;Relational (comparison) operators&lt;/h2&gt;
&lt;p&gt;These compare two values and produce &lt;code&gt;1&lt;/code&gt; (true) or &lt;code&gt;0&lt;/code&gt; (false):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;10 &amp;gt; 5    // 1  greater than
10 &amp;lt; 5    // 0  less than
10 &amp;gt;= 5   // 1  greater or equal
10 &amp;lt;= 5   // 0  less or equal
10 == 5   // 0  equal (two equals signs!)
10 != 5   // 1  not equal
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The classic beginner bug is writing &lt;code&gt;=&lt;/code&gt; when you mean &lt;code&gt;==&lt;/code&gt;. &lt;code&gt;=&lt;/code&gt; assigns and &lt;code&gt;==&lt;/code&gt; compares, and the compiler may not catch the mix-up in an &lt;code&gt;if&lt;/code&gt;. If you wonder why C lets you put an assignment inside a condition, you are not alone; that is one of the language&amp;#39;s darker corners, covered in the conditionals post.&lt;/p&gt;
&lt;h2&gt;Logical operators&lt;/h2&gt;
&lt;p&gt;These combine true/false values:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;flag1 &amp;amp;&amp;amp; flag2   // AND: 1 only if both are true
flag1 || flag2   // OR:  1 if at least one is true
!flag            // NOT: flips the value
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int age = 25;
age &amp;gt;= 18 &amp;amp;&amp;amp; age &amp;lt;= 65   // 1: adult and of working age
age &amp;lt; 18 || age &amp;gt; 65     // 0: neither child nor senior
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt; are &lt;strong&gt;short-circuit&lt;/strong&gt;: C evaluates the left side first and only evaluates the right side if needed. With &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, if the left is false the right is never evaluated; with &lt;code&gt;||&lt;/code&gt;, if the left is true the right is skipped. That is safe for the common guard pattern like &lt;code&gt;ptr != NULL &amp;amp;&amp;amp; *ptr == 5&lt;/code&gt;, which would crash if evaluated left-to-right without short-circuiting.&lt;/p&gt;
&lt;h2&gt;Bitwise operators&lt;/h2&gt;
&lt;p&gt;These work on the individual bits of the value. You rarely need them in application code, but they are what make C the language of low-level programming:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;~a     // NOT: flips every bit
a &amp;amp; b  // AND: 1 only where both bits are 1
a | b  // OR:  1 where at least one bit is 1
a ^ b  // XOR: 1 where the bits differ
a &amp;lt;&amp;lt; n // shift all bits left by n (like multiplying by 2^n)
a &amp;gt;&amp;gt; n // shift all bits right by n (like dividing by 2^n)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int flags = 0b00001100;       // 12
flags = flags &amp;lt;&amp;lt; 1;           // 0b00011000 (24)
int masked = 12 &amp;amp; 0b00001010; // 0b00001000 (8)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Common uses: packing flags into a single byte, reading hardware registers, and fast multiply/divide by powers of two.&lt;/p&gt;
&lt;h2&gt;The conditional (ternary) operator&lt;/h2&gt;
&lt;p&gt;C has one three-part operator, a compact if/else that evaluates to a value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int max = (a &amp;gt; b) ? a : b;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it as &amp;quot;if the condition is true, take the first value, otherwise the second.&amp;quot; It is just a shortcut; anything you write with it can be written with a full &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt;, which you will meet in the next post.&lt;/p&gt;
&lt;h2&gt;The comma operator and sizeof&lt;/h2&gt;
&lt;p&gt;The comma operator evaluates several expressions in order and keeps the last value, usually inside &lt;code&gt;for&lt;/code&gt; loops to update multiple variables:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int i, j;
for (i = 0, j = 10; i &amp;lt; j; i++, j--) {
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sizeof&lt;/code&gt; is technically an operator, not a function: it returns the size in bytes of a type or value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;printf(&amp;quot;%zu\n&amp;quot;, sizeof(int));   // most often 4
printf(&amp;quot;%zu\n&amp;quot;, sizeof(3.14));  // 8, because the literal is a double
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Precedence and associativity&lt;/h2&gt;
&lt;p&gt;When an expression mixes operators, C needs a rule to know what binds tighter. That rule is &lt;strong&gt;operator precedence&lt;/strong&gt;[^1]: high-precedence operators group before low-precedence ones. &lt;code&gt;2 + 3 * 4&lt;/code&gt; is &lt;code&gt;2 + (3 * 4)&lt;/code&gt; = 14, not 20, because &lt;code&gt;*&lt;/code&gt; binds tighter than &lt;code&gt;+&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The practical guidance is short: you cannot reliably memorize the full table, and neither can most working C programmers. Write parentheses to say what you mean:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int x = (a + b) * c;     // clear
int y = a + b * c;       // correct, but you have to think about it
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When operands of the same precedence appear together, order is decided by &lt;strong&gt;associativity&lt;/strong&gt; (left-to-right for arithmetic). The one surprising case is assignment, which is right-to-left: &lt;code&gt;a = b = 5&lt;/code&gt; assigns 5 to &lt;code&gt;b&lt;/code&gt; and then to &lt;code&gt;a&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Two more notes worth engraving. Integer division and remainder follow their own rules covered in the data types post. And dangerous-looking expressions like &lt;code&gt;x++ + x++&lt;/code&gt; are &lt;strong&gt;undefined behavior&lt;/strong&gt;: C does not promise &lt;em&gt;any&lt;/em&gt; result when you read and modify the same variable more than once in an expression, so never write them.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Arithmetic does math, assignment stores results (with handy &lt;code&gt;+=&lt;/code&gt;-style shorthand), relational operators produce true/false, logical operators combine them with short-circuiting, bitwise operators touch raw bits, and the ternary operator is a one-line branch. Precedence decides grouping, and parentheses are free insurance. With these, you can write any calculation; the next two posts show how to decide &lt;em&gt;which&lt;/em&gt; calculation runs. That is conditionals.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Order_of_operations&quot;&gt;Operator precedence - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>operators</category><category>how-to</category></item><item><title>Data Types in C</title><link>https://www.wesleybertipaglia.com/blog/6js0cwcu/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/6js0cwcu/</guid><description>The kinds of values C can store: integers, characters, floats, and booleans, their sizes and ranges, and how to convert between types.</description><pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;strong&gt;data type&lt;/strong&gt; tells the compiler two things: how many bytes a value occupies in memory, and how those bytes should be interpreted. Because those two facts are fixed, the type also decides the range of values a variable can hold. This post covers the basic types C gives you: integers, characters, floating-point numbers, and booleans.&lt;/p&gt;
&lt;h2&gt;The basic types&lt;/h2&gt;
&lt;p&gt;C has five fundamental types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;char&lt;/code&gt; - one byte (usually 8 bits), used for a single character like &lt;code&gt;&amp;#39;A&amp;#39;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int&lt;/code&gt; - an integer, typically 4 bytes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;float&lt;/code&gt; - a single-precision decimal number, 4 bytes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;double&lt;/code&gt; - a double-precision decimal number, 8 bytes. Prefer it over &lt;code&gt;float&lt;/code&gt; for general math.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;void&lt;/code&gt; - &amp;quot;no type&amp;quot;. It cannot hold a value; it is used to mean &amp;quot;returns nothing&amp;quot; or &amp;quot;no parameters&amp;quot;, and later for generic pointers.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char letter = &amp;#39;A&amp;#39;;
int count = 42;
float pi = 3.14f;       // the f marks a float literal
double precise = 3.14159;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Sizes and ranges&lt;/h2&gt;
&lt;p&gt;The list above says &amp;quot;usually&amp;quot; because C does not fix the sizes; it guarantees only minimum widths. On modern desktop machines, the de-facto sizes are:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;type       size    range (signed)
char       1 byte  -128 to 127
int        4 bytes -2,147,483,648 to 2,147,483,647
float      4 bytes ±3.4e38 (about 7 significant digits)
double     8 bytes ±1.8e308 (about 15 significant digits)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can ask the compiler instead of guessing, with the &lt;code&gt;sizeof&lt;/code&gt; operator:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;stdio.h&amp;gt;

int main(void) {
    printf(&amp;quot;int:    %zu bytes\n&amp;quot;, sizeof(int));
    printf(&amp;quot;double: %zu bytes\n&amp;quot;, sizeof(double));
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sizeof&lt;/code&gt; returns bytes, and &lt;code&gt;%zu&lt;/code&gt; is the specifier for that kind of value.&lt;/p&gt;
&lt;h2&gt;Modifiers: signed, unsigned, short, long&lt;/h2&gt;
&lt;p&gt;You can modify the basic types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;signed&lt;/code&gt; accepts negatives; &lt;code&gt;unsigned&lt;/code&gt; only zero and positives, doubling the upper range because the sign bit is freed for value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;short&lt;/code&gt; makes an integer smaller (usually 2 bytes); &lt;code&gt;long&lt;/code&gt; makes it bigger (usually 8 bytes).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;unsigned int score = 4294967295U;  // 0 to ~4.29 billion
short small = 32767;               // usually 2 bytes
long big = 3000000000L;            // usually 8 bytes
long long huge = 9000000000000LL;  // at least 8 bytes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;char&lt;/code&gt; can also be &lt;code&gt;signed&lt;/code&gt; or &lt;code&gt;unsigned&lt;/code&gt;, which matters when you read raw bytes. The combined rules are easy to check mentally: an unsigned 8-bit value goes &lt;code&gt;0..255&lt;/code&gt;, a signed 8-bit value goes &lt;code&gt;-128..127&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Overflow: what happens when you go past the range&lt;/h2&gt;
&lt;p&gt;If arithmetic produces a number too big for the type, the bits wrap around. With &lt;code&gt;int&lt;/code&gt; you get undefined behavior in C, so the program may do anything; with &lt;code&gt;unsigned&lt;/code&gt; the result wraps deterministically like an odometer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;unsigned char x = 255;
x = x + 1;
printf(&amp;quot;%u\n&amp;quot;, x);   // prints 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a feature on purpose for low-level code, and a footgun everywhere else. If your values could approach the edge of the range, use a wider type.&lt;/p&gt;
&lt;h2&gt;Booleans&lt;/h2&gt;
&lt;p&gt;C historically had no boolean type; any non-zero value is true and zero is false. That rule still applies everywhere in C. Since C99 there is a real &lt;code&gt;_Bool&lt;/code&gt;, normally used through &lt;code&gt;stdbool.h&lt;/code&gt;, which defines &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;true&lt;/code&gt;, and &lt;code&gt;false&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;stdbool.h&amp;gt;

bool active = true;
bool done = false;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Exactly two values, and a &lt;code&gt;bool&lt;/code&gt; printed with &lt;code&gt;%d&lt;/code&gt; shows &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Converting between types&lt;/h2&gt;
&lt;p&gt;C converts types automatically in many situations, called &lt;strong&gt;implicit conversion&lt;/strong&gt;. Assigning an &lt;code&gt;int&lt;/code&gt; value into a &lt;code&gt;double&lt;/code&gt; variable promotes it losslessly; going the other way truncates:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;double d = 7;    // 7 becomes 7.0
int i = 2.99;    // becomes 2 (the fraction is dropped, not rounded)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you mix types in arithmetic, the smaller type is promoted so both operands share a type; this is called &lt;strong&gt;usual arithmetic conversions&lt;/strong&gt;[^1]. The integer types get promoted to &lt;code&gt;int&lt;/code&gt; or &lt;code&gt;unsigned int&lt;/code&gt;, then to the floating-point type if one operand is a float.&lt;/p&gt;
&lt;p&gt;You can also force a conversion explicitly with a &lt;strong&gt;cast&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int total = 7;
int items = 2;
double average = (double)total / items;   // 3.5, not 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without the cast, &lt;code&gt;7 / 2&lt;/code&gt; is integer division and yields &lt;code&gt;3&lt;/code&gt;, because both operands are &lt;code&gt;int&lt;/code&gt;. The cast makes one operand a &lt;code&gt;double&lt;/code&gt;, so the division becomes floating-point. This single cast fixes one of the most common beginner bugs in C.&lt;/p&gt;
&lt;h2&gt;Integer division deserves attention&lt;/h2&gt;
&lt;p&gt;For integers, &lt;code&gt;/&lt;/code&gt; cuts off the fraction entirely (&lt;code&gt;7 / 2 == 3&lt;/code&gt;), and &lt;code&gt;%&lt;/code&gt; gives the remainder (&lt;code&gt;7 % 2 == 1&lt;/code&gt;). The remainder keeps the sign of the dividend. Mixing an integer and a float promotes the integer, so &lt;code&gt;7.0 / 2&lt;/code&gt; is &lt;code&gt;3.5&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Types are the contract between your program and memory: &lt;code&gt;char&lt;/code&gt; for single bytes, &lt;code&gt;int&lt;/code&gt; and its modified forms for whole numbers, &lt;code&gt;float&lt;/code&gt;/&lt;code&gt;double&lt;/code&gt; for decimals, &lt;code&gt;bool&lt;/code&gt; for flags, and &lt;code&gt;void&lt;/code&gt; for &amp;quot;nothing&amp;quot;. The compiler checks types at build time, so getting them right catches a huge class of bugs before your program ever runs. Next, the tools for combining these values: operators.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Type_conversion&quot;&gt;Type conversion - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>data-types</category><category>how-to</category></item><item><title>C Syntax</title><link>https://www.wesleybertipaglia.com/blog/lpjhe7cg/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/lpjhe7cg/</guid><description>The building blocks every C program is made of: comments, statements, blocks, variables, constants, identifiers, and printing and reading input.</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Syntax is the shape of the language: what you are allowed to write and where. C&amp;#39;s syntax is tiny compared to most languages, and this post shows the pieces you will see in every file: comments, statements, blocks, variables, constants, and how to print text and read input. Conditionals and loops have their own posts later, so they are intentionally not covered here.&lt;/p&gt;
&lt;h2&gt;Comments&lt;/h2&gt;
&lt;p&gt;Comments are notes for humans; the compiler ignores them entirely. There are two forms:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// A single-line comment, from the // to the end of the line.

/*
   A multi-line comment.
   Everything between /* and */ is ignored.
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use them to explain &lt;em&gt;why&lt;/em&gt; the code does something. Code says what it does; a comment should say why.&lt;/p&gt;
&lt;h2&gt;Statements and blocks&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;statement&lt;/strong&gt; is one instruction, and it always ends with a semicolon:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int x = 5;
printf(&amp;quot;hi\n&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;strong&gt;block&lt;/strong&gt; is a group of statements wrapped in braces &lt;code&gt;{ ... }&lt;/code&gt;. Blocks group code, and they also define where variables are visible. Every &lt;code&gt;main&lt;/code&gt; function body is a block:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int main(void) {
    int x = 5;
    printf(&amp;quot;hi\n&amp;quot;);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Braces always come in pairs, and semicolons end statements, not braces. Forgetting a semicolon is the most common compile error a beginner hits.&lt;/p&gt;
&lt;h2&gt;Identifiers&lt;/h2&gt;
&lt;p&gt;An &lt;strong&gt;identifier&lt;/strong&gt; is a name you invent: variable names, function names. Rules are simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Letters, digits, and the underscore &lt;code&gt;_&lt;/code&gt; are allowed.&lt;/li&gt;
&lt;li&gt;Must start with a letter or &lt;code&gt;_&lt;/code&gt;, not a digit.&lt;/li&gt;
&lt;li&gt;No spaces, no dashes, no punctuation.&lt;/li&gt;
&lt;li&gt;Case matters: &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;Count&lt;/code&gt;, and &lt;code&gt;COUNT&lt;/code&gt; are three different names.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int studentCount = 3;   // valid
int 2ndPlace = 1;       // INVALID: starts with a digit
int total_amount = 10;  // valid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some words are reserved by the language and cannot be used as names, like &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;return&lt;/code&gt;, &lt;code&gt;if&lt;/code&gt;, and &lt;code&gt;while&lt;/code&gt;. They are the &lt;strong&gt;keywords&lt;/strong&gt; of the language, and your editor will color them differently.&lt;/p&gt;
&lt;h2&gt;Variables&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;variable&lt;/strong&gt; is a named box in memory that holds a value. You must declare it before using it. A declaration states the type and the name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int age;          // declares an int named age
double price;     // declares a double named price
int a, b, c;      // declares three ints in one line
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can give it a value at declaration time. That is called &lt;strong&gt;initialization&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int age = 30;
double price = 19.99;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or assign later with the &lt;code&gt;=&lt;/code&gt; operator. Assigning is an action, so it is a statement and needs its semicolon:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;age = 31;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rule is simple: declare before you use, initialize before you read. An uninitialized variable holds whatever garbage was already in memory, and reading it is a bug.&lt;/p&gt;
&lt;h2&gt;Constants&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;constant&lt;/strong&gt; is a value that never changes. The &lt;code&gt;const&lt;/code&gt; keyword marks a variable as read-only after initialization:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;const int maxUsers = 100;
maxUsers = 200;   // compile error: cannot assign to a const
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The language also has a preprocessor directive, &lt;code&gt;#define&lt;/code&gt;, that names a value, though &lt;code&gt;const&lt;/code&gt; is the safer modern choice. You will see both in real code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define MAX 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The preprocessor replaces every &lt;code&gt;MAX&lt;/code&gt; in the file with &lt;code&gt;100&lt;/code&gt; before the compiler runs. Note that &lt;code&gt;#define&lt;/code&gt; lines do &lt;strong&gt;not&lt;/strong&gt; end with a semicolon.&lt;/p&gt;
&lt;h2&gt;Printing and reading input&lt;/h2&gt;
&lt;p&gt;Printing uses &lt;code&gt;printf&lt;/code&gt;, and the &lt;code&gt;%&lt;/code&gt; symbols inside the string are replaced by the values you pass. Each &lt;code&gt;%?&lt;/code&gt; is a &lt;strong&gt;format specifier&lt;/strong&gt; describing how to print one value.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;stdio.h&amp;gt;

int main(void) {
    int age = 30;
    double grade = 9.5;
    printf(&amp;quot;I am %d years old.\n&amp;quot;, age);
    printf(&amp;quot;My grade is %.2f.\n&amp;quot;, grade);
    printf(&amp;quot;age=%d grade=%.2f\n&amp;quot;, age, grade);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The common specifiers: &lt;code&gt;%d&lt;/code&gt; prints an &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;%f&lt;/code&gt; prints a &lt;code&gt;double&lt;/code&gt; (and &lt;code&gt;%.2f&lt;/code&gt; prints it with two decimal places), &lt;code&gt;%c&lt;/code&gt; prints a single &lt;code&gt;char&lt;/code&gt;, &lt;code&gt;%s&lt;/code&gt; prints a string, and &lt;code&gt;%p&lt;/code&gt; prints an address (used later with pointers). The &lt;code&gt;\n&lt;/code&gt; inside a string is an &lt;strong&gt;escape sequence&lt;/strong&gt;, a way to write a character that is hard to type directly. The ones you will use constantly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;\n   newline      \t   tab
\&amp;quot;   double quote \\   backslash
%    percent      \0   null terminator (used with strings)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To print a literal percent sign, write &lt;code&gt;%%&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Reading input uses &lt;code&gt;scanf&lt;/code&gt;, the mirror image of &lt;code&gt;printf&lt;/code&gt;. Give it a specifier and the &lt;em&gt;address&lt;/em&gt; of the variable to fill in. The &lt;code&gt;&amp;amp;&lt;/code&gt; operator means &amp;quot;the address of&amp;quot;; you will understand it deeply in the pointers post, but for now know that &lt;code&gt;scanf(&amp;quot;%d&amp;quot;, &amp;amp;age)&lt;/code&gt; stores what the user typed into &lt;code&gt;age&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;stdio.h&amp;gt;

int main(void) {
    int age;
    printf(&amp;quot;How old are you? &amp;quot;);
    scanf(&amp;quot;%d&amp;quot;, &amp;amp;age);
    printf(&amp;quot;You are %d years old. Good to know!\n&amp;quot;, age);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The risky part of &lt;code&gt;scanf&lt;/code&gt;: string input with &lt;code&gt;%s&lt;/code&gt; does not limit length and can overflow the buffer, so prefer &lt;code&gt;fgets&lt;/code&gt; for reading text. Numbers via &lt;code&gt;%d&lt;/code&gt; and &lt;code&gt;%f&lt;/code&gt; are fine for learning.&lt;/p&gt;
&lt;h2&gt;Include and define live outside the code&lt;/h2&gt;
&lt;p&gt;Lines starting with &lt;code&gt;#&lt;/code&gt; are preprocessor directives[^1], not C statements. &lt;code&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/code&gt; copies the contents of the standard header into your file so the compiler knows the names of functions like &lt;code&gt;printf&lt;/code&gt;. Every program that prints or reads needs that one line at the top.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Every C file is built from the same alphabet: comments to explain, statements ended with &lt;code&gt;;&lt;/code&gt;, blocks wrapped in &lt;code&gt;{}&lt;/code&gt;, names that follow simple rules, variables you declare and initialize, and &lt;code&gt;printf&lt;/code&gt;/&lt;code&gt;scanf&lt;/code&gt; to talk to the user. That is the grammar you will type hundreds of times. The next post fills in the kinds of values those variables can hold: the data types.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/C_preprocessor&quot;&gt;C preprocessor - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>syntax</category><category>how-to</category></item><item><title>Getting Started with C</title><link>https://www.wesleybertipaglia.com/blog/6jrttirk/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/6jrttirk/</guid><description>What C is, what you need to write it, your first program, and how source code becomes an executable you can run.</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;C is one of the oldest programming languages still in daily use, and it has never dropped out of the top of the language rankings. It was created in the early 1970s by &lt;strong&gt;Dennis Ritchie&lt;/strong&gt;[^1] to write the Unix operating system, and it remains the language used to write operating systems, embedded devices, compilers, and the core of almost every other language. If you want to know what a computer really does when your code runs, C is the shortest path there: the language is small, and almost everything it does maps directly to what the hardware can do.&lt;/p&gt;
&lt;p&gt;This post sets you up: what you need installed, your first program, and the path your source code takes from your editor to a running executable.&lt;/p&gt;
&lt;h2&gt;What you need&lt;/h2&gt;
&lt;p&gt;C has no official IDE, no package manager, and no runtime you install. To write C you need exactly three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;A compiler.&lt;/strong&gt; The compiler[^2] translates your C source code into machine code the CPU can run. The two most common are &lt;code&gt;gcc&lt;/code&gt; (GNU Compiler Collection) and &lt;code&gt;clang&lt;/code&gt; (part of LLVM). Both are free and work on Linux, macOS, and Windows. On macOS you often get &lt;code&gt;clang&lt;/code&gt; for free with the Command Line Tools; on Linux install &lt;code&gt;gcc&lt;/code&gt; with your package manager; on Windows &lt;code&gt;gcc&lt;/code&gt; comes inside MinGW, MSYS2, or WSL.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A text editor.&lt;/strong&gt; Any editor works because a &lt;code&gt;.c&lt;/code&gt; file is just text. VS Code, Neovim, Vim, or a plain notepad all work. There is no special project format; you just type and save.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A terminal.&lt;/strong&gt; You will type a single command to build your program and another to run it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That is everything. Check that your compiler exists by asking it its version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;gcc --version
# or
clang --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the terminal prints a version, you are ready.&lt;/p&gt;
&lt;h2&gt;Your first program&lt;/h2&gt;
&lt;p&gt;Create a file called &lt;code&gt;hello.c&lt;/code&gt; and write this into it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;stdio.h&amp;gt;

int main(void) {
    printf(&amp;quot;Hello, world!\n&amp;quot;);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is a complete C program. It has three parts, and you will see the same skeleton in every C program you write:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/code&gt; brings in the declaration of &lt;code&gt;printf&lt;/code&gt;, the function used to print text. &lt;code&gt;stdio.h&lt;/code&gt; is the standard header for &lt;strong&gt;st&lt;/strong&gt;an&lt;strong&gt;d&lt;/strong&gt;ard &lt;strong&gt;i&lt;/strong&gt;nput/&lt;strong&gt;o&lt;/strong&gt;utput.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int main(void)&lt;/code&gt; defines the &lt;code&gt;main&lt;/code&gt; function, the entry point. Every C program starts here. When you run the program, the operating system calls &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;printf(&amp;quot;Hello, world!\n&amp;quot;);&lt;/code&gt; prints the text. &lt;code&gt;\n&lt;/code&gt; is an escape sequence meaning &amp;quot;new line&amp;quot;, so the cursor moves to the next line after printing. The line ends with &lt;code&gt;;&lt;/code&gt;, because every statement in C ends with a semicolon.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;return 0;&lt;/code&gt; tells the operating system the program finished successfully. A non-zero value signals an error.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The order inside &lt;code&gt;main&lt;/code&gt; is the order of execution: print the line, then return 0.&lt;/p&gt;
&lt;h2&gt;Build and run&lt;/h2&gt;
&lt;p&gt;Open a terminal in the folder that contains &lt;code&gt;hello.c&lt;/code&gt; and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;gcc hello.c -o hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Breaking that down: &lt;code&gt;gcc&lt;/code&gt; is the compiler, &lt;code&gt;hello.c&lt;/code&gt; is the input file, and &lt;code&gt;-o hello&lt;/code&gt; says &amp;quot;put the result in a file named &lt;code&gt;hello&lt;/code&gt;&amp;quot;. If the code is correct, this produces no output, just a new executable file. Now run it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;./hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;./&lt;/code&gt; says &amp;quot;run the file in this directory&amp;quot;. You should see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Hello, world!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the whole workflow: edit a &lt;code&gt;.c&lt;/code&gt; file, compile it with &lt;code&gt;gcc&lt;/code&gt;, run the executable. Every example in this series follows the same pattern.&lt;/p&gt;
&lt;h2&gt;What the compiler actually does&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;gcc hello.c&lt;/code&gt; looks like one step, but under the surface four steps happen in order[^3]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;hello.c (source)
   ↓ 1. preprocessor: handles #include and #define
   ↓ 2. compiler: C → assembly
   ↓ 3. assembler: assembly → machine code (object file)
   ↓ 4. linker: object files + libraries → hello (executable)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Preprocessing.&lt;/strong&gt; Lines starting with &lt;code&gt;#&lt;/code&gt; are handled first. &lt;code&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/code&gt; literally copies the contents of &lt;code&gt;stdio.h&lt;/code&gt; into your file, so the compiler knows what &lt;code&gt;printf&lt;/code&gt; looks like.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compiling.&lt;/strong&gt; The C code is translated into assembly, the human-readable form of the CPU&amp;#39;s instructions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assembling.&lt;/strong&gt; The assembly is translated into machine code and saved as an object file. In the step above the object file is temporary and deleted, but you can keep it with the &lt;code&gt;-c&lt;/code&gt; flag (&lt;code&gt;gcc -c hello.c&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linking.&lt;/strong&gt; Your object file is combined with pre-compiled libraries (like the code that actually knows how to talk to your terminal) into a single executable. This is the &lt;code&gt;hello&lt;/code&gt; file you run.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You do not need to remember the details, only that source code passes through these stages and that each one can report its own error. Oh, and there is no fifth &amp;quot;runtime&amp;quot; step: the executable you build is a finished piece of machine code. The same compiler command produces it on any platform, which is why C is called &lt;strong&gt;portable&lt;/strong&gt;: move the source to another machine, compile there, and it runs natively.&lt;/p&gt;
&lt;h2&gt;Compiling with warnings&lt;/h2&gt;
&lt;p&gt;Real C projects always compile with extra warnings enabled, because the compiler notices a lot of mistakes before your program runs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;gcc -Wall -Wextra hello.c -o hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-Wall&lt;/code&gt; and &lt;code&gt;-Wextra&lt;/code&gt; turn on a useful set of warnings. Treat every warning as a bug and fix it. The compiler is your first and fastest test suite.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;C is just three things to start: a compiler (&lt;code&gt;gcc&lt;/code&gt; or &lt;code&gt;clang&lt;/code&gt;), a text file ending in &lt;code&gt;.c&lt;/code&gt;, and a build command (&lt;code&gt;gcc file.c -o program&lt;/code&gt;). Your first program prints a line and returns zero. From here the series takes each piece of the language one at a time, starting with the syntax you will use in every single program you ever write.&lt;/p&gt;
&lt;p&gt;The rest of the series uses the same commands everywhere, so this post is your reference whenever an example needs to be compiled.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Dennis_Ritchie&quot;&gt;Dennis Ritchie - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Compiler&quot;&gt;Compiler - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Compiler&quot;&gt;Compiler - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>c</category><category>programming</category><category>getting-started</category><category>how-to</category></item><item><title>What Happens When You Type google.com?</title><link>https://www.wesleybertipaglia.com/blog/11vojlj5/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/11vojlj5/</guid><description>The capstone of the Networks Explained series: the full lifecycle of a single web request across every layer-DNS lookup, TCP handshake, TLS negotiation, HTTP GET, packet routing, ARP resolution, and frame delivery.</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This series built a network one layer at a time: what a network is for, the &lt;a href=&quot;/blog/82r60uj7&quot;&gt;seven-layer model&lt;/a&gt;, the &lt;a href=&quot;/blog/8ymy4yyc&quot;&gt;real stack&lt;/a&gt;, &lt;a href=&quot;/blog/ri76k15u&quot;&gt;signals&lt;/a&gt;, &lt;a href=&quot;/blog/esoqqx40&quot;&gt;frames&lt;/a&gt;, &lt;a href=&quot;/blog/y9rgzb70&quot;&gt;packets&lt;/a&gt;, &lt;a href=&quot;/blog/cj1ck7im&quot;&gt;connections&lt;/a&gt;, &lt;a href=&quot;/blog/2fai7eui&quot;&gt;HTTP&lt;/a&gt;, and &lt;a href=&quot;/blog/g9i0vflz&quot;&gt;TLS&lt;/a&gt;. Every post assumed the network already worked. This one stops assuming and follows a single request from the moment a key is pressed to the moment a page appears, watching all nine posts happen at once.&lt;/p&gt;
&lt;h2&gt;Step 0: the key press&lt;/h2&gt;
&lt;p&gt;You type &lt;code&gt;google.com&lt;/code&gt; and press enter. Before a single packet moves, your browser decides four things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What kind of request&lt;/strong&gt; this is. No scheme typed means the browser applies its default, and for any domain it recognizes, that default is now &lt;code&gt;https&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Whether it already knows the answer.&lt;/strong&gt; If you have visited this site before, the address may be in the DNS cache and even in the TLS session store, and parts of this whole journey are skipped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Where to look first.&lt;/strong&gt; The browser checks its own cache, then the operating system&amp;#39;s cache, then the hosts file, then the resolver&amp;#39;s cache, and only then asks the network.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What to do with the response.&lt;/strong&gt; How to render it, and which parts of the page it will need next.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is worth pausing on: a large fraction of page loads never touch the network at all. Everything below describes the slow path, the one that happens when nothing is cached.&lt;/p&gt;
&lt;h2&gt;Step 1: a name becomes an address&lt;/h2&gt;
&lt;p&gt;Your program cannot send anything to &lt;code&gt;google.com&lt;/code&gt;. It can only send to an address, so the first question is DNS[^1].&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;browser: any cached address for google.com?
   └─ no

OS resolver: hosts file? cache?
   └─ no

ask the resolver (your provider):
   ├─ cache still valid? (TTL not expired) → return 142.250.78.100
   └─ not cached → walk the hierarchy:
        root       &amp;quot;where does .com live?&amp;quot;        → the .com servers
        .com       &amp;quot;where does google.com live?&amp;quot; → the authoritative servers
        google     &amp;quot;ask me&amp;quot;                      → 142.250.78.100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each answer in that chain comes back with a TTL, and every machine along the way caches it. When you visit the site an hour later, the answer comes from a cache in microseconds and no question is asked anywhere.&lt;/p&gt;
&lt;p&gt;DNS answers with more than an address. It may return several, choose between IPv4 and IPv6, and record other things about the domain, like which servers handle email.&lt;/p&gt;
&lt;h2&gt;Step 2: local or remote&lt;/h2&gt;
&lt;p&gt;Your machine now holds an address. The next decision is the one from the &lt;a href=&quot;/blog/esoqqx40&quot;&gt;link layer post&lt;/a&gt; and the &lt;a href=&quot;/blog/y9rgzb70&quot;&gt;network layer post&lt;/a&gt;: is that address on my network?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;your machine: 192.168.0.15, netmask /24  → 192.168.0.0/24 is &amp;quot;mine&amp;quot;
destination:  142.250.78.100               → not in 192.168.0.0/24

→ longest match in my routing table is the default route
→ the next hop is my gateway, 192.168.0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The IP layer now knows a fact it could not know before: the frame leaving this machine is not addressed to the destination. It is addressed to the router, because the router is the only machine on this wire that can help.&lt;/p&gt;
&lt;h2&gt;Step 3: the local address question, again&lt;/h2&gt;
&lt;p&gt;To send that frame, your machine needs the router&amp;#39;s MAC address, and IP addresses do not tell it. So the same question from the ARP[^2] section returns:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;quot;who has 192.168.0.1? tell aa:bb:cc:dd:ee:ff&amp;quot;
   ◀── everyone on the LAN hears it (broadcast)
       the router replies, and your machine caches the answer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the only broadcast in the whole journey, and it never leaves your local network. From this moment on, every frame of this request is addressed to the router&amp;#39;s MAC and carries the destination IP inside its payload.&lt;/p&gt;
&lt;h2&gt;Step 4: opening the connection&lt;/h2&gt;
&lt;p&gt;The transport layer does its &lt;a href=&quot;/blog/cj1ck7im&quot;&gt;handshake&lt;/a&gt;[^3], one round trip of three packets:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;client ── SYN      seq 1000 ─────────────▶ server
client ◀── SYN ACK  seq 5000, ack 1001 ── server
client ── ACK      ack 5001 ────────────▶ server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each packet is wrapped on the way down: TCP header inside an IP header inside an Ethernet frame inside signal changes on the wire. The client&amp;#39;s kernel assigns an ephemeral source port, say 54321, and the server&amp;#39;s is 443, so the four-tuple identifies this conversation uniquely. The kernel now knows the server will not lose data, reorder it, or duplicate it. It still has not encrypted anything.&lt;/p&gt;
&lt;h2&gt;Step 5: making it private&lt;/h2&gt;
&lt;p&gt;Then the &lt;a href=&quot;/blog/g9i0vflz&quot;&gt;TLS handshake&lt;/a&gt;[^4], another round trip, and the reason nobody reads your traffic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;client ── ClientHello: TLS 1.3, cipher suites, server name: google.com ──▶

client ◀── ServerHello + certificate + key share ──

      client verifies the chain up to a root it trusts,
      runs the key exchange, derives the shared secret

client ── Finished ─────────────────────────────────────────────────────▶

      from here, every byte is encrypted
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three things just happened in about one round trip: the server proved its identity by presenting a certificate your browser can chain to a trusted root, both sides derived a key nobody watching could compute, and the channel became authenticated, so nobody can sit in the middle and relay it. In HTTP/3 this happens over QUIC on UDP, in one round trip total, with the transport rebuilt in the browser rather than in the kernel.&lt;/p&gt;
&lt;h2&gt;Step 6: the request&lt;/h2&gt;
&lt;p&gt;Now the top layer finally speaks, and the actual request is about 500 bytes of plain text:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET / HTTP/2
Host: google.com
User-Agent: Mozilla/5.0 ...
Accept: text/html
Cookie: ...

HTTP/2 200 OK
content-type: text/html; charset=utf-8
content-encoding: br
set-cookie: ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Somewhere on the far side, a machine that was waiting on port 443 accepts the connection, decrypts it, reads the request, and hands it to a web server. The web server may find the answer in a cache a few kilometers away, or ask a database, or construct the page. Whatever happens there is application logic. From this network&amp;#39;s point of view, the server simply answers.&lt;/p&gt;
&lt;p&gt;The answer comes back, and the browser starts rendering while the rest is still arriving.&lt;/p&gt;
&lt;h2&gt;Step 7: the journey back, layer by layer&lt;/h2&gt;
&lt;p&gt;The response is the same machinery in reverse, and this is where the layering becomes visible in one place:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;encapsulation, sending side:

  [ HTTP 200 ]                         application
  [ TCP | port 443 | ack | HTTP 200 ]  transport   ← reliable, ordered
  [ IP | 142.250.78.100 | TCP | ... ] network    ← addressed to the server
  [ Eth | router&amp;#39;s MAC | IP | ... ]   link        ← addressed to the next door
  10110010 0011...                     physical     ← volts, light, or waves

at each hop, in the middle of the network:

  router takes in a frame
    ├─ reads the destination MAC: not me? → forward
    ├─ reads the destination IP: not mine? → look up the routing table
    ├─ longest prefix match says &amp;quot;next hop is this neighbour&amp;quot;
    ├─ rewrite the IP header: my address becomes the source, next hop&amp;#39;s the destination
    ├─ decrement the TTL
    ├─ rebuild the frame with the next neighbour&amp;#39;s MAC address
    └─ push it out the next port
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Watch the two identity swaps on every single hop, because they are the elegant part of the whole design. The &lt;strong&gt;IP addresses stay the same&lt;/strong&gt; from source to destination, end to end, carrying the identity of the conversation. The &lt;strong&gt;MAC addresses change every time&lt;/strong&gt;, because each link is a separate physical world with its own neighbours. The payload is never opened. The router does not know or care whether the bytes are a video, a bank transfer, or a page; it is moving an envelope forward.&lt;/p&gt;
&lt;p&gt;And somewhere on this path, a packet is lost. Nothing notices yet: the loss only exists as the absence of an acknowledgment. The receiver&amp;#39;s acknowledgment for that range stops advancing, the sender sees it, resends the segment, and the receiver recognizes the number as one it already has and silently discards the duplicate. From your side, the page simply takes a few milliseconds longer.&lt;/p&gt;
&lt;h2&gt;Step 8: what it all cost&lt;/h2&gt;
&lt;p&gt;The interesting question is not what happened but where the time went, because the answer is mostly not the network moving data.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;around a server 30 ms away:

  DNS lookup                5-30 ms   one round trip to a resolver
  TCP handshake             30 ms     1 RTT
  TLS handshake             30 ms     1 RTT (HTTP/3 saves this one)
  request + first bytes    ~30 ms     1 RTT, plus the server&amp;#39;s work
                           ───────
  first paint             ~100-150 ms

moving the megabytes that follow is mostly bandwidth, not latency
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same page from another continent adds about 100 ms to every single round trip, which is exactly why protocols moved to one-round-trip handshakes and why streaming starts rendering before the page is complete. This is the concrete payoff of the numbers in the &lt;a href=&quot;/blog/ri76k15u&quot;&gt;physical layer post&lt;/a&gt;: distance is measured in hops and milliseconds, not in kilometers, and no amount of bandwidth changes it.&lt;/p&gt;
&lt;p&gt;Then the browser asks for the stylesheet, the logo, and the script. Each one is another request over the same connection in HTTP/2 or HTTP/3, multiplexed so a slow script never delays the logo, and cached so a second visit asks for almost nothing at all.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;One typed string, and every post in this series happens in the space of a few hundred milliseconds: a name resolved through a distributed caching hierarchy, a local decision about which machine to hand the frame to, an ARP broadcast to learn a hardware address, a three-message handshake to open a reliable connection, a certificate and a key exchange to make it private, a five-hundred-byte text request, and a response that travels back through nineteen hops[^5], each rewriting its envelope and forwarding by longest prefix match, while a lost packet is noticed only as a missing acknowledgment.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;you type google.com
  │
  ├─ DNS: name → address              (a cached hierarchy)
  ├─ routing table: not local → gateway
  ├─ ARP: gateway&amp;#39;s MAC address        (the one broadcast)
  ├─ TCP: SYN, SYN-ACK, ACK           (reliable, ordered, byte stream)
  ├─ TLS: certificate + key exchange   (private, authenticated)
  ├─ HTTP: GET /  →  200 OK           (meaning, finally)
  │
  ▼
the same machinery in reverse, plus a renderer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three series now sit on one shelf. The &lt;a href=&quot;/blog/2lfin2nu&quot;&gt;Computers Explained&lt;/a&gt; series built the machine that runs your browser: bits, logic, a processor, memory, storage, and I/O. The &lt;a href=&quot;/blog/zkb3xkdy&quot;&gt;Operating Systems Explained&lt;/a&gt; series explained how that machine stays yours while a hundred programs share it, and how it boots from a cold press of power. This series took the last step: how that machine talks to other machines it knows nothing about, and how a string of characters typed into a browser becomes a page by agreeing, layer by layer, on what a bit, a frame, a packet, a connection, a request, and a private conversation are.&lt;/p&gt;
&lt;p&gt;The protocols will keep changing. HTTP/4 will eventually replace HTTP/3, and something will replace TCP. But the questions will not: who is this for, where is it going, how do I know it arrived, and can I trust the answer? Those four questions are the network, and they are older than the machines carrying them.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Domain_Name_System&quot;&gt;Domain Name System - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Address_Resolution_Protocol&quot;&gt;Address Resolution Protocol - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Three-way_handshake&quot;&gt;Three-way handshake - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Transport_Layer_Security&quot;&gt;Transport Layer Security - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Border_Gateway_Protocol&quot;&gt;Border Gateway Protocol - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>networking</category><category>http</category><category>dns</category><category>how-it-works</category></item><item><title>Network Security &amp; TLS</title><link>https://www.wesleybertipaglia.com/blog/g9i0vflz/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/g9i0vflz/</guid><description>A deep dive into securing network communications: symmetric vs. asymmetric encryption, public key infrastructure (PKI), TLS handshakes, firewalls, NAT, and common network security primitives.</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Everything in this series so far has been in the clear. Your packets were described as frames, segments, and headers, and every one of those words means a machine you do not control could read them. The &lt;a href=&quot;/blog/2fai7eui&quot;&gt;application post&lt;/a&gt; left that open: the network is a public road with strangers on it, and HTTP is printed in the clear. This post closes the loop. What an attacker can do, the one piece of arithmetic that makes private conversations possible on a public network, who is trusted to say whose name is whose, and what all of it still fails to protect.&lt;/p&gt;
&lt;h2&gt;Assume the road is watched&lt;/h2&gt;
&lt;p&gt;Three things can go wrong on a network, and it is worth naming them precisely because every defense maps to one of them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eavesdropping.&lt;/strong&gt; Someone reads what you send. Every router on the path, every provider along the way, and anyone on the same Wi-Fi can read plaintext HTTP.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tampering.&lt;/strong&gt; Someone changes what you send in flight. Without protection, a machine in the middle can rewrite a payment amount or inject a script into a page, and neither end notices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Impersonation.&lt;/strong&gt; Someone pretends to be the other end. If nobody vouches for names, the machine answering your request could be anyone at all.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In cryptography, the rule of thumb is that the network is hostile. Design assuming every packet is being read and written, and confidentiality becomes a property of the message instead of a property of the road.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; on an open Wi-Fi network, a device that only forwards traffic can sit between you and a website, read unencrypted requests, and serve its own pages instead. With HTTPS, that device sees which host you are talking to and nothing about what you are saying.&lt;/p&gt;
&lt;h2&gt;Two kinds of lock&lt;/h2&gt;
&lt;p&gt;Cryptography offers two families, and the difference between them is the reason the internet works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Symmetric encryption&lt;/strong&gt;[^1] uses one shared key for both encrypting and decrypting. It is fast, simple, and strong. Its only problem is distribution: to talk to a server you must already possess a secret it also possesses, and there is no way to send the first secret securely without already having one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Asymmetric encryption&lt;/strong&gt;[^2] comes as a pair: a &lt;strong&gt;public key&lt;/strong&gt; you give to the world and a &lt;strong&gt;private key&lt;/strong&gt; you keep. What one can do, the other can undo. Encrypt with the public key and only the private key can decrypt. This solves distribution: an open, unauthenticated channel can carry a public key safely, because an attacker who substitutes his own key is the problem the next section addresses.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;symmetric:     one shared secret           fast, both directions

asymmetric:    public key (share it)       slow, solves the chicken-and-egg
               private key (never share)   problem of getting the secret there
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Neither is usable alone. Asymmetric cryptography is roughly a hundred times slower than symmetric and cannot encrypt a large document efficiently. Symmetric is fast but cannot start the conversation. Real security uses both, in a pattern called &lt;strong&gt;hybrid encryption&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. asymmetric  prove identity, agree on a shared secret
2. symmetric   encrypt the actual data with that secret
3. symmetric   the data is encrypted separately in each direction
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The bulk of the traffic is cheap symmetric encryption. Expensive asymmetric math happens once, at the start, to establish a secret that the fast layer then uses. Every encrypted connection on earth is this pattern.&lt;/p&gt;
&lt;h2&gt;Certificates: someone has to vouch&lt;/h2&gt;
&lt;p&gt;Asymmetric keys solve the transport of a secret. They do not solve the harder problem of identity, and this is the part people skip. If a machine in the middle sends you its own public key and claims to be your bank, the cryptography works perfectly and you have just been robbed.&lt;/p&gt;
&lt;p&gt;The answer is a third party everyone already trusts: the &lt;strong&gt;certificate authority&lt;/strong&gt;[^3]. A &lt;strong&gt;public key certificate&lt;/strong&gt;[^4] is a public key plus a statement about who owns it, signed by an authority. It is the digital equivalent of a notarized stamp: not that the claim is certainly true, but that someone paid to check.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;your browser
   │  certificate: &amp;quot;example.com&amp;#39;s key is correct&amp;quot;
   │  signed by: Let&amp;#39;s Encrypt Authority X
   │  signed by: an authority in the browser&amp;#39;s built-in root list
   ▼
the browser checks the signatures back up to a root it trusts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The browser ships a list of &lt;strong&gt;root certificates&lt;/strong&gt; it was built with. If a chain of signatures leads from the server&amp;#39;s certificate to one of those roots, the browser accepts the identity. That entire trust system is &lt;strong&gt;public key infrastructure&lt;/strong&gt;[^5], and the trust decisions were made long before your connection, baked into the browsers and operating systems you installed.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;certificate chain:

root (trusted by your browser, pre-installed)
  └── intermediate (vouches for the next one)
        └── leaf: example.com&amp;#39;s public key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two consequences follow from this design. First, certificates &lt;strong&gt;expire&lt;/strong&gt;, because a certificate with no end date could never be revoked quietly. Second, &lt;strong&gt;automated issuance&lt;/strong&gt; exists: services like Let&amp;#39;s Encrypt issue certificates automatically to anyone who proves control of a domain, which is why enabling HTTPS went from an annual project to a single command.&lt;/p&gt;
&lt;h2&gt;The TLS handshake&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;TLS&lt;/strong&gt;[^6] is the protocol that runs this pattern on every HTTPS connection. It happens in milliseconds, before any of your actual request, and it is the moment where the previous four sections all meet.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;client                                   server
  │ ClientHello                            │
  │  - supported versions                  │
  │  - supported cipher suites             │
  │  - &amp;quot;I want TLS 1.3&amp;quot;                    │
  │  - server name: example.com            │
  │───────────────────────────────────────▶│
  │                          ServerHello    │
  │                          certificate   │
  │                          its key       │
  │◀───────────────────────────────────────│
  │                          Finished      │
  │◀───────────────────────────────────────│
  │ Finished                               │
  │───────────────────────────────────────▶│
  │                                         │
  ▼  both now share a secret                ▼  real data, encrypted
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Step by step, because each step answers one problem:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ClientHello&lt;/strong&gt; lists what the client supports and, in the &lt;strong&gt;server name indication&lt;/strong&gt;[^7] field, which host it wants. This is why one IP address can serve thousands of sites and why that field, sent in the clear, leaks which site you are visiting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ServerHello&lt;/strong&gt; picks one version and cipher, and sends the &lt;strong&gt;certificate&lt;/strong&gt; plus the server&amp;#39;s public key.&lt;/li&gt;
&lt;li&gt;The client verifies the certificate back to a root it trusts. This is the step that makes impersonation fail: a fake certificate does not chain to a trusted root.&lt;/li&gt;
&lt;li&gt;Both sides run a &lt;strong&gt;key exchange&lt;/strong&gt;[^8] over those public keys to derive the same secret without ever putting it on the wire. In TLS 1.3 the client also sends its own contribution, so a middle machine cannot negotiate a secret and silently relay it. The channel is &lt;strong&gt;authenticated&lt;/strong&gt;, not just encrypted.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Finished&lt;/strong&gt; messages prove each side actually derived the same keys, which closes the last gap: an attacker who only forwards traffic is detected.&lt;/li&gt;
&lt;li&gt;From here the connection is encrypted and every HTTP request rides inside it.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1 RTT: handshake with a session ticket from a previous connection
0 RTT: resumption using a ticket, data sent immediately
      (safe only for operations that tolerate replay)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;ephemeral&lt;/strong&gt; part of the key exchange is what gives &lt;strong&gt;forward secrecy&lt;/strong&gt;[^9]. Because the key used for a session is thrown away when the session ends, someone who records traffic for months and later obtains the server&amp;#39;s private key still cannot read the old conversation. The certificate&amp;#39;s key proves identity; the throwaway key does the work.&lt;/p&gt;
&lt;h2&gt;What TLS does not do&lt;/h2&gt;
&lt;p&gt;Honest limits, because overclaiming here causes real damage.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It protects data in transit, not endpoints.&lt;/strong&gt; A server that logs your messages in plaintext hands over everything anyway. TLS moves the trust boundary; it does not remove it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It does not hide metadata.&lt;/strong&gt; The addresses, ports, sizes, and timings are still visible, and so is the hostname in the server name indication. Someone watching knows who you talked to, not what you said.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It does not protect against a compromised authority.&lt;/strong&gt; Whoever controls a trusted root can issue a certificate for any name. This is the entire weakness of the system, and the reason some people pin certificates or distrust authorities entirely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It does not cover everything else.&lt;/strong&gt; Plain HTTP is still everywhere, from internal tools to old APIs, and plain protocols like DNS and email remain largely unprotected by default.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Firewalls and NAT&lt;/h2&gt;
&lt;p&gt;Two network features get confused with security constantly, so here is what each actually is.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;firewall&lt;/strong&gt;[^10] filters traffic. Rules decide which connections are allowed in and out, and a &lt;strong&gt;stateful&lt;/strong&gt; firewall remembers which connections it has seen permitted, so replies come back automatically. A firewall protects a boundary: it does not read traffic, and it cannot help once traffic is allowed through.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NAT&lt;/strong&gt;[^11], from the &lt;a href=&quot;/blog/y9rgzb70&quot;&gt;network layer post&lt;/a&gt;, is address translation, and it exists because of the shortage of IPv4 addresses, not for security. Inside a network, many machines share one public address; the router rewrites addresses on the way out and keeps a table of who asked for what.&lt;/p&gt;
&lt;p&gt;NAT has one genuine security side effect: a machine behind NAT has no publicly reachable address, so unsolicited connections cannot reach it. That is a side effect of address translation, not a design goal, and it is why a home router looks protected in ways a corporate network does not. It is not a security control, and it should never be used as one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; the difference shows up the moment you add a port forwarding, or a hole punched for a call. Opening one path to a machine behind NAT removes the accidental protection of that address hiding, and the machine is exposed to the internet directly.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;A public network means eavesdropping, tampering, and impersonation are all on the table, so security has to be a property of the message, not of the road. Symmetric encryption is fast but needs a secret already shared; asymmetric keys can carry a secret across a public channel but are slow, so real protocols use both. Certificates and a chain of trusted authorities turn a public key into a proven identity, and the TLS handshake combines verification, key exchange, and proof into a private channel in one round trip. Firewalls filter; NAT translates addresses and only incidentally hides machines. And all of it protects data in transit only.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;client ─── ClientHello ───────────────────▶ server
         ◀── certificate + ephemeral key ──
         verify chain → derive secret → Finished
         ▼
      encrypted HTTP, unreadable to every router in between
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One post left, and it is the one this series was building toward. The final post takes everything above and follows it in real time: one machine, one address bar, and the full journey of a single request across all the layers, from the first letter typed to the first byte rendered.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Symmetric-key_cryptography&quot;&gt;Symmetric-key cryptography - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Public-key_cryptography&quot;&gt;Public-key cryptography - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Certificate_authority&quot;&gt;Certificate authority - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Public_key_certificate&quot;&gt;Public key certificate - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Public_key_infrastructure&quot;&gt;Public key infrastructure - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Transport_Layer_Security&quot;&gt;Transport Layer Security - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Server_Name_Indication&quot;&gt;Server Name Indication - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Key_exchange&quot;&gt;Key exchange - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Forward_secrecy&quot;&gt;Forward secrecy - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Firewall_(computing)&quot;&gt;Firewall (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Network_address_translation&quot;&gt;Network address translation - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>networking</category><category>security</category><category>tls</category><category>how-it-works</category></item><item><title>The Application Layer &amp; HTTP</title><link>https://www.wesleybertipaglia.com/blog/2fai7eui/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/2fai7eui/</guid><description>A deep dive into client-server communication: domain name system (DNS), client vs. server roles, HTTP/1.1 to HTTP/3 evolution, WebSockets, REST, and state management over stateless protocols.</description><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/cj1ck7im&quot;&gt;transport layer post&lt;/a&gt; left you holding a stream of bytes you can trust. Trustworthy bytes are still meaningless noise until someone agrees what they say. That agreement is the application layer: the top floor, the only one your program sees, and the place where the internet turned into something people actually use. This post opens it: who talks to whom, how names become addresses, and how one protocol designed in the 1990s grew into the most-deployed application on earth.&lt;/p&gt;
&lt;h2&gt;Client and server&lt;/h2&gt;
&lt;p&gt;The simplest division in computing: one program &lt;strong&gt;waits&lt;/strong&gt;, the other &lt;strong&gt;asks&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;server&lt;/strong&gt; opens a port and waits for incoming connections. A &lt;strong&gt;client&lt;/strong&gt; opens a connection and starts the conversation. The roles belong to the endpoints, not to the machines: your browser is a server when your colleague loads a page from the little app you wrote.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;client                                         server
   │  &amp;quot;send me the page&amp;quot;        ───────────────▶ │
   │                                              │
   │  &amp;quot;here it is&amp;quot;              ◀─────────────── │
   │                                              │
   ▼  connection closed                          ▼  waits for the next
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This request-response shape is called &lt;strong&gt;client-server&lt;/strong&gt;, and it is one of two patterns in wide use. The other is &lt;strong&gt;peer-to-peer&lt;/strong&gt;, where every machine both asks and offers, which is what file sharing and some video calling use.&lt;/p&gt;
&lt;p&gt;The word &amp;quot;application&amp;quot; covers a wide range of things, including two things that are not really applications at all: &lt;strong&gt;DNS&lt;/strong&gt;, which turns names into addresses, and &lt;strong&gt;TLS&lt;/strong&gt;, which encrypts the conversation. Neither shows up in your code as a function call, and that is exactly the &lt;a href=&quot;/blog/8ymy4yyc&quot;&gt;end-to-end idea&lt;/a&gt; in practice: the network moves bytes, and everything meaningful happens at the ends.&lt;/p&gt;
&lt;h2&gt;URLs: naming a thing on the web&lt;/h2&gt;
&lt;p&gt;Before any request, you need to name the thing. The &lt;strong&gt;URL&lt;/strong&gt;[^1] is that name, and its anatomy explains more about the web than any design document.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://example.com:443/docs/page?q=networking#requests
│     │             │   │        │        │        │
│     │             │   │        │        │        └ fragment: never sent
│     │             │   │        │        └────────── query: parameters
│     │             │   │        └─────────────────── path: which resource
│     │             │   └──────────────────────────── port: usually implicit
│     │             └──────────────────────────────── host: which machine
│     └────────────────────────────────────────────── scheme: which protocol
└─────────────────────────────────────────────────── everything
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each part answers a question at a different layer, which is why a URL feels layered even though it is one string. The scheme says which application protocol to speak, the host says which machine, the port says which program on it, and the path says which resource that program should produce. The fragment is a detail for your browser only; it is not sent to the server at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a browser given &lt;code&gt;https://example.com:443/docs&lt;/code&gt; sends &lt;code&gt;GET /docs&lt;/code&gt; to port 443, because the scheme already implies HTTPS and 443 is its default port. A URL carries more information than it appears to, and the parts it omits are the parts it assumes.&lt;/p&gt;
&lt;h2&gt;DNS: names into addresses&lt;/h2&gt;
&lt;p&gt;Machines have IP addresses. Humans have names. &lt;strong&gt;DNS&lt;/strong&gt;[^2] is the system that connects them, and it is one of the oldest, strangest, and most consequential pieces of the internet.&lt;/p&gt;
&lt;p&gt;It is distributed on purpose. The whole system answers one question, &lt;em&gt;what IP address does this name have?&lt;/em&gt;, and it answers it by asking strangers in a fixed order, caching aggressively so nobody has to ask often.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;your machine does not know the answer
   │
   ▼ asks the resolver (your provider, usually a small program near you)
   │
   ├─ cached? → answer immediately             (this is most of the time)
   │
   ▼ not cached, ask the root server: &amp;quot;where does .com live?&amp;quot;
   │  → &amp;quot;ask the .com server&amp;quot;
   ▼ ask the .com server: &amp;quot;where does example.com live?&amp;quot;
   │  → &amp;quot;ask this server&amp;quot;
   ▼ ask the authoritative server: &amp;quot;192.0.2.1, and here are its records&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The hierarchy mirrors how knowledge is distributed: root knows the top-level domains, the top-level domain knows the authoritative servers, the authoritative server knows one zone and is the only one allowed to give the final answer. Everyone above can point downward without knowing the details, which is what makes the system scale to a few hundred million names.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Caching&lt;/strong&gt; is what makes it usable. Every answer comes with a &lt;strong&gt;TTL&lt;/strong&gt;[^3], a lifetime in seconds. The resolver, the browser, and the operating system each keep answers until their TTL expires, so a popular name is asked about almost never again. This is also the property that causes pain: for up to a few hours after a record changes, part of the internet is still answering with the old address.&lt;/p&gt;
&lt;p&gt;DNS answers more than addresses. &lt;strong&gt;Records&lt;/strong&gt;[^4] come in types, and the type says what the answer is: an A record is an IPv4 address, AAAA is IPv6, a CNAME is another name, an MX record says where email for this domain should be delivered, an NS record says which server is authoritative. That is why changing an email provider and changing a website are both DNS edits, and why the two feel unrelated to users.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; &lt;code&gt;dig&lt;/code&gt; and &lt;code&gt;nslookup&lt;/code&gt; ask DNS questions directly, so when people say &amp;quot;DNS is broken&amp;quot; it usually means either a name does not resolve or it resolves to the wrong place. The last mile of the web is famously a DNS problem more often than a server problem.&lt;/p&gt;
&lt;h2&gt;HTTP: the request and the response&lt;/h2&gt;
&lt;p&gt;With a name and an address, two programs can talk. &lt;strong&gt;HTTP&lt;/strong&gt;[^5] is the protocol that decides what to say, and it is a text protocol: readable lines, human-inspectable, about as simple as a protocol can be.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET /docs/page?q=networking HTTP/1.1     ← the request

Host: example.com
User-Agent: Mozilla/5.0 ...
Accept: text/html
Connection: keep-alive

HTTP/1.1 200 OK                          ← the response

Content-Type: text/html; charset=utf-8
Content-Length: 4821
Cache-Control: max-age=3600

&amp;lt;html&amp;gt;...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A request has a &lt;strong&gt;method&lt;/strong&gt;, a path, and &lt;strong&gt;headers&lt;/strong&gt;, which carry control information rather than content. A response has a &lt;strong&gt;status code&lt;/strong&gt;, headers, and a body. The methods are the whole vocabulary of the web in five words:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GET&lt;/strong&gt;: fetch something. Should not change anything on the server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;POST&lt;/strong&gt;: submit something, or ask for an action.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PUT&lt;/strong&gt; and &lt;strong&gt;PATCH&lt;/strong&gt;: replace or modify something that exists.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DELETE&lt;/strong&gt;: remove something.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rule underneath is that GET should be safe to repeat. Your browser retries failed GETs, prefetches links, and crawls pages. If GET could charge a card, the web could not work.&lt;/p&gt;
&lt;p&gt;Status codes are grouped by their first digit, which is enough to know what kind of problem you have: &lt;strong&gt;2xx&lt;/strong&gt; worked, &lt;strong&gt;3xx&lt;/strong&gt; look elsewhere, &lt;strong&gt;4xx&lt;/strong&gt; you asked wrong, &lt;strong&gt;5xx&lt;/strong&gt; the server failed. Distinguishing them is a real skill, because &amp;quot;404&amp;quot; means the resource is missing while &amp;quot;502&amp;quot; means a gateway in front of your server is broken. Both look like failure to a user and mean completely different things to whoever fixes it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; &lt;code&gt;curl -v https://example.com&lt;/code&gt; prints the exact conversation above, headers and all, in order. Reading HTTP with curl is the fastest way to see that a web framework is just a library that builds this text and parses the reply.&lt;/p&gt;
&lt;h2&gt;Stateless, and where state comes from&lt;/h2&gt;
&lt;p&gt;HTTP is &lt;strong&gt;stateless&lt;/strong&gt;[^6]: the server does not remember your previous request. Each request stands alone, and the client carries everything needed to identify itself.&lt;/p&gt;
&lt;p&gt;That is a deliberate design, and it is the reason the web scaled to enormous traffic. Any server in a pool can answer any request. A machine can crash and another takes over instantly. There is no shared session, no sticky routing, no single point of failure.&lt;/p&gt;
&lt;p&gt;The cost is that servers cannot remember anything by themselves, so state has to travel with every request. &lt;strong&gt;Cookies&lt;/strong&gt;[^7] are that mechanism: small pieces of data the server sets, the browser stores, and sends back on every subsequent request to that domain.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;server → Set-Cookie: session=abc123
client → stores it
client → GET /cart      Cookie: session=abc123
client → GET /checkout  Cookie: session=abc123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server looks up &lt;code&gt;abc123&lt;/code&gt; in its own store, so the &lt;em&gt;reference&lt;/em&gt; travels while the &lt;em&gt;state&lt;/em&gt; stays put. When you clear cookies, the server loses its pointer to you. That is statelessness working exactly as designed, with the state living somewhere else.&lt;/p&gt;
&lt;h2&gt;Three versions of HTTP&lt;/h2&gt;
&lt;p&gt;The protocol is old and the transport under it changed under its feet, so HTTP evolved in steps, each fixing the previous version&amp;#39;s worst problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HTTP/1.0&lt;/strong&gt; opened a fresh connection per request. Simple, and expensive: every request paid for a full handshake.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HTTP/1.1&lt;/strong&gt; added &lt;strong&gt;keep-alive&lt;/strong&gt;, so one connection carries many requests. It also allowed &lt;strong&gt;pipelining&lt;/strong&gt;: send request 2 before request 1&amp;#39;s response arrives. Both sound like wins and were both nightmares, for the same reason. HTTP/1.1 processes responses strictly in order, so if request 1 is slow, every response behind it waits, even if their data is already sitting there. This is head-of-line blocking again, this time at the application layer, and it is why real HTTP/1.1 sites open six connections per browser tab and shard their domain across hostnames.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HTTP/2&lt;/strong&gt;[^8] fixed that by changing the format while keeping the semantics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multiplexing&lt;/strong&gt;: many requests in flight on one connection, each with its own id, so a slow response no longer blocks the others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Binary framing&lt;/strong&gt;: a compact binary header instead of text, which is faster to parse and smaller on the wire.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Header compression&lt;/strong&gt;: &lt;strong&gt;HPACK&lt;/strong&gt;[^9] removes headers you already sent on the same connection, since browsers repeat the same ones constantly.&lt;/li&gt;
&lt;li&gt;Server push, later deprecated, for sending resources before they were asked for.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;HTTP/3&lt;/strong&gt;[^10] moved HTTP onto &lt;strong&gt;QUIC&lt;/strong&gt;[^11], which runs on UDP and does its own reliability. The gain is not speed, it is independence: HTTP/3 no longer depends on the transport protocol that lives in the operating system kernel. A connection can be migrated to a new network path, several requests lose packets without stalling each other, and encryption is not an optional add-on bolted on top but part of how the connection is established.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;HTTP/1.1   many connections · requests in order · blocking
HTTP/2     one connection · requests multiplexed · still TCP underneath
HTTP/3     one connection per origin · multiplexed · QUIC · built-in encryption
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Each version of HTTP was waiting for the transport layer to catch up. HTTP/2 waited years for the operating system&amp;#39;s TCP. HTTP/3 stopped waiting and built its own.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Beyond request and response&lt;/h2&gt;
&lt;p&gt;Two extensions are worth knowing because they explain how modern web applications feel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WebSockets&lt;/strong&gt;[^12] start as an ordinary HTTP request and then keep the connection open, turning it into a full-duplex channel. Both sides can push messages at any time, with no polling and no request per update. That is what makes a chat app live, a dashboard update, or a collaborative document work without the server knowing in advance what you will ask for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;REST&lt;/strong&gt;[^13] is not a protocol feature but a style of using HTTP that the web settled on: address your resources with URLs, let the method say the action, keep responses cacheable, and let the server store nothing about the conversation. Most of the design guidance for HTTP APIs is a restatement of these ideas.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Layer four got bytes to a program. The top layer decides what those bytes mean. A server waits, a client asks, and a URL names the thing being asked for across all the layers at once. DNS turns the human name into an address through a distributed hierarchy that caches aggressively enough to work at internet scale. HTTP is a text request and response with five methods, a handful of status classes, and no memory between requests, which is exactly why it scales and why state has to travel in cookies. HTTP/2 multiplexed to escape head-of-line blocking; HTTP/3 built its own transport to stop depending on the kernel&amp;#39;s. WebSockets turn a request into a live channel, and REST is the shape most of the web settled on.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;a URL names it:  scheme · host · port · path
DNS resolves it: name → address, cached until the TTL
HTTP asks for it: method · path · headers → status · body
state travels:    cookies carry a reference, the server holds the state
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One layer left, and it is the one that keeps being skipped. The next post opens network security: what happens when the network is shared with strangers, why every byte above is currently readable by anyone on the path, and how a handshake nobody can fake turns a public road into a private conversation.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/URL&quot;&gt;URL - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Domain_Name_System&quot;&gt;Domain Name System - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Time_to_live&quot;&gt;Time to live - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Domain_Name_System#Resource_records&quot;&gt;Domain Name System record types - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol&quot;&gt;Hypertext Transfer Protocol - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Stateless_protocol&quot;&gt;Stateless protocol - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_cookie&quot;&gt;HTTP cookie - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP/2&quot;&gt;HTTP/2 - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/HPACK&quot;&gt;HPACK - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP/3&quot;&gt;HTTP/3 - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/QUIC&quot;&gt;QUIC - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^12]: &lt;a href=&quot;https://en.wikipedia.org/wiki/WebSocket&quot;&gt;WebSocket - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^13]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Representational_state_transfer&quot;&gt;Representational state transfer - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>networking</category><category>http</category><category>dns</category><category>how-it-works</category></item><item><title>The Transport Layer: TCP &amp; UDP</title><link>https://www.wesleybertipaglia.com/blog/cj1ck7im/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/cj1ck7im/</guid><description>A deep dive into end-to-end transport: ports and sockets, connectionless UDP vs. reliable TCP, sequence numbers, flow control, sliding windows, and congestion management.</description><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/y9rgzb70&quot;&gt;network layer post&lt;/a&gt; ended with two unanswered questions: did my packet arrive, and did they arrive in order? It also quietly admitted something more awkward. Its addresses are per machine, so on a machine running two hundred programs, IP alone cannot say which program a packet is for. This post opens the layer that fixes both: &lt;strong&gt;TCP&lt;/strong&gt; and &lt;strong&gt;UDP&lt;/strong&gt;, the two protocols that turn a lossy, unordered packet delivery into something a program can actually build on.&lt;/p&gt;
&lt;h2&gt;What the transport layer is for&lt;/h2&gt;
&lt;p&gt;The network layer got the data to the right computer. The transport layer gets it to the right &lt;strong&gt;program&lt;/strong&gt; on that computer, and decides what promises to make along the way.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;192.168.0.15  = your laptop
                    ├─ 443   the browser
                    ├─ 5432  the database client
                    └─ 7000  the game
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those numbers are &lt;strong&gt;ports&lt;/strong&gt;[^1]. A port is a 16-bit number identifying one program listening on a machine, and the transport layer header carries it. Add the IP addresses and you get the &lt;strong&gt;four-tuple&lt;/strong&gt;[^2]: source address, source port, destination address, destination port. Five different connections to the same server look completely different to it, because their source ports differ.&lt;/p&gt;
&lt;p&gt;Ports come in two flavors. &lt;strong&gt;Well-known ports&lt;/strong&gt; below 1024 are reserved for standard services, so 53 is DNS, 80 is HTTP, 443 is HTTPS. &lt;strong&gt;Ephemeral ports&lt;/strong&gt; are assigned to whatever a program needs at that moment, usually from a large pool, and there are thousands of them, which is what lets one web server host tens of thousands of simultaneous connections.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;socket&lt;/strong&gt;[^3] is the object your program actually holds: an address plus a port, plus state. In the &lt;a href=&quot;/blog/zkb3xkdy&quot;&gt;operating system series&lt;/a&gt; terms, creating a socket is a syscall asking the kernel to own an endpoint for you. When two machines talk, there are two sockets, one on each side, and the packets between them belong to that conversation only.&lt;/p&gt;
&lt;p&gt;Now the two choices. Everything below is the difference between them.&lt;/p&gt;
&lt;h2&gt;UDP: send and forget&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;UDP&lt;/strong&gt;[^4] is the simplest useful thing this layer can do. It adds almost nothing: ports, a checksum, and four fields.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;┌──────────────┬──────────────┬──────┬──────┬────────┬─────┐
│ source port  │ dest port    │ len  │csum │payload │     │
└──────────────┴──────────────┴──────┴──────┴────────┴─────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No setup, no acknowledgment, no resending, no ordering, no idea whether the other side exists. You send a datagram and continue working immediately.&lt;/p&gt;
&lt;p&gt;What UDP gives you is &lt;strong&gt;message boundaries&lt;/strong&gt;. One send is one datagram, and the receiver either gets that whole datagram or does not get it. Boundaries survive the journey.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a video game sends your position thirty times a second and a voice app sends a chunk every 20 milliseconds. If a packet of state is late, it is worthless, because the next one is already on the way. Retransmitting it would deliver old information about where you are now standing. UDP is exactly right here: the best use of the network is to send the next thing.&lt;/p&gt;
&lt;p&gt;UDP is also what a programmer chooses when the layer above will do the work. DNS is a famously tiny exchange over UDP. A video call sends its own loss concealment, its own pacing, its own encryption on top. And QUIC, from the &lt;a href=&quot;/blog/2fai7eui&quot;&gt;next post&lt;/a&gt;, implements a full reliable transport &lt;em&gt;on top of UDP&lt;/em&gt; inside the kernel&amp;#39;s application space.&lt;/p&gt;
&lt;h2&gt;TCP: a reliable conversation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;TCP&lt;/strong&gt;[^5] makes the opposite deal. It delivers every byte, in order, exactly once, or it fails loudly trying. In exchange you pay for setup, for state on both ends, for headers on every packet, and for waiting when things go wrong.&lt;/p&gt;
&lt;p&gt;The first surprise is that TCP is a &lt;strong&gt;byte stream&lt;/strong&gt;, not a message service. If you write 100 bytes and then 200 bytes, the other side may read 150 and then 150. There are no message boundaries, because TCP&amp;#39;s promise is about the order of bytes, not about your write calls. Every programmer who has ever been surprised by a JSON parser choking on half a message learned this the hard way.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;what you sent:      [ &amp;quot;hello&amp;quot; ][ &amp;quot;world&amp;quot; ]
what TCP delivers:  [ h ][ e ][ l ][ l ][ o ][ w ][ o ][ r ][ l ][ d ]
                    one continuous stream - the receiver decides the chunks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second surprise is that TCP is &lt;strong&gt;end-to-end&lt;/strong&gt;. The guarantees are provided by the two machines at the ends, not by the routers between them. The network keeps doing exactly what the &lt;a href=&quot;/blog/y9rgzb70&quot;&gt;previous post&lt;/a&gt; described: forward, forget, drop, reorder. TCP builds reliability from that on top, at the endpoints. This is the end-to-end principle from the &lt;a href=&quot;/blog/8ymy4yyc&quot;&gt;TCP/IP post&lt;/a&gt; in action.&lt;/p&gt;
&lt;h2&gt;The handshake&lt;/h2&gt;
&lt;p&gt;Before any data moves, both sides agree to talk. The &lt;strong&gt;three-way handshake&lt;/strong&gt;[^6] does it in three packets:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;client                                server
  │  SYN          &amp;quot;I want to talk&amp;quot;        │
  │──────────────────────────────────────▶│
  │  SYN + ACK     &amp;quot;Sure, I acknowledge&amp;quot;  │
  │◀──────────────────────────────────────│
  │  ACK           &amp;quot;Great, I agree too&amp;quot;    │
  │──────────────────────────────────────▶│
  │                                        │
  ▼  data starts flowing                  ▼
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each message serves a purpose. The first carries the client&amp;#39;s starting sequence number and options. The second acknowledges that number, proving the server received it, and returns the server&amp;#39;s own starting number. The third acknowledges the server&amp;#39;s number, proving the client received &lt;em&gt;that&lt;/em&gt;. Only after this does either side send data.&lt;/p&gt;
&lt;p&gt;The handshake exists because of one hard fact about networks: a message can arrive late, duplicated, or out of order. Two open connections with no handshake can be confused with a retransmission of an old one. Three messages give both sides proof that each heard the other, and give the operating system a definite moment to create a socket and let the &lt;a href=&quot;/blog/fwq0k4n2&quot;&gt;process layer&lt;/a&gt; schedule it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; the half-open connection is why opening 10,000 connections at once can exhaust a server&amp;#39;s backlog. Every one of them is a socket in the middle of a handshake, holding memory, waiting for a third message that will never come.&lt;/p&gt;
&lt;h2&gt;Sequence numbers and acknowledgments&lt;/h2&gt;
&lt;p&gt;Once open, TCP numbers the bytes. The &lt;strong&gt;sequence number&lt;/strong&gt; is the index of the first byte in the segment, and the receiver sends back an &lt;strong&gt;acknowledgment&lt;/strong&gt; with the next number it expects.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;sender:      [seq 1001, 500 bytes]──▶
receiver:    ACK 1501  ──▶          &amp;quot;I have everything up to 1500&amp;quot;

sender:      [seq 1501, 500 bytes]──▶   (this one is lost)

sender:      [seq 2001, 200 bytes]──▶

receiver:    ACK 1501  ──▶          &amp;quot;still waiting for 1501&amp;quot;

sender: resends [seq 1501]  ──▶      duplicate arrives later,
receiver: ignores it, ACKs again     because the numbers say so
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything TCP does to be reliable is in that picture:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Numbers give ordering.&lt;/strong&gt; Out-of-order segments wait in a buffer until the missing one shows up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acknowledgments give proof of receipt.&lt;/strong&gt; Nothing is assumed to have arrived.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A repeated acknowledgment is a signal.&lt;/strong&gt; When the receiver keeps saying &amp;quot;still 1501&amp;quot;, the sender concludes its segment was lost and resends. It does not need an error message; the absence of progress is the evidence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Numbers deduplicate.&lt;/strong&gt; A retransmitted segment carries the same number as the original, so the receiver recognizes it and does not deliver it twice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; this is why typing in a terminal over SSH is fast. Keystrokes are small, arrive almost immediately, and the acknowledgment for each comes back piggybacked on the next packet instead of costing its own trip.&lt;/p&gt;
&lt;h2&gt;Flow control: don&amp;#39;t overwhelm the receiver&lt;/h2&gt;
&lt;p&gt;Acknowledgments solve loss. Two more problems remain, and they are the pair that separates a working connection from a fast one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flow control&lt;/strong&gt; protects the receiver. A fast sender can bury a slow receiver in data it has no room to store, so the receiver advertises its free buffer space, the &lt;strong&gt;receive window&lt;/strong&gt;, and the sender may never have more unacknowledged bytes in flight than that window.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;receiver: &amp;quot;I have 64 KB free, send at most that much&amp;quot;
sender:   &amp;quot;understood, filling 64 KB before waiting for ACKs&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a sliding window: the sender keeps sending until the window is full, then slides it forward as acknowledgments arrive. Bandwidth-delay product, from the &lt;a href=&quot;/blog/ri76k15u&quot;&gt;physical layer post&lt;/a&gt;, is what sets the ideal window size - on a 100 ms round trip to another continent, a 64 KB window caps your throughput no matter how fast the link is. This single fact is why window scaling exists and why &amp;quot;my connection is fast but my download is slow&amp;quot; has a boring explanation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Congestion control&lt;/strong&gt; protects the network, and it is the more delicate of the two because the sender cannot see the network. It infers congestion from the only signal available: if acknowledgments stop arriving or arrive slower than expected, something between is full, and the sender&amp;#39;s queue somewhere overflowed.&lt;/p&gt;
&lt;p&gt;The algorithm behind it is remarkably simple, and roughly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Slow start.&lt;/strong&gt; Begin with a tiny window and double it every round trip until something hurts. The network is mostly empty at the start of a connection, so grow fast.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Congestion avoidance.&lt;/strong&gt; Stop doubling. Add one segment per round trip instead, so growth becomes linear and gentle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loss detected.&lt;/strong&gt; When a segment is missing, halve the window and back off. The network is full; being timid is correct.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A few losses are normal.&lt;/strong&gt; One lost segment in a large batch usually means a queue overflowed briefly, not that the network is dying, so modern stacks react much more gently to a single loss than older ones did.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;The sender is guessing about the whole internet from one signal: how fast acknowledgments come back. That guess is good enough to run the internet.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Two windows, two jobs. The congestion window is the sender&amp;#39;s guess about the network. The receive window is the receiver&amp;#39;s honest statement about itself. The sender is limited by the smaller of the two.&lt;/p&gt;
&lt;h2&gt;Head-of-line blocking&lt;/h2&gt;
&lt;p&gt;TCP delivers bytes in order, so a lost segment holds up everything behind it, even if those later segments arrived perfectly and are sitting in the receiver&amp;#39;s buffer. This is &lt;strong&gt;head-of-line blocking&lt;/strong&gt;[^7].&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;packet 100 lost ··· packets 101-105 arrived

receiver cannot deliver 101 until 100 shows up
the application waits, even though the data is already there
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On a long, variable-latency path this is the worst property of TCP: one lost packet out of a window can stall the whole stream, and the cost grows with distance. This single problem, plus the fact that encryption needs key material before the payload can be read, is what motivated QUIC: a transport protocol that runs on UDP, keeps its own state in userspace, tracks many independent streams, and never lets a lost packet for one stream block the others.&lt;/p&gt;
&lt;p&gt;QUIC also changes where things live. TCP is implemented in the operating system kernel, which is why it took decades to add new options and why middleboxes broke when it changed. QUIC is a library in your process, so it ships with the browser and updates when the browser updates. The layer did not move; the implementation did.&lt;/p&gt;
&lt;h2&gt;Closing a connection&lt;/h2&gt;
&lt;p&gt;Data can end unilaterally. An endpoint sends &lt;strong&gt;FIN&lt;/strong&gt; meaning &amp;quot;no more data from me&amp;quot;; the other replies with its own FIN plus an acknowledgment. Both sides then linger in a wait state before the socket can be reused, so that any straggler segments in the network do not confuse a future connection that reuses the same ports.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a program can close only its sending half and keep receiving. This is why killing a client process does not necessarily close the socket: any data already on the network still arrives, and something else has to read it. Programs that exit while the kernel still has unread data are why closing a socket properly is its own small ceremony.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Layer three got packets to a machine. Layer four gets them to a program, identified by ports, with the four-tuple telling conversations apart. UDP promises nothing, keeps message boundaries, and is the right choice when stale data is worse than missing data. TCP promises every byte, in order, exactly once, by numbering bytes, acknowledging receipt, and resending whatever acknowledgment stops advancing: at the cost of a handshake, stream semantics, and waiting. Flow control protects the receiver with a sliding window; congestion control guesses the network&amp;#39;s state from acknowledgment timing and grows carefully. TCP does all of it in the kernel, end to end, which is both its strength and the reason QUIC now does it in userspace.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;network:  packets, maybe lost, maybe reordered
   ↓ transport adds ordering, dedup, retransmission
   ↓ and two windows: the receiver&amp;#39;s, and the sender&amp;#39;s guess
program:  a stream of bytes it can trust
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next post opens the top floor: how two programs agree on what the bytes mean, how names become addresses, and how the web became the most-deployed protocol in human history.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Port_(computer_networking)&quot;&gt;Port (computer networking) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Socket_address&quot;&gt;Socket address - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Network_socket&quot;&gt;Network socket - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/User_Datagram_Protocol&quot;&gt;User Datagram Protocol - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Transmission_Control_Protocol&quot;&gt;Transmission Control Protocol - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Three-way_handshake&quot;&gt;Three-way handshake - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Head-of-line_blocking&quot;&gt;Head-of-line blocking - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>networking</category><category>tcp</category><category>udp</category><category>how-it-works</category></item><item><title>The Network Layer &amp; IP Routing</title><link>https://www.wesleybertipaglia.com/blog/y9rgzb70/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/y9rgzb70/</guid><description>A deep dive into packet routing: IPv4 vs. IPv6 addresses, CIDR subnets, packet headers, hop-by-hop routing tables, ICMP, and global routing principles (BGP and OSPF).</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/esoqqx40&quot;&gt;link layer post&lt;/a&gt; delivered a frame to the machine on the other end of one cable. That is a small promise for a world-sized problem: the destination is usually in another building, another city, another continent, on a network whose owner has never heard of you. This post opens the layer built for that: the one that gives every machine on earth an address, and that moves data one stranger at a time until it arrives.&lt;/p&gt;
&lt;h2&gt;What the network layer is for&lt;/h2&gt;
&lt;p&gt;Two jobs, both simple to state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Addressing.&lt;/strong&gt; Every host gets a globally unique address, so any machine anywhere can name any other. That is the layer&amp;#39;s whole reason to exist. A local link can get away with small numbers because it only ever has a dozen machines; the network layer needs an address space big enough for the planet, and it needs addresses that contain a bit of structure, because routers will need to group hosts together to forward at scale.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Routing.&lt;/strong&gt; Data is not sent to the destination; it is sent one hop at a time to whichever neighbor is closer. Each machine decides, locally, &amp;quot;for each destination I care about, who is the next step?&amp;quot; No router knows the whole path. The path emerges from a chain of local decisions.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Layer two asks &amp;quot;who is next door?&amp;quot;. Layer three asks &amp;quot;who is next door in the direction of Japan&amp;quot;, and answers it with whatever the neighbour says.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The network layer also has to carry the &lt;a href=&quot;/blog/esoqqx40&quot;&gt;previous post&amp;#39;s&lt;/a&gt; assumption into a world where the wire is unreliable. It does not fix anything. It forwards, counts hops, and reports errors.&lt;/p&gt;
&lt;h2&gt;IP addresses&lt;/h2&gt;
&lt;p&gt;The network layer&amp;#39;s protocol is the &lt;strong&gt;Internet Protocol&lt;/strong&gt; (IP)[^1], and its address is the &lt;strong&gt;IP address&lt;/strong&gt;[^2]. Two versions exist in the world, and both are still in daily use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IPv4&lt;/strong&gt; is 32 bits, written as four decimal numbers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;93.184.216.34
└ each octet: 0-255        2^32 ≈ 4.3 billion addresses
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;IPv6&lt;/strong&gt; is 128 bits, written as eight groups of hexadecimal digits:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;2606:2800:220:1:248:1893:25c8:1946
└ 8 groups of 16 bits       2^128 addresses
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The scale difference is the whole story. IPv4 was designed for a research network and ran out around the time smartphones arrived; IPv6 was created because of that shortage and is being deployed slowly, one network at a time, mostly because the two can coexist.&lt;/p&gt;
&lt;p&gt;Not every address is public. &lt;strong&gt;Private address ranges&lt;/strong&gt;[^3] are reserved for internal networks and are never routed on the internet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;10.0.0.0/8        192.168.0.0/16       172.16.0.0/12
(one big block)   (home networks)       (most offices)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is why your laptop is &lt;code&gt;192.168.0.x&lt;/code&gt; at home and why your provider gives you an address no website can look up. Something must translate between the two, and that something is &lt;a href=&quot;/blog/g9i0vflz&quot;&gt;NAT&lt;/a&gt;, which the security post opens properly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a public site cannot distinguish your laptop from a million other laptops behind a million routers by IP address alone. They all share one address. NAT is why &amp;quot;your IP address&amp;quot; is much weaker evidence than it sounds.&lt;/p&gt;
&lt;h2&gt;Subnets, prefixes, and CIDR&lt;/h2&gt;
&lt;p&gt;An address is not just an address. It is a &lt;strong&gt;prefix&lt;/strong&gt; plus a host part, and that split is what makes routing possible at all.&lt;/p&gt;
&lt;p&gt;The old scheme carved addresses into three fixed classes: class A for enormous networks, class B for large, class C for small. It was exhausted almost immediately, because &amp;quot;medium-sized network&amp;quot; was not a size anyone had.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CIDR&lt;/strong&gt;[^4], classless inter-domain routing, replaced it with one idea: write the prefix length explicitly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;192.168.1.0/24

│            └── 24 bits of prefix = the network
└───────────────── 32 bits total

the /24 means the first 24 bits identify the network,
the last 8 bits identify hosts (up to 254 usable)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The prefix length is the only concept you need. And because a prefix describes a whole range, a router can advertise a million addresses as one line.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A subnet is not a special kind of address. It is an address plus a ruler saying how much of it is the network.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The IP packet header&lt;/h2&gt;
&lt;p&gt;The unit layer three moves is the &lt;strong&gt;packet&lt;/strong&gt;[^5], and it is the first place we meet the idea from the &lt;a href=&quot;/blog/ylhwiwpb&quot;&gt;representation post&lt;/a&gt; applied to real wire formats: a fixed layout of fields, each with a known offset.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt; 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| version |  IHL  |    DSCP     |      total length            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           identification         | flags      |fragment offset|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|      time to live      |  protocol |       header checksum      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        source address                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      destination address                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    options (if IHL &amp;gt; 5)                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fields you will actually reason about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Source and destination address.&lt;/strong&gt; The whole point of the packet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protocol.&lt;/strong&gt; What is inside: 6 for TCP, 17 for UDP, 1 for ICMP. This is the pointer that lets the receiving host hand the payload to the right module.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time to live.&lt;/strong&gt; A counter that starts at some value, like 64, and is decremented by every router. This is the field that stops packets from circulating forever.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Header checksum.&lt;/strong&gt; Only the header is checked, and it is recomputed at every hop, because the header changes at every hop.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total length and IHL.&lt;/strong&gt; How big the packet is, and how big the header is, since options make the header variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note the asymmetry: the header changes at every hop, the payload does not. The payload survives the journey untouched; only the envelope is re-addressed.&lt;/p&gt;
&lt;h2&gt;TTL and why tracert works&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;time to live&lt;/strong&gt;[^6] field is a distance counter, not a timer. A router that receives a packet with TTL 1 decrements it to 0, sends back a message saying &amp;quot;I had to throw this away&amp;quot;, and drops the packet. Any packet with a TTL high enough to cross a continent still counts its hops.&lt;/p&gt;
&lt;p&gt;That single field produces the best network tool most people know:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;traceroute to a server in Amsterdam:

 1  192.168.0.1        0.9 ms   your router
 2  10.x.x.x           8.1 ms   your provider
 3  ...               14.2 ms   backbone
 ...
 7  ...              121.4 ms   the destination, or near it
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each line is one network refusing to carry the packet any further and saying so. The tool asks again with TTL 2, again with 3, and the sequence of refusals &lt;em&gt;is&lt;/em&gt; the path.&lt;/p&gt;
&lt;p&gt;This also retires a myth from the &lt;a href=&quot;/blog/ri76k15u&quot;&gt;physical layer post&lt;/a&gt;. People often blame satellite latency on light being slow. It is: the count of hops is what distance means on a network, and the light is not the villain. The number of intermediate machines is.&lt;/p&gt;
&lt;h2&gt;Routing, hop by hop&lt;/h2&gt;
&lt;p&gt;Every host and every router has a &lt;strong&gt;routing table&lt;/strong&gt;[^7]: a list of destinations and next hops. When a packet arrives, the router looks at the destination and picks a row. The match is not exact, and this is the elegant part: the router picks the &lt;strong&gt;longest matching prefix&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;routing table:

192.168.1.0/24      →  port 3          (my own network)
10.0.0.0/8          →  port 5
172.16.5.0/24       →  router A       (someone else&amp;#39;s network)
0.0.0.0/0           →  default route  (everything else)

packet to 172.16.5.9   →  longest match is 172.16.5.0/24 → router A
packet to 8.8.8.8      →  only the /0 matches           → default route
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;default route&lt;/strong&gt;[^8], written &lt;code&gt;0.0.0.0/0&lt;/code&gt;, is the catch-all that lets a small network reach the internet with one entry. It is the same trick as a phone&amp;#39;s &amp;quot;call anywhere&amp;quot; entry, and it is why routers do not need an entry for every host on earth.&lt;/p&gt;
&lt;p&gt;Longest prefix match is what makes prefixes useful rather than decorative. A host can have one entry for its own street and still inherit everything else from the default route, and a big provider can announce one line covering a million addresses and let every router downstream pick the most specific one that applies.&lt;/p&gt;
&lt;h2&gt;Who builds these tables&lt;/h2&gt;
&lt;p&gt;Two machines disagreeing about routes is the normal state of the internet, and fixing it is a whole field. The dividing line is ownership.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inside one organization&amp;#39;s network&lt;/strong&gt;, machines run an &lt;strong&gt;interior gateway protocol&lt;/strong&gt; to compute routes automatically. &lt;strong&gt;OSPF&lt;/strong&gt;[^9] is the common one: routers describe their links to each other, everyone builds a full map of that network, and the shortest path wins. Here, topology is shared data, and everyone can know everything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Between organizations&lt;/strong&gt;, that is impossible and undesirable. A provider has no interest in a competitor knowing its internal topology, and the shortest path is often the wrong path for policy reasons: peering agreements, transit fees, and who you are willing to carry traffic for. So the border protocol is different in kind. &lt;strong&gt;BGP&lt;/strong&gt;[^10] is how networks, called &lt;strong&gt;autonomous systems&lt;/strong&gt;[^11], announce to each other which prefixes they can reach.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;inside one network:      OSPF - &amp;quot;here are my links, compute the best paths&amp;quot;
between networks:        BGP  - &amp;quot;I can reach these prefixes&amp;quot;, plus policy

BGP does not compute shortest paths.
It decides who to tell what, in an order operators agree on.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two properties of BGP explain a lot of the internet&amp;#39;s behavior. It does not tell anyone about everything, so some traffic takes odd detours. And because it runs in logical time, seconds or minutes, a misconfigured announcement can misdirect traffic for real stretches of time. BGP is a set of agreements between strangers, maintained by trust and contracts, not by mathematics.&lt;/p&gt;
&lt;h2&gt;ICMP: the network layer talking back&lt;/h2&gt;
&lt;p&gt;Routing is one thing; telling someone their packet failed is another. That is &lt;strong&gt;ICMP&lt;/strong&gt;[^12], the protocol next to IP.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Echo request and echo reply&lt;/strong&gt; are what &lt;code&gt;ping&lt;/code&gt; sends: a tiny packet asking &amp;quot;are you there?&amp;quot;, answered with &amp;quot;yes, and here is the time&amp;quot;. That is how ping measures the latency from the &lt;a href=&quot;/blog/ri76k15u&quot;&gt;physical layer post&lt;/a&gt;, because it measures a round trip through the whole chain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Destination unreachable&lt;/strong&gt; is how a router refuses to deliver. When you see &amp;quot;no route to host&amp;quot;, that message is why.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time exceeded&lt;/strong&gt; is how a router reports &amp;quot;your TTL ran out&amp;quot;, which is what traceroute reads.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fragmentation needed&lt;/strong&gt; tells the sender its packet was too large.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ICMP is the network&amp;#39;s error reporting, and it is genuinely useful, which is also why attackers love it: forged ICMP messages can be used to discover hosts, redirect traffic, or amplify traffic into a flood. Good networks answer ICMP carefully rather than blocking it, and that is why &amp;quot;blocked ping&amp;quot; is not the security win it sounds like.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Layer three gives every host a globally meaningful address, carves it into a prefix plus host part so billions of addresses can be advertised in single lines, and moves packets one hop at a time by longest prefix match, default route in hand. It never fixes anything: it forwards, decrements a TTL that doubles as tracert, and reports trouble through ICMP. Inside a network, OSPF computes paths from a full picture. Between networks, BGP exchanges which prefixes are reachable under policy, because strangers do not share maps. And every packet&amp;#39;s header is rewritten at every hop while its payload survives untouched.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;quot;send this to 93.184.216.34&amp;quot;

your machine:  not mine → default route
your router:   not mine → provider&amp;#39;s router
provider:      not mine → transit backbone
... nineteen hops of &amp;quot;not mine, but I know who&amp;quot; ...
destination:   that is mine → hand to the host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nobody knows the whole route. Everybody knows enough. The next post adds the layer that finally asks for something: not &amp;quot;how do I get there&amp;quot; but &amp;quot;did it arrive, and in what order&amp;quot;.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Internet_Protocol&quot;&gt;Internet Protocol - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/IP_address&quot;&gt;IP address - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Private_IP_address&quot;&gt;Private IP address - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing&quot;&gt;Classless inter-domain routing - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/IP_packet&quot;&gt;IP packet - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Time_to_live&quot;&gt;Time to live - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Routing_table&quot;&gt;Routing table - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Default_route&quot;&gt;Default route - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Open_Shortest_Path_First&quot;&gt;Open Shortest Path First - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Border_Gateway_Protocol&quot;&gt;Border Gateway Protocol - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Autonomous_system_(Internet)&quot;&gt;Autonomous system (Internet) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^12]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol&quot;&gt;Internet Control Message Protocol - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>networking</category><category>ip</category><category>routing</category><category>how-it-works</category></item><item><title>The Link Layer &amp; Local Networks</title><link>https://www.wesleybertipaglia.com/blog/esoqqx40/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/esoqqx40/</guid><description>A deep dive into local framing: MAC addresses, Ethernet frames, switches vs. hubs, ARP (Address Resolution Protocol), collision domains, and local area network mechanics.</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/ri76k15u&quot;&gt;physical layer post&lt;/a&gt; ended at the bottom: bits, and nothing that knows what they mean. This post adds the first thing that does. Layer one moves bits to the neighbor. Layer two gives those bits an address, a frame around them, and a device smart enough to deliver them to exactly one machine out of everyone plugged into the same cable. This is the layer that turns loose signals into a local network, and it is the first place the addressing idea appears.&lt;/p&gt;
&lt;h2&gt;What the link layer is for&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;/blog/82r60uj7&quot;&gt;OSI post&lt;/a&gt; summarized the job in one sentence: deliver bytes to the neighbor. That is worth unpacking, because the neighbor is the whole point.&lt;/p&gt;
&lt;p&gt;A router connecting two networks receives a packet and forwards it to a different cable. It never looks inside for a local machine. The link layer is what happens on a single cable, between machines sharing that cable, and it answers three questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Who is this for?&lt;/strong&gt; There may be a dozen machines on this wire.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Where does it start and end?&lt;/strong&gt; The physical layer delivers a soup of bits. Somebody must mark the message boundaries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Did it survive the trip?&lt;/strong&gt; Signals get damaged, and someone should detect that before passing garbage upward.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note what the link layer does &lt;em&gt;not&lt;/em&gt; do. It knows nothing about hosts across the world, nothing about programs, and nothing about whether a message will be answered. It moves frames one hop, on this wire, to this machine.&lt;/p&gt;
&lt;h2&gt;MAC addresses: identity on the wire&lt;/h2&gt;
&lt;p&gt;The link layer&amp;#39;s address is the &lt;strong&gt;MAC address&lt;/strong&gt;[^1]: 48 bits, usually written as six hex bytes, and permanently burned into the network interface at the factory.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;a4:83:e7:2f:19:0b

first 3 bytes (a4:83:e7)  who made the card (the OUI vendor prefix)
last 3 bytes (2f:19:0b)    which card (assigned by the manufacturer)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The structure matters because it is the one place where the internet&amp;#39;s &amp;quot;everything is flat&amp;quot; idea is broken. IP addresses are allocated top-down and therefore tell you where a machine is roughly. MAC addresses are assigned by hardware makers and tell you nothing about geography at all: the card in your laptop and the card in a server in Seoul share only a vendor prefix.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; your laptop and your neighbor&amp;#39;s laptop, on the same café Wi-Fi, can have MAC addresses from the same manufacturer batch. There is no &amp;quot;where&amp;quot; in a MAC address.&lt;/p&gt;
&lt;p&gt;A note on modern hardware: many interfaces now use a locally administered address, with a bit flipped to signal &amp;quot;this is a random number, not the vendor&amp;#39;s&amp;quot;, because tracking devices by permanent hardware identity turned into a privacy problem. The format stayed the same; the promise of global uniqueness got weaker on purpose.&lt;/p&gt;
&lt;h2&gt;The Ethernet frame&lt;/h2&gt;
&lt;p&gt;Layer two does not send bare bytes. It sends a &lt;strong&gt;frame&lt;/strong&gt;[^2]: the payload from above, wrapped in a header and a trailer that only nearby machines care about.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;┌────────┬────────┬──────┬──────────────┬─────┬─────────┐
│ dest   │ source │ type │   payload    │ pad │  FCS    │
│ MAC    │ MAC    │      │  46-1500 B   │     │  4 B    │
│ 6 B    │ 6 B    │ 2 B  │              │     │         │
└────────┴────────┴──────┴──────────────┴─────┴─────────┘
    └ header ─────────────┘                  └ trailer ──┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Field by field:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Destination and source MAC&lt;/strong&gt;: who this frame is for, and who sent it. These two fields are the entire local addressing system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Type&lt;/strong&gt;: what is inside, meaning an IP packet, an ARP message, or something else. This is the &lt;a href=&quot;/blog/8ymy4yyc&quot;&gt;EtherType&lt;/a&gt; pointer that lets a machine hand the frame to the right layer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Payload&lt;/strong&gt;: up to 1500 bytes of layer 3 data. A 1500-byte payload is the &lt;strong&gt;MTU&lt;/strong&gt;[^3], the maximum transmission unit, the standard ceiling for a frame on ordinary Ethernet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FCS&lt;/strong&gt;: a &lt;strong&gt;frame check sequence&lt;/strong&gt;[^4], a checksum over the whole frame.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two details in there surprise people. First, the minimum: a frame must be at least 64 bytes, so tiny payloads get padded. Second, the trailer: the frame does not end when the payload does, which means a receiver knows to keep reading after a short message instead of mistaking the gap for the end.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; when your machine sends 20 bytes of a DNS query, the Ethernet frame is still 64 bytes minimum, padded to 60 with zeros. Small packets on a real link are mostly padding and headers, which is one reason protocols prefer fewer, larger messages.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;FCS&lt;/strong&gt; is the link layer&amp;#39;s only reliability claim, and it is modest: it detects errors, and a corrupt frame is silently dropped. Detection is not recovery. Layer two never retransmits, because it has no idea whether the frame was lost in transit or arrived while the receiver was busy, and re-sending blindly would flood a busy link with duplicates. The &lt;a href=&quot;/blog/cj1ck7im&quot;&gt;transport layer&lt;/a&gt; is where retransmission actually happens.&lt;/p&gt;
&lt;h2&gt;Switches: the box that learned who is where&lt;/h2&gt;
&lt;p&gt;Now the hardware. A &lt;strong&gt;hub&lt;/strong&gt; is the old answer: an electrical box that repeats every signal to every port. Every machine hears everything, and only one may speak at a time, or the signals collide into noise. A hub is one big &lt;strong&gt;collision domain&lt;/strong&gt;[^5]: everybody shares one conversation slot.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;switch&lt;/strong&gt;[^6] is the modern answer. It forwards each frame only out the port where the destination actually is, and it learns that mapping by watching:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;laptop sends frame  ──▶  switch notes: A4:83:E7:2F:19:0B is on port 3
                        and stores it (the CAM table)

later, a frame for A4:83:E7:2F:19:0B arrives
   ├─ destination known  → forward out port 3 only
   └─ destination unknown → flood out every other port
                            (the first sighting must find someone)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This learning is the whole trick, and it has a name that sounds corporate and is simple: source learning. Every frame that arrives tells the switch whose machine is on which port. The switch never asks.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;hub:    port1 ─┐
        port2 ─┼─ repeat everything everywhere
        port3 ─┘      one collision domain

switch: port1 ─┐   each port is its own collision domain;
        port2 ─┼─ frames go only where they need to go
        port3 ─┘   full duplex: send and receive simultaneously
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That last property matters more than it sounds. Because a switch gives every machine its own private wire, a machine can send and receive at the same time. Hubs could not: they were half duplex, and the whole network took turns. Moving from hubs to switches is the single change that made office networks feel ten times faster, without any cable being replaced.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; when you run a speed test on Wi-Fi and get half the advertised speed, one common reason is that the wireless medium is shared, unlike the wired network. Full-duplex switches are why a gigabit cable port really does reach close to a gigabit.&lt;/p&gt;
&lt;h2&gt;ARP: bridging the two address worlds&lt;/h2&gt;
&lt;p&gt;Here is a gap in the story. Layer 3 identifies machines with &lt;strong&gt;IP addresses&lt;/strong&gt;[^7], which are what applications use and what routing depends on. Layer 2 delivers frames using &lt;strong&gt;MAC addresses&lt;/strong&gt;. To send anything, a host needs to know which MAC belongs to which IP.&lt;/p&gt;
&lt;p&gt;Inside your own network, that question is answered by broadcast, because broadcasting is cheap there. The &lt;strong&gt;Address Resolution Protocol&lt;/strong&gt;[^8] asks it directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;host A wants to send to 192.168.1.50, and does not know the MAC

A ──▶ everyone on the LAN: &amp;quot;who has 192.168.1.50? tell a4:83:e7:11:22:33&amp;quot;
50 ──▶ A: &amp;quot;that&amp;#39;s me&amp;quot;
A   records the answer in its ARP cache, and sends the frame
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result is cached, so the question is asked once per address rather than once per packet. That cache is the reason ARP is invisible in normal use and briefly visible when you open a terminal and run &lt;code&gt;arp -a&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Two things worth knowing about this design. If the destination is not on your own network, A does not resolve the destination at all: it resolves the &lt;strong&gt;default gateway&lt;/strong&gt;[^9], the machine that forwards traffic off the local network, which is why the first ARP request after you open a browser is always about your router. And because ARP replies are unauthenticated, a machine on the same network can answer for anyone, forging MACs. This is one of the oldest tricks on the internet, and it is one reason the security post exists.&lt;/p&gt;
&lt;p&gt;IPv6 does not use ARP. Neighbors are discovered with a protocol built on multicast, so a machine can ask only the machines that could plausibly be the answer, and nobody on the network has to see the question.&lt;/p&gt;
&lt;h2&gt;Wi-Fi is a link layer too&lt;/h2&gt;
&lt;p&gt;Everything above applies to wireless, with one structural difference that explains everyday Wi-Fi behavior: there is no cable, so there is no dedicated wire per machine and no switch. &lt;strong&gt;Every device on a Wi-Fi network shares one radio channel&lt;/strong&gt;[^10].&lt;/p&gt;
&lt;p&gt;Sharing a medium means collisions, and collisions cannot be detected by listening while transmitting, because a radio cannot hear itself clearly. So Wi-Fi cannot use the wired approach of detecting a collision after the fact. It avoids them instead, using &lt;strong&gt;carrier sense multiple access with collision avoidance&lt;/strong&gt;: listen before speaking, wait a randomized amount, transmit, and if you did not hear an acknowledgement, assume it collided and back off exponentially. Acknowledging every single frame is the price of a shared, invisible medium.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A wired switch gives each machine a private lane. A Wi-Fi access point gives everyone the same lane, so the protocol has to behave politely.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;VLANs: networks inside the network&lt;/h2&gt;
&lt;p&gt;A single physical switch can host several logical networks. &lt;strong&gt;VLANs&lt;/strong&gt;[^11] tag frames with a small identifier so the switch keeps the groups apart, so a port for accounting machines and a port for guest Wi-Fi can sit in the same switch and never talk.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;one physical switch
   ├── VLAN 10: employees
   ├── VLAN 20: servers
   └── VLAN 30: guest Wi-Fi      (isolated by tag, not by cable)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the &amp;quot;virtual&amp;quot; in virtual local area network. It also previews the next post&amp;#39;s world: once you can carve a switch into logical networks, you start wanting a layer that does the same across an entire globe, and the machine that does that is a router.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Layer two delivers frames to the neighbor, and only the neighbor. It addresses machines with flat, meaningless 48-bit MAC addresses, wraps the payload in a frame with type, padding, and a checksum that detects errors without ever repairing them, and moves frames with a switch that learns which port belongs to which machine from the frames it already sees, giving each machine its own collision domain and full duplex. ARP bridges the IP world to the MAC world by asking the whole local network and caching the answer. Wi-Fi runs the same layer over a shared radio, which is why it avoids collisions instead of detecting them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;layer 3: &amp;quot;192.168.1.50&amp;quot;          the address that means something
   ↓ ARP: who owns that IP?      the local question
layer 2: &amp;quot;a4:83:e7:11:22:33&amp;quot;     the address on this wire
   ↓ switch forwards, physical signals
destination machine
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One hop, one address, one wire. The next post takes the packet that link layer carries and asks the hard question: how does it get from that machine to one on another continent?&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/MAC_address&quot;&gt;MAC address - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Ethernet_frame&quot;&gt;Ethernet frame - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Maximum_transmission_unit&quot;&gt;Maximum transmission unit - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Frame_check_sequence&quot;&gt;Frame check sequence - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Collision_domain&quot;&gt;Collision domain - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Network_switch&quot;&gt;Network switch - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/IP_address&quot;&gt;IP address - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Address_Resolution_Protocol&quot;&gt;Address Resolution Protocol - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Default_gateway&quot;&gt;Default gateway - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Wireless_network&quot;&gt;Wireless network - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Virtual_LAN&quot;&gt;Virtual LAN - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>networking</category><category>ethernet</category><category>mac-address</category><category>how-it-works</category></item><item><title>The Physical Layer &amp; Signals</title><link>https://www.wesleybertipaglia.com/blog/ri76k15u/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/ri76k15u/</guid><description>A deep dive into transmitting raw bits: guided and unguided media (copper, fiber, radio waves), signals vs. bits, bandwidth vs. latency, modulation, and physical media constraints.</description><pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/8ymy4yyc&quot;&gt;TCP/IP post&lt;/a&gt; ended with a promise to drop to layer one. So here we are at the bottom: no addresses, no packets, no protocols, no error handling, no idea what the bits mean. A voltage is high or low, a light pulses or does not, a radio wave is at one frequency or another. Everything the internet is, down here is just physics, and physics has rules. This post opens them: what a bit becomes on a wire, what the media can and cannot carry, the two numbers that describe a road, and why moving a single bit is harder than it looks.&lt;/p&gt;
&lt;h2&gt;A bit is not a number, it is a signal&lt;/h2&gt;
&lt;p&gt;A bit is an abstraction. On the wire it is never a number, only a physical difference that the receiver has to interpret.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;logical bit:      1        0        1        1        0
on copper:       +2.5V   -2.5V   +2.5V    +2.5V   -2.5V
on fiber:         light    dark    light    light    dark
on radio:         shift   no shift  shift   shift   no shift
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two machines agree beforehand on what counts as a 1. That agreement is the only reason the same bytes mean the same thing everywhere. Change the encoding and the bytes change meaning, which is why older Ethernet standards had to stop transmitting preamble bytes when they changed the signaling scheme: to old receivers those bytes would have looked like data.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A bit on the wire is a promise that the receiver will read it back the same way. Everything above layer one is bookkeeping on top of that promise.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The crucial thing to internalize: &lt;strong&gt;a bit is analog&lt;/strong&gt;. The voltage drifts, the light dims, the wave picks up noise. The receiver does not read a bit; it decides, with a threshold and a guess, that a bit was there. It sometimes guesses wrong. Layer one offers no guarantee whatsoever, and that is exactly why layer two exists.&lt;/p&gt;
&lt;h2&gt;Guided media: signals inside something&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Guided media&lt;/strong&gt;[^1] carry signals inside a physical path: the signal has nowhere to go but forward.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Twisted pair&lt;/strong&gt;[^2] is the reason your house has ethernet ports. Two insulated copper wires carry opposite signals and twist around each other. The twist looks decorative and is not: each twist makes the interference each wire receives at a slightly different time, so the noise partially cancels out, the same idea as noise-canceling headphones. Copper categories differ mainly in how tightly the pair is twisted and how well shielded the cable is, which is why &amp;quot;Cat 6&amp;quot; is not just marketing.&lt;/p&gt;
&lt;p&gt;Limits come from physics, not standards:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Distance:&lt;/strong&gt; the signal weakens with every meter, and eventually the receiver cannot tell a 1 from a 0. That is why copper runs stop at 100 meters.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crosstalk:&lt;/strong&gt; a fast-changing signal leaks into neighboring pairs. Better twisting and shielding reduce it; higher frequencies leak more, which is why high-speed standards are fussier about cable quality.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interference:&lt;/strong&gt; motors, fluorescent lights, and other cables all add noise.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Coaxial cable&lt;/strong&gt;[^3] puts one conductor inside another with insulation between them. The shielding makes it far less noisy than twisted pair, which is why it survived for cable television and early Ethernet, and why it still dominates long cable runs in some countries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fiber-optic cable&lt;/strong&gt;[^4] carries light through a glass strand thinner than a human hair. The interesting part is why the light stays inside the fiber at all. The core and the cladding have slightly different refractive indices, and light hitting the boundary at a steep angle reflects back inside, a phenomenon called &lt;strong&gt;total internal reflection&lt;/strong&gt;[^5]. The fiber acts as a light pipe, bouncing light along internally.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;copper:  electricity  →  needs a wire, limited by noise and distance
fiber:   light        →  no electricity in the path, immune to
                         electrical interference, tiny loss over
                         kilometers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fiber&amp;#39;s practical consequence is scale. It carries data for tens of kilometers before any regeneration, uses far less power than electrical repeaters, and supports wavelengths beyond visible light so many independent channels share one strand, each carrying more than a hundred gigabits per second. This is why undersea cables carry almost all intercontinental traffic, and why &amp;quot;the cloud&amp;quot; has a physical shape: long glass lines drawn across ocean floors, landing in a handful of coastal buildings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; between two cities, a fiber backbone may carry hundreds of thousands of concurrent video calls, while the copper from your home to your provider&amp;#39;s cabinet may manage a few hundred megabits. Same internet, different material, different physics.&lt;/p&gt;
&lt;h2&gt;Unguided media: signals through the air&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Unguided media&lt;/strong&gt;[^6] have no path. The signal radiates and arrives wherever it arrives, which buys mobility and costs control.&lt;/p&gt;
&lt;p&gt;Everything wireless is a slice of the &lt;strong&gt;radio spectrum&lt;/strong&gt;[^7], a limited range of frequencies handed out by regulators because radio waves from different users interfere if they overlap. Within a slice, the trade is always the same: &lt;strong&gt;higher frequency carries more data but travels less far and is stopped by walls more easily&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wi-Fi&lt;/strong&gt;[^8]: short range, high speed, walls are enemies. It powers local networks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cellular&lt;/strong&gt;: the same idea in cells that hand phones from tower to tower as they move, tuned by frequency: low bands reach far, high bands go fast and stop at the next building.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microwave and satellite&lt;/strong&gt;: line-of-sight beams and orbital relays for distances where cable is impossible. A satellite link is famous for a property that will appear in the next post: about 300,000 km round trip, and a speed of light that refuses to negotiate.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Wireless is not &amp;quot;slower internet&amp;quot;. It is the same bits through air, and air is a harder medium than glass.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Two numbers that decide everything&lt;/h2&gt;
&lt;p&gt;Two words describe a road, and confusing them makes conversations about internet speed pointless.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bandwidth&lt;/strong&gt;[^9] is how much data fits per second, measured in bits per second or multiples of it. It is the width of the pipe.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Latency&lt;/strong&gt;[^10] is how long one piece of data takes to arrive, measured in milliseconds. It is the length of the pipe.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;a fat, short pipe      → fast throughput, quick answers
a thin, long pipe      → slow throughput, slow answers
a fat, long pipe       → fast throughput, slow answers
                        (this is the common real-world surprise)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fiber and long distances are why the first two rows are not the two things you get. Seattle to São Paulo is one of the shortest intercontinental routes and still costs about 120 ms each way, and no amount of bandwidth improves it, because the light is already taking as long as it takes. That delay is why video calls feel awkward and why players on other continents are tough.&lt;/p&gt;
&lt;p&gt;The two combine into a number that decides how much you can have &amp;quot;in flight&amp;quot; at once. The &lt;strong&gt;bandwidth-delay product&lt;/strong&gt;[^11] is bandwidth times round-trip time, and it is the amount of data that fits in the pipe between a request and its answer.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;100 Mbps × 100 ms = 10 Mbit ≈ 1.25 MB in flight at once

that is why throughput feels capped when latency is high,
even on a very fast connection
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Modulation: putting bits on a wave&lt;/h2&gt;
&lt;p&gt;Sending a stream of 1s and 0s is one option. Real links do something more efficient: they let bits change properties of a wave, a technique called &lt;strong&gt;modulation&lt;/strong&gt;[^12].&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;amplitude:  change the strength of the wave
frequency:  change how fast it oscillates
phase:      shift where the wave starts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Early schemes changed one property per bit: on-off, a shift in frequency, a shift in phase. &lt;strong&gt;QAM&lt;/strong&gt;, used everywhere from cable modems to Wi-Fi, changes amplitude and phase together, so one symbol carries several bits. The efficiency is not free: denser constellations need cleaner signal, so higher-order modulation works close to the transmitter and falls apart with noise and distance.&lt;/p&gt;
&lt;p&gt;Digital links have a second problem: the receiver must know where each bit starts. A long run of identical bits gives nothing to synchronize against. Line encodings solve it by adding structure: &lt;strong&gt;Manchester encoding&lt;/strong&gt;[^13] guarantees a transition in every bit period, using it to mean both the value and the clock.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;NRZ:        1 1 1 1 0 0 0        ← long runs, no way to resync
Manchester: ↑↓ ↑↓ ↑↓ ↑↓   ↑↓      ← a transition every bit
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Layer one is the only layer where physics dictates the vocabulary. Everything above can be renamed by a committee. Voltages and frequencies cannot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Attenuation, noise, and the end of the road&lt;/h2&gt;
&lt;p&gt;Every signal on every medium gets worse as it travels, through two named effects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Attenuation&lt;/strong&gt;[^14] is loss of strength with distance: the wave simply weakens. &lt;strong&gt;Noise&lt;/strong&gt;[^15] is anything else added along the way that was not part of the message, from electrical interference to thermal jitter to another radio station.&lt;/p&gt;
&lt;p&gt;Both are cumulative, which is why physical limits exist and why cables are sold in categories. Three techniques push the limit rather than eliminate it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Amplification&lt;/strong&gt; boosts the signal, and also boosts the noise along with it. It works a few times, then the noise dominates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regeneration&lt;/strong&gt; (repeaters, and later regenerators) reads the signal, decides what it was, and transmits a clean fresh copy. This is the only technique that truly resets the damage, and it is why copper links have repeaters every hundred meters and fiber has none for tens of kilometers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coding and equalization&lt;/strong&gt; spend extra bits to undo predictable damage. This is why a modern copper or radio link can move more data through a worse channel than an old one did.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;A bit is a signal, not a number: a voltage, a pulse of light, or a shift in a wave, all of them analog and all of them read by a receiver making a threshold guess. Guided media (twisted pair, coaxial, fiber) move that signal inside a path, limited by attenuation, crosstalk, and distance; unguided media (radio, microwaves, satellite) radiate it, trading reach for speed. Bandwidth and latency are different numbers, and a fat long pipe gives you both a fast download and slow answers. Modulation puts many bits in each symbol, line encodings keep the receiver in sync, and regeneration is the only thing that truly undoes accumulated damage.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;sender: bits → symbols → modulation → signal
                                    ↓  copper · glass · air
receiver: signal → threshold decision → bits
                    (sometimes wrong)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing down here knows what your data is. The next post adds the first thing that does: addresses for the machine on the other end of this cable, and the switch that delivers a frame to exactly that machine.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Guided_media&quot;&gt;Guided media - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Twisted_pair&quot;&gt;Twisted pair - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Coaxial_cable&quot;&gt;Coaxial cable - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Fiber-optic_communication&quot;&gt;Fiber-optic communication - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Total_internal_reflection&quot;&gt;Total internal reflection - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Unguided_media&quot;&gt;Unguided media - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Radio_spectrum&quot;&gt;Radio spectrum - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Wi-Fi&quot;&gt;Wi-Fi - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Bandwidth_(computing)&quot;&gt;Bandwidth (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Latency_(engineering)&quot;&gt;Latency (engineering) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Bandwidth-delay_product&quot;&gt;Bandwidth-delay product - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^12]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Modulation&quot;&gt;Modulation - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^13]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Manchester_code&quot;&gt;Manchester code - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^14]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Attenuation_(electrical_networks)&quot;&gt;Attenuation (electrical networks) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^15]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Noise_(electronics)&quot;&gt;Noise (electronics) - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>networking</category><category>physical-layer</category><category>signals</category><category>how-it-works</category></item><item><title>The TCP/IP Stack Explained</title><link>https://www.wesleybertipaglia.com/blog/8ymy4yyc/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/8ymy4yyc/</guid><description>A deep dive into the pragmatic internet architecture: the 4-layer model (Link, Internet, Transport, Application), RFC standardization, host-to-host philosophy, and why TCP/IP defeated OSI in implementation.</description><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/82r60uj7&quot;&gt;OSI post&lt;/a&gt; ended with a promise: the model is a map, but there is a machine actually making the journey. That machine is TCP/IP, and it is a strange piece of history: the seven-floor design that lost became the most successful networking architecture ever built, because the four-floor design that won shipped first. This post opens the real stack: the four layers your bytes travel through, how layers point at each other, the idea of an internet as a network of independent networks, and the two decisions that explain everything else.&lt;/p&gt;
&lt;h2&gt;The stack that actually runs&lt;/h2&gt;
&lt;p&gt;Where the &lt;a href=&quot;/blog/82r60uj7&quot;&gt;previous post&lt;/a&gt; had seven floors, TCP/IP has four. The two OSI floors that turned out to be unnecessary are gone, and the rest are renamed after what they actually do.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;4  Application     your program&amp;#39;s language       HTTP, DNS, TLS, QUIC
3  Transport       deliver to the right program  TCP, UDP
2  Internet        address and route across      IP, ICMP, routers
1  Link            address and deliver nearby   Ethernet, Wi-Fi, ARP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mapping the two models onto each other explains most of the disagreements you will hear about:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;OSI                TCP/IP
Application   7  ┐
Session       5  ├─ Application    4   (HTTP, DNS, plus
Presentation  6  ┘                  │    TLS and QUIC)
Transport     4     ─────────────── 3   (TCP, UDP)
Network       3     ─────────────── 2   (IP, ICMP)
Data link     2     ─────────────── 1   (Ethernet, Wi-Fi)
Physical     1     ─────────────── 1   (folded into the link layer)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Session and presentation disappeared&lt;/strong&gt; because nobody could agree on what belonged in them, and every real implementation ended up doing the same job somewhere else. Encoding, compression, and encryption live inside libraries and protocols now, not in a layer of their own.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Physical folded into the link layer&lt;/strong&gt; because on any real machine, the driver that talks to the network card handles both. You do not choose a &amp;quot;physical layer&amp;quot; separately from the driver that programs the card. Layer 1 is the wire; layer 2 is the code that puts frames on it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;QUIC complicates the picture.&lt;/strong&gt; HTTP/3 runs its own reliability on top of UDP inside what looks like the transport layer, which is why some diagrams draw five layers. This series keeps the four-floor picture and treats QUIC as what it is: an exception that proves the rule.&lt;/p&gt;
&lt;h2&gt;How layers point at each other&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;/blog/82r60uj7&quot;&gt;encapsulation post&lt;/a&gt; showed headers being added, but not how a receiver knows which header to read. Each layer tags its data with an identifier so the layer below can tell what is inside. There are three of these identifiers, and they are why everything interoperates.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EtherType&lt;/strong&gt;[^1]: a number in the Ethernet header saying &amp;quot;the payload is IPv4&amp;quot;, &amp;quot;IPv6&amp;quot;, or &amp;quot;ARP&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protocol number&lt;/strong&gt;[^2]: a byte in the IP header saying &amp;quot;the payload is TCP&amp;quot;, &amp;quot;UDP&amp;quot;, or &amp;quot;ICMP&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Port number&lt;/strong&gt;[^3]: a 16-bit number in the transport header saying &amp;quot;this belongs to the web server, 443&amp;quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;frame  →  EtherType 0x0800   →  IP
packet →  protocol 6          →  TCP
segment→  port 443            →  the web server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every packet carries the whole chain, because the receiving host receives one layer at a time, unwraps, and needs to know what to unwrap next. A host can read a header, recognize what is inside, and only then decide which of its own modules should handle it. This is why you can add a new protocol on the application layer and old machines still deliver your packets correctly: they read &amp;quot;TCP&amp;quot; in the IP header, deliver to TCP, and let TCP deliver to your new port.&lt;/p&gt;
&lt;h2&gt;An internet, not a network&lt;/h2&gt;
&lt;p&gt;The word matters. A &lt;strong&gt;network&lt;/strong&gt; connects machines under one owner. An &lt;strong&gt;internet&lt;/strong&gt; connects networks, each owned by someone else, and promises only to cooperate.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;your laptop ── home network ── ISP network ── IXP ── Google network ── server
              (owned by you)  (owned by     (a         (owned by      (owned by
                                your ISP)    switch)    Google)        Google)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No machine in this drawing is in charge. Routers do not know the whole path to your destination, and they do not need to: each router only needs to know which &lt;strong&gt;next hop&lt;/strong&gt; is closer to the destination than it is. That is called &lt;strong&gt;hop-by-hop forwarding&lt;/strong&gt;, and it is the difference between a network that needs a map and an internet that needs a neighbor.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The internet does not know where anything is. Every router knows only which road to take next. The route emerges from millions of partial answers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This also means the internet has no natural owner, no address of record, and no place where a message &amp;quot;arrives at the internet&amp;quot;. There is no such machine. There are only cooperating networks, each making a locally reasonable forwarding decision, exactly the way the &lt;a href=&quot;/blog/zkb3xkdy&quot;&gt;operating system&lt;/a&gt; makes locally reasonable scheduling decisions about a hundred processes.&lt;/p&gt;
&lt;h2&gt;Rules that let strangers cooperate&lt;/h2&gt;
&lt;p&gt;For networks owned by strangers to interoperate, the rules cannot be invented per network. They are written down, publicly, and numbered. That is the &lt;strong&gt;request for comments&lt;/strong&gt; (RFC)[^4] system: documents that begin as proposals, gather review from anyone, and become the standards that everyone implements. The internet&amp;#39;s rules live in the RFC series, and a famous early one, RFC 791, defines IP.&lt;/p&gt;
&lt;p&gt;The culture around those documents became famous too, from the rules of RFC 817[^5] for writing them: it required a section on &lt;em&gt;consistency&lt;/em&gt; with other RFCs and &lt;em&gt;documentation&lt;/em&gt; of the rationale, and in the same spirit of practicality, its authors declared that &amp;quot;no kings, no presidents, no voting&amp;quot; and that trivial objections should be dismissed with &amp;quot;we reject: your reasoning is bad&amp;quot;. Standards get written by the people who have to implement them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Real-time systems have standards written by vendors. The internet has standards written by users. That difference is not trivia; it is why the internet won.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The end-to-end principle&lt;/h2&gt;
&lt;p&gt;The second decision that explains everything is a rule about where functions live, usually called the &lt;strong&gt;end-to-end principle&lt;/strong&gt;[^6]: put a function at the ends of the network whenever you can, and keep the middle dumb.&lt;/p&gt;
&lt;p&gt;The argument: the middle of the network only knows how to move packets. It cannot know whether those packets carry a video, a bank transfer, or a text message, and any function placed there can only be correct for some applications and wrong for the rest. So the middle moves bytes, and the meaning lives in software at the endpoints, which both ends control.&lt;/p&gt;
&lt;p&gt;This is why the web needed almost nothing from the network. The network delivers unreliable packets; HTTP and TCP at the ends turn that into a page. It is also why a new service can be invented, like video calls, without asking anyone&amp;#39;s permission: the network did not change at all.&lt;/p&gt;
&lt;p&gt;It also predicts where new ideas land. Peer-to-peer file sharing needed a hole puncher in the middle and the logic at the ends. Encrypted traffic needed the endpoints to agree on keys and little help from the middle. Every time the ends can solve it, the solution goes in the ends.&lt;/p&gt;
&lt;h2&gt;Why TCP/IP won&lt;/h2&gt;
&lt;p&gt;OSI was, by its own designers&amp;#39; account, a better design: it separated concerns more finely and defined interfaces more precisely. TCP/IP was cruder: four layers, session and presentation dropped, and the &amp;quot;interface&amp;quot; between layers left to each operating system to implement as it saw fit.&lt;/p&gt;
&lt;p&gt;TCP/IP still won, for reasons that have little to do with elegance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It shipped first.&lt;/strong&gt; Real implementations existed while the OSI debate was still running.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It was minimal and permissive.&lt;/strong&gt; A host that ignored a layer could still interoperate, which is unusual generosity for a standard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The end-to-end rule made it extensible.&lt;/strong&gt; New applications needed no network changes, so the stack stayed put while everything above it exploded.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adoption followed use.&lt;/strong&gt; Everyone implemented it because everyone was implementing it, and the internet reached critical mass decades before a competing stack could.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The OSI model survives as vocabulary, which is the best ending a losing design can get.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;TCP/IP is four layers: application, transport, internet, link. Ethernet types, protocol numbers, and port numbers let each layer hand off to the next, so new protocols can be added without touching the ones below. An internet is a network of independently owned networks connected by routers that know only the next hop, which is why nobody needs a map of the whole world. RFCs write the rules that strangers follow, and the end-to-end principle keeps the middle dumb and the meaning at the ends.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;your program
  ↓ application: HTTP, DNS, TLS
  ↓ transport:   TCP, UDP (reliability, ports)
  ↓ internet:    IP (addresses, hop-by-hop routing)
  ↓ link:        Ethernet, Wi-Fi (neighbors, frames)
  ↓ the wire
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything above is a promise about what happens after the bits are gone. The next post drops to layer one: what a bit actually is on a copper pair, a fiber strand, or a radio wave, and why moving a bit is harder than it sounds.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/EtherType&quot;&gt;EtherType - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_IP_numbers&quot;&gt;List of IP numbers - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Port_(computer_networking)&quot;&gt;Port (computer networking) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Request_for_Comments&quot;&gt;Request for Comments - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/RFC_817&quot;&gt;RFC 817 - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/End-to-end_principle&quot;&gt;End-to-end principle - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>networking</category><category>tcp-ip</category><category>internet</category><category>how-it-works</category></item><item><title>The OSI Model &amp; Encapsulation</title><link>https://www.wesleybertipaglia.com/blog/82r60uj7/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/82r60uj7/</guid><description>A deep dive into theoretical network abstraction: the 7-layer OSI model, layer responsibilities, data units (PDUs), header framing, and how data undergoes encapsulation and decapsulation at each step.</description><pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/ze2vg26v&quot;&gt;networks overview&lt;/a&gt; ended with a stack of layers and a promise: each layer adds one guarantee, from volts to web pages. But that drawing was a sketch. Somebody chose those layers, counted them, named them, and wrote them down. This post opens that document. The seven-layer model everyone half-remembers from school, what each floor is actually responsible for, and the single mechanism that makes layering work: wrapping data in headers on the way down and unwrapping it on the way up.&lt;/p&gt;
&lt;h2&gt;The model came before the network&lt;/h2&gt;
&lt;p&gt;In 1979, before the internet existed as a public network, the International Organization for Standardization published a reference model for how a network should be divided into layers: the &lt;strong&gt;OSI model&lt;/strong&gt;[^1]. Its authors were not describing the existing world. They were arguing about how it ought to be built, and they argued about it for years, mostly about how many floors a building should have.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;OSI was a design document, not a description. The interesting part is not that it failed. It is that the diagram outlived every attempt to build it exactly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The model&amp;#39;s purpose was to let two teams write different layers independently and still fit together, the same way a CPU ISA lets hardware teams and software teams work apart. It is a &lt;strong&gt;logical model&lt;/strong&gt;: an agreed way to name responsibilities, not a list of products.&lt;/p&gt;
&lt;h2&gt;What a layer is&lt;/h2&gt;
&lt;p&gt;A layer is one job, with three obligations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Service.&lt;/strong&gt; It offers a capability to the layer above. &amp;quot;I will deliver these bytes to a machine on my local network.&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interface.&lt;/strong&gt; It tells the layer above how to request that service. Not &amp;quot;what I do inside&amp;quot;, just &amp;quot;how to ask&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Layer below.&lt;/strong&gt; It gets its work done using the layer below, and stays ignorant of how.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rule that makes this cheap: a layer never needs to know how the layer below works, and never needs to know why the layer above called it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Think of an envelope inside an envelope inside an envelope. You write a letter, put it in an envelope addressed to a building, put that in a bag addressed to a city, and hand it to the postal service. Nobody in the chain needs to read the letter to move it one step.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The seven layers&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;7  Application    what the program wants          HTTP, DNS, SMTP
6  Presentation   format, encrypt, compress       TLS, JPEG, encoding
5  Session        start, keep, end conversations  sessions, RPC
4  Transport      deliver to the right program    TCP, UDP, ports
3  Network        find a path across networks     IP, ICMP, routers
2  Data link      deliver to the neighbor          Ethernet, MAC, switches
1  Physical       move bits as signals            cables, radio, fiber
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The numbering counts from the bottom because layer 1 is closest to the wire. Here is each floor in one honest sentence, before this series opens them one by one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7 Application.&lt;/strong&gt; The layer your program actually talks to. You call a function like &amp;quot;send this HTTP request&amp;quot; and the network world takes it from there. No other layer is ever visible to your code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6 Presentation.&lt;/strong&gt; Translation between what the program means and what the wire can carry: character encodings, compression, and encryption. TLS usually lives here in textbooks, which is why TLS placement causes arguments. In practice encryption can sit in several places, and HTTP/3 puts it below the application inside QUIC.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5 Session.&lt;/strong&gt; Opening, maintaining, and closing a conversation between two programs, plus the dialog rules that keep it orderly. Half of this layer was quietly absorbed into the transport layer, and authentication services moved here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4 Transport.&lt;/strong&gt; End-to-end delivery between two &lt;em&gt;programs&lt;/em&gt;, identified by port numbers. This is where reliability, ordering, and flow control live, and where TCP and UDP sit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3 Network.&lt;/strong&gt; Addressing across the whole world and choosing a route, hop by hop, through machines you do not control. This is where IP and routers live.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2 Data link.&lt;/strong&gt; Reliable delivery across a single hop, to the machine on the other end of this cable, using hardware addresses. This is where Ethernet frames and switches live.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1 Physical.&lt;/strong&gt; Bits as electricity, light, or radio waves, plus the media that carry them and the noise they fight.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; when your code calls an HTTP library, layers 7 down to 1 become one continuous pipeline. You never see them, but every byte you send passed through all seven, and every byte you receive passed back up through all seven.&lt;/p&gt;
&lt;h2&gt;Encapsulation: the wrapping&lt;/h2&gt;
&lt;p&gt;Here is the mechanism the whole model rests on. As data moves &lt;strong&gt;down&lt;/strong&gt; the stack on the sending side, each layer adds its own small header, called a &lt;strong&gt;header&lt;/strong&gt;[^2], and passes the result to the layer below. The payload of the layer above becomes the data of the layer below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;sending side, going down:

[ HTTP GET /index.html ]                  layer 7: data
[ TCP | port 443, seq 1 | HTTP GET ... ]  layer 4: segment
[ IP | to 93.184.216.34 | TCP | ... ]     layer 3: packet
[ Ethernet | to aa:bb:cc... | IP | ... ]  layer 2: frame
1 0 1 1 0 0 1 0 0 1 1 0 0 0 0 0 0         layer 1: bits
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each header answers a question the layer below cannot ask the sender. The IP header says where the packet is going. The Ethernet header says which machine on this cable should take it. The TCP header says which program on that machine should get the bytes, and whether they arrived. That is &lt;strong&gt;encapsulation&lt;/strong&gt;[^3].&lt;/p&gt;
&lt;p&gt;On the receiving side the process runs in reverse. Each layer reads its own header, uses it, and strips it before handing the rest up, which is &lt;strong&gt;decapsulation&lt;/strong&gt;. The application at the top never learns that a frame ever existed.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;receiving side, going up:

1 0 1 1 0 ...                    layer 1: bits
[ Ethernet | IP | TCP | HTTP ]    layer 2: read MAC, drop header
[ IP | TCP | HTTP ]              layer 3: right host? drop header
[ TCP | HTTP ]                   layer 4: right port? reassemble, drop header
[ HTTP GET /index.html ]         layer 7: the request, reassembled
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Encapsulation is why you can swap Wi-Fi for a fiber link without changing a single line of your program: the layers above never looked inside.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;PDUs: what each layer&amp;#39;s data is called&lt;/h2&gt;
&lt;p&gt;The data at each level has its own name, called a &lt;strong&gt;protocol data unit&lt;/strong&gt;[^4] (PDU). The names are not important to memorize, but the pattern is: the more a layer knows about the journey, the more meaningful its unit becomes.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;PDU name&lt;/th&gt;
&lt;th&gt;Carries&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;7-6&lt;/td&gt;
&lt;td&gt;Data / message&lt;/td&gt;
&lt;td&gt;what the program means&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;an open conversation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Segment (TCP)&lt;/td&gt;
&lt;td&gt;bytes for one program&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Datagram (UDP)&lt;/td&gt;
&lt;td&gt;one independent message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Packet&lt;/td&gt;
&lt;td&gt;bytes for one host, across hops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Frame&lt;/td&gt;
&lt;td&gt;bytes for one neighbor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Bits&lt;/td&gt;
&lt;td&gt;1s and 0s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Why the model won even though the plan lost&lt;/h2&gt;
&lt;p&gt;No product ever implemented all seven layers as separate pieces of hardware and software. TCP/IP, which the next post opens, does the same job in four floors, folds session and presentation into the application layer, and never heard of the OSI model except to argue with it.&lt;/p&gt;
&lt;p&gt;Yet nobody deletes the diagram. Three reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It names responsibilities.&lt;/strong&gt; When something breaks, &amp;quot;is this a link problem or a transport problem?&amp;quot; is a real, useful question, and the model supplies the vocabulary to ask it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It keeps layers honest.&lt;/strong&gt; Every layer must add something, or it should not exist. Applying that test is how you find designs that pretend to be layered.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It travels.&lt;/strong&gt; The model outlived every piece of 1980s networking hardware and is still the fastest shared vocabulary in the industry.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The layers in this series follow the real four-floor stack, because that is what your bytes actually travel through. Where OSI is useful, this series names it, and it does not spend a post on floors that turned out to be empty.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;A layer is one job with a service, an interface, and no curiosity about its neighbors. OSI proposed seven such layers, from moving bits to speaking a program&amp;#39;s language. Encapsulation is what holds them together: going down, each layer wraps the data above in a header answering one more question; coming up, each layer reads and removes its own header. Each layer&amp;#39;s output is the next layer&amp;#39;s input, which is why the layers can be built by different teams, replaced one at a time, and still fit.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;program&amp;#39;s data
  ↓ wrap    ┌─ transport: for which program?
  ↓ wrap    ┌─ network: for which host, across the world?
  ↓ wrap    ┌─ link: for which machine on this cable?
  ↓ wrap    └─ physical: as signals
bits → signals → frame → packet → segment → data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The model is a map. The next post opens the machine that actually runs the journey: the four layers of TCP/IP, the standard that won, and the philosophy that let a network of networks exist at all.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/OSI_model&quot;&gt;OSI model - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Header_(computing)&quot;&gt;Header (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Encapsulation_(networking)&quot;&gt;Encapsulation (networking) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Protocol_data_unit&quot;&gt;Protocol data unit - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>networking</category><category>osi</category><category>encapsulation</category><category>how-it-works</category></item><item><title>Networks Explained</title><link>https://www.wesleybertipaglia.com/blog/ze2vg26v/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/ze2vg26v/</guid><description>A broad tour of what a computer network is: moving from isolated nodes to interconnected systems, packet switching vs. circuit switching, topologies, and the core problem networks solve.</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/x0g3dogf&quot;&gt;input and output&lt;/a&gt; post named the network card as a device that is input and output at the same time, and left it there. The &lt;a href=&quot;/blog/zkb3xkdy&quot;&gt;operating system&lt;/a&gt; post showed the kernel talking to that card like it talks to a keyboard or a disk. Neither post asked the obvious question: who is on the other end? This post opens the world outside the machine. What a network actually is, why machines share a road instead of owning it, and the problem that every layer of the next nine posts exists to solve.&lt;/p&gt;
&lt;h2&gt;What a network is&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;computer network&lt;/strong&gt;[^1] is two or more machines connected together, able to exchange information. That is the whole definition, and it sounds trivial until you count what it hides.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;node&lt;/strong&gt; is any machine with a network address: your laptop, a phone, a printer, a router, a server in a data center. A &lt;strong&gt;link&lt;/strong&gt; is the road between two nodes: a cable, a radio wave, a fiber strand, a satellite hop. A &lt;strong&gt;protocol&lt;/strong&gt;[^2] is the agreement on how bits turn into meaning on that road. Two machines speaking different protocols on the same wire hear only noise.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A network is not the cable. The cable is just copper or glass. The network is the agreement plus the addressing plus the machinery that delivers bytes to the right machine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;node ──── link ──── node
 │                      │
 └── same protocol ─────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The machine inside your laptop is already a full computer with its own memory and CPU. A network does not merge those machines into one super-computer. It gives each one a way to say &amp;quot;send these bytes over there&amp;quot; and to receive bytes meant for it. Isolation stays; communication is added.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; when you print a document, your laptop does not become part of the printer. It sends bytes to the printer&amp;#39;s address, and the printer pulls them out of its own memory buffer. Two computers, two memories, one agreed road.&lt;/p&gt;
&lt;h2&gt;Why not connect every machine to every machine&lt;/h2&gt;
&lt;p&gt;The naive way to link five machines is a dedicated cable between each pair: 5 × 4 ÷ 2 = 10 cables. Twenty machines need 190. A thousand machines need half a million. Every port and every cable is a cost, a failure point, and a configuration headache. No network ever shipped this way.&lt;/p&gt;
&lt;p&gt;The fix is the same idea as a street system: do not build a private road between every pair of houses. Build shared roads, give every house an address, and let the roads carry everyone&amp;#39;s traffic.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;naive:      A ─ B   A ─ C   A ─ D   B ─ C   B ─ D   C ─ D
shared:     A ─┐
                ├─ shared road ─┬─ B
             C ─┘               └─ D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the number of links grows with the number of machines, not with its square. Somebody still has to do the work of reading the destination address and forwarding each message: that somebody is a &lt;strong&gt;router&lt;/strong&gt;[^3], and the machine that gives your laptop its first step onto the road is a &lt;strong&gt;switch&lt;/strong&gt;. The rest of this series is about the layers of machinery that make one shared road serve millions of machines at once, reliably.&lt;/p&gt;
&lt;h2&gt;Two ways to share a road&lt;/h2&gt;
&lt;p&gt;When many senders want one road, there are two classic answers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Circuit switching&lt;/strong&gt;[^4] reserves the road for one conversation at a time. You get a dedicated path for the whole call, nobody else can use it, and the order of your data is guaranteed. The telephone network worked this way for a century. The cost is obvious once you notice it: the reserved parts sit idle whenever you are not talking. A voice call uses maybe 20% of a reserved circuit in each direction, and the other 80% is wasted capacity that no one else is allowed to touch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Packet switching&lt;/strong&gt;[^5] shares the road by chopping the data into small pieces called &lt;strong&gt;packets&lt;/strong&gt;[^6]. Each packet carries its destination address and travels independently. Many senders interleave their packets on the same road, and the receiver reassembles them in order.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;circuit switching:
sender ────────────────► receiver      (the whole road, reserved)

packet switching:
A: [p1][p2][p3]   B: [q1][q2]   C: [r1]
   [p1] [q1] [p2] [r1] [p3] [q2]      (one road, many senders)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Packet switching wins because it never reserves anything. The road carries whatever anyone has to send, and idle capacity is used by the next packet. It also lets packets from the same sender take different routes across the internet, a property that turns out to be a feature rather than a bug.&lt;/p&gt;
&lt;p&gt;But the freedom has a price, and this price is the reason the internet needed most of this series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Packets can be lost.&lt;/strong&gt; Memory and queues are finite; a burst of traffic overruns a router and something has to be dropped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Packets can arrive out of order.&lt;/strong&gt; Two packets take two paths or two turns in a queue.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Packets can be duplicated.&lt;/strong&gt; A router unsure whether a packet arrived may send it again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Packets can be corrupted.&lt;/strong&gt; Noise on the wire flips bits.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the network itself is &lt;strong&gt;best effort&lt;/strong&gt;: it makes no promises. It carries what it can and shrugs at the rest. Every layer above it exists to build the guarantees that best effort refuses to give.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The internet is not a reliable network that sometimes fails. It is an unreliable network that applications have learned to make reliable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Topology: how machines are wired together&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Network topology&lt;/strong&gt;[^7] is the shape of the connections: who is wired to whom. A few shapes recur.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;bus:         A ─ B ─ C ─ D        (everyone shares one cable)
star:        A ─┐
                 ├─ (hub/switch)
              B ─┘
mesh:        A ─── B
             │  ╲ │  ╱
             │    ╳
             │  ╱ │  ╲
             C ─── D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;bus&lt;/strong&gt; was the first cheap answer: one cable, every machine taps it, everyone hears everything. It collapses when a cable breaks or two machines transmit at once, because signals collide. The &lt;strong&gt;star&lt;/strong&gt; fixes that with a central box, and it won. Modern home and office networks are stars: every machine has its own cable to a &lt;strong&gt;switch&lt;/strong&gt;, which is a box that learns addresses and forwards each frame only where it is needed. The &lt;strong&gt;mesh&lt;/strong&gt; survives any single failure because there is no single point of failure, which is why it shows up between routers and data centers rather than between laptops.&lt;/p&gt;
&lt;p&gt;Note what the star really is: one shared medium, split into private lanes. Each machine gets a dedicated cable into the switch, and the switch makes sure no two machines speak at the same time on the wire. Contention does not disappear in a star; it moves inside the box, where it can be managed.&lt;/p&gt;
&lt;h2&gt;The core problem networks solve&lt;/h2&gt;
&lt;p&gt;Every network, from two laptops with one cable to the entire internet, is solving the same set of problems. It is worth naming them now, because each later post is the answer to one of these lines.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Naming and addressing:&lt;/strong&gt; who is the machine I want to talk to, and how does my data carry that answer?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delivery:&lt;/strong&gt; given an address, how does the data travel across a road that may not lead there, over hops that belong to other people?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reliability:&lt;/strong&gt; the road loses and reorders packets. Who notices, and who sends them again?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sharing:&lt;/strong&gt; one road, many senders, and a way to take turns without anyone starving.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Governance:&lt;/strong&gt; no one owns the internet. Millions of independent networks agree to carry each other&amp;#39;s traffic and to be polite about it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security:&lt;/strong&gt; strangers share the same road. How do you keep them from reading or changing what is not theirs?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last set is why a network is not just fast wires. Speed is the easy part.&lt;/p&gt;
&lt;h2&gt;Why a network is not one big machine&lt;/h2&gt;
&lt;p&gt;There is one more design decision before the layers, and it is the one this series is really about.&lt;/p&gt;
&lt;p&gt;One plausible design: a single protocol that does everything. Name resolution, routing, reliability, encryption, the web. One code path, one place to debug.&lt;/p&gt;
&lt;p&gt;It does not work. Every use is different: a video game cannot wait for lost packets, a file transfer must, a printer needs a language nobody else speaks. One protocol for all of them forces every case to pay for every feature.&lt;/p&gt;
&lt;p&gt;So networks are built in &lt;strong&gt;layers&lt;/strong&gt;. Each layer offers a service to the layer above and uses the layer below without knowing how it works. The link layer delivers bytes to the neighbor. The network layer finds a path across the world. The transport layer makes delivery reliable for one program. The application layer speaks the language your program actually wants.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;your program:      &amp;quot;get me this page&amp;quot;
   ↓
application:       HTTP request
   ↓
transport:         reliable bytes to one program
   ↓
network:           packets across every hop
   ↓
link:              frames to the next machine
   ↓
physical:          volts on a wire
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each layer gets to be simple because it trusts the layer below. And each one can be replaced, improved, or ignored without rewriting the others, which is exactly why the internet kept working while everything got replaced.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The physical layer is not at the bottom of importance, only at the bottom of the stack. It still decides whether your video works.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; when Wi-Fi got faster, the link changed and almost nothing above it did. When TCP was replaced by QUIC in HTTP/3, the physical layer never heard about it. Layering is not bureaucracy; it is what let the internet survive five decades of replacing every part.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;A network is machines plus roads plus agreements. Wiring every pair directly does not scale, so machines share roads with addresses, and routers do the forwarding. Sharing a road means chopping data into packets that may be lost, reordered, duplicated, or corrupted, so the network itself promises nothing. On top of that best effort, layers each add one guarantee, from &amp;quot;these bits reach the neighbor&amp;quot; up to &amp;quot;here is your web page&amp;quot;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;nodes + links + protocol
  ↓
shared roads, addressed (routers forward)
  ↓
packets: lossy, out of order, duplicated
  ↓
layers, each adding one guarantee
  ↓
programs that can rely on something
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next post opens the model that organizes those layers, the famous seven-floor list everyone half-remembers from school, and what actually happens to your data each time it crosses one.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer_network&quot;&gt;Computer network - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Network_protocol&quot;&gt;Network protocol - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Router_(computing)&quot;&gt;Router (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Circuit_switching&quot;&gt;Circuit switching - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Packet_switching&quot;&gt;Packet switching - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Network_packet&quot;&gt;Network packet - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Network_topology&quot;&gt;Network topology - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>networking</category><category>internals</category><category>how-it-works</category></item><item><title>From Power-On to main()</title><link>https://www.wesleybertipaglia.com/blog/6bbtdrie/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/6bbtdrie/</guid><description>The capstone of the Operating Systems Explained series: the full journey from power button to your program’s main.</description><pubDate>Thu, 17 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every post in this series assumed a running kernel: processes scheduled, pages translated, files mounted, devices humming. The &lt;a href=&quot;/blog/xpbttuoq&quot;&gt;I/O&lt;/a&gt; post named the missing piece explicitly: RAM is empty at power-on and no kernel exists anywhere. This capstone closes that gap and the two series with it. We will follow one press of the power button through firmware, bootloader, kernel startup, the first user process, the program loader, and the handoff to &lt;code&gt;main&lt;/code&gt;, meeting every earlier post&amp;#39;s machinery as it comes online.&lt;/p&gt;
&lt;h2&gt;Firmware: the first instructions&lt;/h2&gt;
&lt;p&gt;At power-on the CPU resets to a known state: caches cold, RAM empty and untested, devices uninitialized. It fetches from a hardwired address pointing at &lt;strong&gt;firmware&lt;/strong&gt;[^1] in non-volatile memory (BIOS[^2] on old machines, &lt;strong&gt;UEFI&lt;/strong&gt;[^3] on modern ones). Firmware runs before anything else exists: it tests RAM, enumerates devices, initializes DRAM controllers and clocks, and finds something bootable.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;power ──▶ CPU reset vector ──▶ firmware (ROM/flash)
                                   │ POST, init RAM, enumerate
                                   ▼
                              find boot device
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Firmware&amp;#39;s last job is loading the next stage and jumping to it. The chain has begun: each stage just smart enough to load a smarter one.&lt;/p&gt;
&lt;h2&gt;Bootloader: the kernel&amp;#39;s delivery service&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;bootloader&lt;/strong&gt;[^4] (GRUB, systemd-boot, phone bootloaders) bridges firmware and kernel. Firmware loads it; it understands filesystems (unlike firmware, which reads raw sectors or simple partitions), finds the kernel image and initial RAM disk, sets up the boot parameters and memory map, and transfers control. The two-stage trick exists because firmware stays small and dumb while the bootloader, living on disk it can update, carries the complexity.&lt;/p&gt;
&lt;p&gt;On UEFI systems the path is standardized: firmware reads the EFI partition, executes the bootloader application, which calls back into UEFI services for disk and display until the kernel takes over and those services are abandoned (&lt;strong&gt;ExitBootServices&lt;/strong&gt;). The machine is still single-tasked; no processes, no virtual memory, just code running on bare metal toward one goal.&lt;/p&gt;
&lt;h2&gt;Kernel startup: building the world&lt;/h2&gt;
&lt;p&gt;The kernel entry point runs the most delicate code in the system: no stack worth speaking of, no heap, no scheduler, no memory management yet. Startup builds every subsystem this series described, in dependency order:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;entry ──▶ early page tables ──▶ memory allocator ──▶ interrupts &amp;amp; timer
              │                       │                      │
              ▼                       ▼                      ▼
        virtual memory on      kmalloc works          preemption possible
              │                       │                      │
              └───────────┬───────────┘                      │
                          ▼                                  ▼
                    scheduler + drivers + VFS ──▶ mount root ──▶ spawn init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Early page tables turn on virtual memory (the &lt;a href=&quot;/blog/j5n50ly4&quot;&gt;paging&lt;/a&gt; machinery now active for the kernel itself); the page allocator and slab allocator follow; interrupt descriptors and the timer IRQ enable preemption (the &lt;a href=&quot;/blog/fwq0k4n2&quot;&gt;scheduler&lt;/a&gt; can now exist); drivers probe buses; the virtual filesystem layer mounts the root filesystem (the &lt;a href=&quot;/blog/dlxizd4r&quot;&gt;filesystems&lt;/a&gt; world now reachable). Each layer assumes only what came before, which is why the order is fixed.&lt;/p&gt;
&lt;h2&gt;Init: the first process&lt;/h2&gt;
&lt;p&gt;With the kernel alive, it becomes a process factory and manufactures process number one. On Unix the kernel spawns &lt;strong&gt;init&lt;/strong&gt;[^5] (pid 1, systemd on most Linux systems); orphaned processes get adopted by it, which is why the &lt;a href=&quot;/blog/s8qmq1mn&quot;&gt;processes&lt;/a&gt; post&amp;#39;s reaping promise holds. Init mounts filesystems, starts services, and brings up login: the machine graduates from one kernel thread to a managed multi-process system. Everything you think of as &amp;quot;the OS running&amp;quot; is init&amp;#39;s descendants, scheduled, isolated, and page-tabled exactly as earlier posts described.&lt;/p&gt;
&lt;h2&gt;Loader: from file to process&lt;/h2&gt;
&lt;p&gt;Starting your program replays process birth at full detail. The shell forks; the child calls exec on your binary; the kernel&amp;#39;s &lt;strong&gt;loader&lt;/strong&gt; takes over: parse the &lt;strong&gt;ELF&lt;/strong&gt;[^6] headers, map code and data segments into a fresh address space, map shared libraries (or the dynamic linker that maps them), zero the BSS, allocate the stack with arguments and environment, set the entry point, and mark the process ready.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;fork ──▶ exec(&amp;quot;./app&amp;quot;) ──▶ parse ELF ──▶ map segments + libraries
                                              │
                                              ▼
                              stack (argv, env) ──▶ jump to _start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_start&lt;/code&gt; is not your &lt;code&gt;main&lt;/code&gt;. It is the C runtime&amp;#39;s entry: set up the standard library, run global constructors, collect the exit code afterward. Its last act is calling your code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;_start: ──▶ __libc_start_main ──▶ main(argc, argv, envp) ──▶ exit(status)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eight posts of machinery narrow to one function call. &lt;code&gt;main&lt;/code&gt; returns, the runtime calls &lt;code&gt;exit&lt;/code&gt;, the kernel reaps, the parent is notified. Birth to death, syscalls at both ends.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Two series, one arc. The Computers Explained series built the machine: representation, logic, CPU, memory, storage, I/O, interconnects, and the stored-program idea that makes one machine every machine. The Operating Systems Explained series taught that machine to share: kernel and syscalls, processes, threads, scheduling, virtual memory, filesystems, I/O paths, and now the boot that starts it all. Press power and firmware wakes, the bootloader delivers, the kernel builds its world layer by layer, init fathers every process, the loader maps your bytes, and &lt;code&gt;_start&lt;/code&gt; calls &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Whatever you build on top, from Fibonacci to distributed systems, runs on this stack. The details will keep evolving: new firmware, new schedulers, new filesystems. The shape, stages loading stages until a program believes the machine is its own, will keep rhyming.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Firmware&quot;&gt;Firmware - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/BIOS&quot;&gt;BIOS - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/UEFI&quot;&gt;UEFI - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Bootloader&quot;&gt;Bootloader - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Init&quot;&gt;Init - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Executable_and_Linkable_Format&quot;&gt;Executable and Linkable Format - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Booting&quot;&gt;Booting - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Power-on_self-test&quot;&gt;Power-on self-test - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Initial_ramdisk&quot;&gt;Initial RAM disk - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/_start&quot;&gt;_start - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>os</category><category>boot</category><category>internals</category><category>how-it-works</category></item><item><title>I/O &amp; Syscalls Explained</title><link>https://www.wesleybertipaglia.com/blog/xpbttuoq/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/xpbttuoq/</guid><description>A deep dive into input/output and system calls: blocking versus non-blocking I/O, multiplexing with poll and epoll, buffers and caches, and the data path from a read call down to the hardware.</description><pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/dlxizd4r&quot;&gt;filesystems&lt;/a&gt; post ended with an open thread: files live on disk, but programs read keyboards, screens, and sockets through the same syscalls. The &lt;a href=&quot;/blog/x0g3dogf&quot;&gt;hardware I/O&lt;/a&gt; post showed the device side (controllers, interrupts, DMA); this post shows the OS side. We will look at how syscalls cross into the kernel in practice, what blocking really means, the non-blocking and multiplexed alternatives, the layers of buffering along the way, and the full path of one &lt;code&gt;read&lt;/code&gt; from program to platter and back.&lt;/p&gt;
&lt;h2&gt;Syscalls in practice: the cost of crossing&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;system call&lt;/strong&gt; crosses privilege levels: trap instruction, argument validation, mode switch, work, copy results back, return. The crossing costs hundreds of nanoseconds to microseconds, trivial once, significant a million times per second. Three techniques keep the bill down.&lt;/p&gt;
&lt;p&gt;First, &lt;strong&gt;batching&lt;/strong&gt;: the C library buffers. &lt;code&gt;printf&lt;/code&gt; accumulates in userspace and issues one &lt;code&gt;write&lt;/code&gt; for thousands of characters; &lt;code&gt;malloc&lt;/code&gt; subdivides kernel pages so most allocations never trap. Second, &lt;strong&gt;vector I/O&lt;/strong&gt; (&lt;code&gt;readv&lt;/code&gt;, &lt;code&gt;writev&lt;/code&gt;): one crossing moves many scattered buffers. Third, &lt;strong&gt;avoidance&lt;/strong&gt;: &lt;code&gt;vDSO&lt;/code&gt; maps a few read-only kernel results (time, CPU number) into userspace so &lt;code&gt;gettimeofday&lt;/code&gt; often never traps at all.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;program              libc                  kernel
printf(&amp;quot;hi&amp;quot;) ──▶  buffer it          ──▶  (nothing yet)
printf(&amp;quot;ho&amp;quot;) ──▶  buffer it          ──▶  (nothing yet)
fflush()     ──▶  one write(fd,&amp;quot;hiho&amp;quot;)──▶  validate, copy, queue to device
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Strace from the overview post shows the seam: every line is one crossing, with arguments, return value, and time spent. Reading strace output is reading the program&amp;#39;s conversation with the OS, syscall by syscall.&lt;/p&gt;
&lt;h2&gt;Blocking I/O: sleep until ready&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Blocking I/O&lt;/strong&gt;[^1] is the default: a &lt;code&gt;read&lt;/code&gt; on an empty socket suspends the thread until data arrives. The kernel parks the thread in the blocked state, schedules someone else, and wakes the sleeper when the device interrupts or the data lands. The thread pays nothing while waiting; the CPU stays busy elsewhere. Simple to program (code reads top to bottom), efficient under light concurrency, awkward at scale: ten thousand connections need ten thousand threads, each with its own stack and scheduling cost.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;thread calls read(fd) ──▶ no data → BLOCKED, scheduler runs others
device interrupts ──▶ kernel copies data ──▶ thread READY → RUNNING, read returns
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Blocking is honest waiting: the thread declared it cannot proceed, and the kernel believed it.&lt;/p&gt;
&lt;h2&gt;Non-blocking and multiplexed I/O: ask, don&amp;#39;t sleep&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Non-blocking I/O&lt;/strong&gt; flips the contract: the call returns immediately, with data or with &lt;code&gt;EAGAIN&lt;/code&gt; (&amp;quot;not ready, try later&amp;quot;). The thread never sleeps, but somebody must retry, which naive polling turns into a CPU-burning loop. The answer is &lt;strong&gt;I/O multiplexing&lt;/strong&gt;[^2]: one syscall watches many descriptors and sleeps until at least one is ready.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// wait until some fds are readable, then handle only those
struct pollfd fds[] = {{fd1, POLLIN}, {fd2, POLLIN}};
poll(fds, 2, -1);
for (int i = 0; i &amp;lt; 2; i++)
    if (fds[i].revents &amp;amp; POLLIN)
        handle(fds[i].fd);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;select&lt;/code&gt; and &lt;code&gt;poll&lt;/code&gt; scan the whole set per call; &lt;strong&gt;epoll&lt;/strong&gt; (Linux) and &lt;code&gt;kqueue&lt;/code&gt; (BSD/macOS) keep the interest set in the kernel and report only changes, scaling to tens of thousands of connections. This is the engine inside nginx, Node.js, and every event loop: one thread, many sockets, wakeups only where work waits. &lt;strong&gt;Asynchronous I/O&lt;/strong&gt; (&lt;code&gt;io_uring&lt;/code&gt; on Linux) pushes further: submit operations through shared queues and harvest completions, batching submissions and avoiding per-operation syscalls entirely.&lt;/p&gt;
&lt;h2&gt;Buffers everywhere: the data sits still so it can move fast&lt;/h2&gt;
&lt;p&gt;Between program and device sit layers of staging memory, each hiding a speed mismatch:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Userspace buffers&lt;/strong&gt; (stdio, language runtimes): coalesce tiny writes into few syscalls.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Page cache&lt;/strong&gt;: the kernel keeps file data in RAM; reads hit cache instead of disk, writes land in cache and flush lazily (&lt;strong&gt;write-back&lt;/strong&gt;). Most &amp;quot;disk&amp;quot; I/O never reaches any disk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device queues&lt;/strong&gt;: the I/O scheduler orders pending requests (merge adjacent sectors, prioritize deadlines, keep the device streaming).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;printf ──▶ stdio buffer ──▶ write() ──▶ page cache ──▶ I/O scheduler ──▶ device
           (userspace)        (kernel memory)            (queue)          (DMA)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each layer answers the same question with more patience than the one above: wait a little, batch a lot, touch slow hardware rarely.&lt;/p&gt;
&lt;h2&gt;One read, end to end&lt;/h2&gt;
&lt;p&gt;Trace &lt;code&gt;read(fd, buf, 4096)&lt;/code&gt; on a disk file, cache cold:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. trap: validate fd, check permissions, find file offset
2. page cache lookup: miss → allocate page, queue device read
3. driver programs DMA: device copies blocks straight to RAM, CPU free
4. interrupt: transfer done → wake blocked thread
5. copy_to_user: page → program buffer, advance offset, return 4096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Five crossings of boundaries (user/kernel, cache/device, interrupt/thread), most of them invisible to the program, which slept through steps 2–4 and woke with bytes ready. Sockets differ in details (protocol stack instead of filesystem, network card instead of disk) but keep the shape: validate, buffer, queue, interrupt, wake, copy.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Syscalls cost crossings, so libraries batch and vectors widen; blocking I/O sleeps honestly and scales poorly; non-blocking plus multiplexing (poll, epoll, io_uring) scales one thread to thousands of descriptors; buffers at every level trade patience for throughput; and one &lt;code&gt;read&lt;/code&gt; descends through cache, driver, DMA, and interrupt before returning with bytes.&lt;/p&gt;
&lt;p&gt;Every layer so far assumed a running kernel: processes scheduled, pages translated, files mounted, devices humming. None of it explains how the machine gets there from a dead power-on, with RAM empty and no kernel anywhere. Booting into the first process, and &lt;code&gt;main()&lt;/code&gt;, is the capstone&amp;#39;s subject.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Blocking_(computing)&quot;&gt;Blocking (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/I/O_multiplexing&quot;&gt;I/O multiplexing - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/System_call&quot;&gt;System call - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Page_cache&quot;&gt;Page cache - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Direct_memory_access&quot;&gt;Direct memory access - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Epoll&quot;&gt;Epoll - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Kqueue&quot;&gt;Kqueue - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Io_uring&quot;&gt;Io uring - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Interrupt&quot;&gt;Interrupt - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Device_driver&quot;&gt;Device driver - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>os</category><category>io</category><category>syscalls</category><category>how-it-works</category></item><item><title>Filesystems Explained</title><link>https://www.wesleybertipaglia.com/blog/dlxizd4r/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/dlxizd4r/</guid><description>A deep dive into filesystems: files and inodes, directories as tables, journaling and crash consistency, permissions, and how blocks on a disk become names you can open.</description><pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/j5n50ly4&quot;&gt;virtual memory&lt;/a&gt; post ended with a boundary: RAM forgets and swap is scratch, so anything worth keeping needs a durable home. Disks hold blocks; humans want files with names, folders, and permissions that survive power loss. The bridge is the &lt;strong&gt;file system&lt;/strong&gt;[^1]. This post opens it up. We will look at what a file really is, how directories map names to files, how the system survives crashes mid-write, who is allowed to do what, and the path a single &lt;code&gt;open&lt;/code&gt; takes from name to bytes.&lt;/p&gt;
&lt;h2&gt;Files and inodes: names point at metadata&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;file&lt;/strong&gt; is two things: a name users see, and numbered blocks holding bytes. Between them sits the &lt;strong&gt;inode&lt;/strong&gt;[^2] (index node): one record per file holding everything except the name itself (size, owner, permissions, timestamps, and pointers to the data blocks). Directory entries map names to inode numbers; the inode maps onward to blocks.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;directory entry              inode 4217                 data blocks
/home/you/notes.txt ──▶  no. 4217 ──▶ size, owner, ──▶ block 91
                                         mode, times,     block 92
                                         pointers…        block 118
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Separating names from metadata buys two features for free. &lt;strong&gt;Hard links&lt;/strong&gt; are two names pointing at one inode: same file, two doors, edits visible through both, blocks freed only when the last name (and open handle) goes away. And renaming is cheap: it rewrites a directory entry, never moves bytes.&lt;/p&gt;
&lt;p&gt;Large files need many pointers, so inodes layer them: a few direct pointers for small files, then indirect blocks (a block full of pointers), double-indirect, triple-indirect for the enormous. Small files cost one inode; huge files scale without changing the record&amp;#39;s size.&lt;/p&gt;
&lt;h2&gt;Directories: tables all the way down&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;directory&lt;/strong&gt;[^3] is a file with a special format: a table mapping names to inode numbers. Opening &lt;code&gt;/home/you/notes.txt&lt;/code&gt; walks the path component by component, starting from the root inode: look up &lt;code&gt;home&lt;/code&gt; in &lt;code&gt;/&lt;/code&gt;, get its inode, look up &lt;code&gt;you&lt;/code&gt; in it, get that inode, look up &lt;code&gt;notes.txt&lt;/code&gt;, arrive at inode 4217. Each step is a lookup in a table; the kernel caches recent translations (the dentry cache) so repeated walks stay fast.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/ (inode 2)         /home (inode 88)      /home/you (inode 301)
home → 88     ──▶   you → 301       ──▶   notes.txt → 4217
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mounting grafts one filesystem&amp;#39;s tree onto another&amp;#39;s directory: the mounted root covers the mount point, and paths cross the boundary invisibly. Your program sees one tree; the kernel sees a table of mounted filesystems stitched together.&lt;/p&gt;
&lt;h2&gt;Crash consistency: the write that was half done&lt;/h2&gt;
&lt;p&gt;Power can die between any two disk writes, leaving structures half-updated: a file&amp;#39;s size bumped but its blocks unrecorded, an inode allocated but unlinked. The classic answer is &lt;strong&gt;journaling&lt;/strong&gt;[^4]: write the intention to a sequential log first, then apply to the real structures, then mark the log entry done. After a crash, the kernel replays completed intentions and discards partial ones: every operation either happened or did not, never half.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. journal: &amp;quot;create notes.txt → inode 4217, blocks 91–92&amp;quot;  (sequential, fast)
2. apply:   write inode, directory entry, bitmaps           (in place)
3. journal: mark done, reclaim log space
   ── crash anywhere ──▶ replay redoes step 2 or drops it
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatives trade differently. &lt;strong&gt;Copy-on-write&lt;/strong&gt; filesystems never overwrite live data: they write new versions elsewhere and flip one pointer atomically, so the old tree is always intact (snapshots fall out naturally). &lt;code&gt;fsck&lt;/code&gt;-style &lt;strong&gt;consistency checking&lt;/strong&gt; scans and repairs after the fact instead of preventing damage. All three serve one contract: a crash may lose the last seconds, but never the filesystem&amp;#39;s coherence.&lt;/p&gt;
&lt;h2&gt;Permissions: who may do what&lt;/h2&gt;
&lt;p&gt;Every inode carries an owner, a group, and &lt;strong&gt;mode bits&lt;/strong&gt;[^5]: read, write, execute for user, group, and others (&lt;code&gt;rwxr-xr--&lt;/code&gt; reads as owner-all, group-read-execute, other-read). Directories reuse the bits with shifted meaning: read lists entries, write creates and deletes, execute traverses through. The kernel checks on every &lt;code&gt;open&lt;/code&gt;: match the process&amp;#39;s user and groups against the bits, allow or deny with &lt;code&gt;EACCES&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;-rw-r--r--  you  devs   notes.txt   (owner rw, group r, other r)
drwxr-x---  you  devs   project/    (group members enter, others barred)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Setuid, the set-group-ID bit, and the sticky bit extend the scheme (run-as-owner executables, shared-directory protections), and modern systems layer &lt;strong&gt;access-control lists&lt;/strong&gt; for per-user entries beyond the three classes. Permissions are checked at open time against names; open file descriptors keep working even if the name is later unlinked, which is how safe temporary files and atomic replacement (&lt;code&gt;write temp, rename over&lt;/code&gt;) work.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Blocks become files through inodes (metadata without names), names resolve through directories (tables mapping to inodes, walked component by component), journaling makes updates atomic across crashes, and mode bits gate every open by user and group. One &lt;code&gt;open&lt;/code&gt; call walks, checks, and translates; reads and writes then stream through the page cache to disk in the background.&lt;/p&gt;
&lt;p&gt;But files are only half of I/O. Keyboards, screens, and sockets are not files on disk, yet programs read and write them through the same syscalls, blocking, waking, and moving bytes down to hardware that answers in interrupts. Unifying that path, and the syscalls that drive it, is the next post&amp;#39;s subject.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/File_system&quot;&gt;File system - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Inode&quot;&gt;Inode - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Directory_(computing)&quot;&gt;Directory (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Journaling_file_system&quot;&gt;Journaling file system - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/File-system_permissions&quot;&gt;File-system permissions - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Hard_link&quot;&gt;Hard link - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Dentry&quot;&gt;Directory entry - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Copy-on-write&quot;&gt;Copy-on-write - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Fsck&quot;&gt;Fsck - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Access-control_list&quot;&gt;Access-control list - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>os</category><category>filesystems</category><category>storage</category><category>how-it-works</category></item><item><title>Virtual Memory Explained</title><link>https://www.wesleybertipaglia.com/blog/j5n50ly4/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/j5n50ly4/</guid><description>A deep dive into virtual memory: address spaces, paging and page tables, the TLB, page faults, swap, mmap and brk, and how malloc finally gets its memory from the OS.</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/fwq0k4n2&quot;&gt;scheduling&lt;/a&gt; post decided who runs; this post decides what each runner sees. Every process believes it owns gigabytes of private, contiguous memory, yet there is one RAM shared among all tenants. The fiction is &lt;strong&gt;virtual memory&lt;/strong&gt;[^1], and it also closes a loop opened two series ago: the &lt;a href=&quot;/blog/cmvoxsqk&quot;&gt;CPU&lt;/a&gt; post waved at &lt;code&gt;malloc&lt;/code&gt; as library bookkeeping over &lt;code&gt;brk&lt;/code&gt; and &lt;code&gt;mmap&lt;/code&gt; without explaining either. We will look at address spaces, paging and page tables, the TLB that makes translation affordable, page faults and swap, and finally how &lt;code&gt;mmap&lt;/code&gt;, &lt;code&gt;brk&lt;/code&gt;, and &lt;code&gt;malloc&lt;/code&gt; fit together.&lt;/p&gt;
&lt;h2&gt;Address spaces: every process gets its own universe&lt;/h2&gt;
&lt;p&gt;An &lt;strong&gt;address space&lt;/strong&gt; is the set of virtual addresses a process may use: its code, heap, stacks, shared libraries, and kernel mappings, each at fixed conventional places. The addresses are virtual: they name slots in the process&amp;#39;s private universe, not bytes in RAM. The memory hardware translates every virtual address to a physical one on each access, consulting the current process&amp;#39;s page table, and refuses translations the table forbids.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;process A addresses        RAM                process B addresses
0x400000: code      ──▶  frame 91              0x400000: code ──▶ frame 12
0x7fff00: stack     ──▶  frame 7               0x7fff00: stack ──▶ frame 44
0x900000: (unmapped)──▶  fault                 (same numbers, other frames)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same numbers, different frames, enforced by hardware. That is the whole isolation trick from the processes post, stated as mechanism: separate tables, separate worlds.&lt;/p&gt;
&lt;h2&gt;Paging: memory in fixed-size sheets&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Paging&lt;/strong&gt;[^2] divides virtual memory into fixed-size &lt;strong&gt;pages&lt;/strong&gt; (commonly 4 KiB) and RAM into same-size &lt;strong&gt;frames&lt;/strong&gt;. Each page maps to some frame, or to nothing, or to storage. The &lt;strong&gt;page table&lt;/strong&gt;[^3] records the mapping plus permission bits (read, write, execute, user-accessible) checked on every access.&lt;/p&gt;
&lt;p&gt;A virtual address splits into two halves: the page number selects the table entry, the offset selects the byte within the frame:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;virtual address (64-bit, 4 KiB pages)
┌──────────────────────────────┬──────────────┐
│ page number (52 bits)         │ offset (12)  │
└──────────────┬───────────────┴──────┬───────┘
               ▼                      │
        page table lookup             │
        page 7 → frame 42, rw         │
               ▼                      ▼
        physical address = frame 42 + offset
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Real tables are &lt;strong&gt;multi-level&lt;/strong&gt; (four levels on x86-64): unmapped regions cost no table memory because whole subtrees are simply absent. Permissions ride along for free: mark code pages execute-but-not-write, mark kernel pages supervisor-only, and entire bug classes (overwriting code, user code reading kernel memory) die in hardware with a fault instead of silent corruption.&lt;/p&gt;
&lt;h2&gt;The TLB: making translation affordable&lt;/h2&gt;
&lt;p&gt;Translating every access through four table levels in RAM would multiply memory traffic severalfold. The &lt;strong&gt;translation lookaside buffer&lt;/strong&gt; (TLB)[^4] avoids it: a tiny cache inside the memory unit holding recent translations. Common case, the translation hits the TLB and costs roughly nothing; rare case, the hardware walks the table, caches the result, and continues. Locality saves the day again: programs reuse pages, so a few dozen TLB entries cover most accesses.&lt;/p&gt;
&lt;p&gt;The TLB explains two costs from earlier posts. Process switches must invalidate (or retag) TLB entries, since translations belong to one address space: part of the hidden price of a context switch. And huge working sets that thrash the TLB pay a walk per access, one reason large pages (2 MiB, 1 GiB) exist: fewer, bigger translations covering the same memory with fewer entries.&lt;/p&gt;
&lt;h2&gt;Page faults and swap: memory larger than RAM&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;page fault&lt;/strong&gt;[^5] fires when translation fails: the page is unmapped, protected, or not currently resident. Faults are not always errors. The kernel deliberately leaves pages non-resident and fills them on demand: the first touch of fresh memory, a page &lt;strong&gt;swapped&lt;/strong&gt; out to disk, a file mapping not yet read, a copy-on-write page shared since fork. The fault handler finds a frame (evicting some victim page, writing it to &lt;strong&gt;swap space&lt;/strong&gt;[^6] if dirty), loads the contents, updates the table, and resumes the faulting instruction as if nothing happened.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;program touches page ──▶ TLB miss, table says &amp;quot;on disk&amp;quot;
                          │
                          ▼
                     fault handler: evict victim frame,
                     read page from swap/file, map it,
                     resume instruction
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eviction policy decides which victim to sacrifice: approximations of least-recently-used (clock algorithms) track usage bits the hardware sets, sweeping hands around a circular list. The catastrophic failure has a name: &lt;strong&gt;thrashing&lt;/strong&gt;, when the working set exceeds RAM and every access faults, so the machine spends its time paging instead of computing. The cure is fewer tenants or more RAM; no algorithm outruns arithmetic.&lt;/p&gt;
&lt;h2&gt;mmap, brk, and malloc: where heap memory comes from&lt;/h2&gt;
&lt;p&gt;Now the loop closes. The kernel hands out memory in pages through two syscalls. &lt;strong&gt;brk&lt;/strong&gt; (and &lt;code&gt;sbrk&lt;/code&gt;) moves the top of the data segment, growing one contiguous heap region upward. &lt;strong&gt;mmap&lt;/strong&gt;[^7] maps pages anywhere: files into memory, anonymous zeroed pages for fresh heap, shared regions between processes. Modern allocators use both: small allocations extend the &lt;code&gt;brk&lt;/code&gt; heap, large ones get dedicated &lt;code&gt;mmap&lt;/code&gt; regions (easy to return whole).&lt;/p&gt;
&lt;p&gt;And &lt;strong&gt;&lt;code&gt;malloc&lt;/code&gt;&lt;/strong&gt;[^8] itself is not a syscall at all. It is library code managing those kernel pages: carving them into blocks, tracking free lists, splitting and coalescing, and calling &lt;code&gt;brk&lt;/code&gt; or &lt;code&gt;mmap&lt;/code&gt; only when its pools run dry. &lt;code&gt;free&lt;/code&gt; usually returns blocks to the pool without telling the kernel, which is why process memory rarely shrinks after a spike. The CPU post&amp;#39;s one-liner is now a full picture: conventions in userspace, pages from the kernel, translation in hardware.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Virtual addresses name a private universe; paging maps it page by page with hardware-checked permissions; the TLB caches translations so the common case stays fast; faults fetch the missing pieces from swap or files on demand; and &lt;code&gt;mmap&lt;/code&gt; plus &lt;code&gt;brk&lt;/code&gt; supply the raw pages that &lt;code&gt;malloc&lt;/code&gt; subdivides. Isolation, oversubscription, and on-demand loading all fall out of one table per process.&lt;/p&gt;
&lt;p&gt;But RAM forgets and swap is scratch: neither keeps anything worth keeping. Programs that must survive power loss need names, folders, permissions, and crash-safe updates on persistent storage. That durable world, blocks into files, is the next post&amp;#39;s subject.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Virtual_memory&quot;&gt;Virtual memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Paging&quot;&gt;Paging - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Page_table&quot;&gt;Page table - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Translation_lookaside_buffer&quot;&gt;Translation lookaside buffer - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Page_fault&quot;&gt;Page fault - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Paging&quot;&gt;Paging - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Mmap&quot;&gt;mmap - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/C_dynamic_memory_allocation&quot;&gt;C dynamic memory allocation - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Memory_management_unit&quot;&gt;Memory management unit - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Thrashing_(computer_science)&quot;&gt;Thrashing (computer science) - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>os</category><category>memory</category><category>virtual-memory</category><category>how-it-works</category></item><item><title>Scheduling Explained</title><link>https://www.wesleybertipaglia.com/blog/fwq0k4n2/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/fwq0k4n2/</guid><description>A deep dive into scheduling: how the OS shares CPUs among processes and threads, context switches, classic policies from FIFO to fair-share, priorities, and why responsiveness is a scheduling problem.</description><pubDate>Sun, 13 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/ppmfmhcv&quot;&gt;threads&lt;/a&gt; post left one question open: with more threads than cores, who runs next, on which core, and for how long? The answer is the &lt;strong&gt;scheduler&lt;/strong&gt;[^1]: the kernel subsystem that multiplexes CPUs among runnable work. This post opens it up. We will look at what a context switch really costs, the goals every policy balances, the classic policies from simplest to fairest, how priorities and multicore complicate the picture, and why your editor feels instant on a loaded machine.&lt;/p&gt;
&lt;h2&gt;The context switch: the price of sharing&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;context switch&lt;/strong&gt;[^2] swaps the running thread: save registers and program counter into the old PCB, switch page tables if the address space changes, load the new thread&amp;#39;s state, resume. The direct cost is small (hundreds of nanoseconds to microseconds), but the hidden cost dominates: the new thread finds cold caches and a cold TLB, so its first instructions stall while the memory hierarchy refills. Switching address spaces (process switch) pays the full price; switching threads in one process pays less, since the page table and TLB entries survive.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;thread A runs … timer fires … kernel saves A, picks B, restores B … B runs
              ↑                                              ↑
        registers → PCB(A)                        PCB(B) → registers
        (plus cache/TLB pollution nobody bills for)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two conclusions follow. Switches must be frequent enough for responsiveness but rare enough that useful work dominates overhead: the classic &lt;strong&gt;time slice&lt;/strong&gt; (quantum) of a few milliseconds balances the two. And voluntary switches are free wins: when a thread blocks on I/O, the kernel schedules someone else instead of idling, which is why I/O-heavy machines stay busy.&lt;/p&gt;
&lt;h2&gt;Goals: what &amp;quot;good&amp;quot; scheduling means&lt;/h2&gt;
&lt;p&gt;Every policy optimizes some mix of competing goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Throughput&lt;/strong&gt;: jobs completed per second. Batch systems live here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Turnaround time&lt;/strong&gt;: submission to completion. Short jobs hate waiting behind long ones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Response time&lt;/strong&gt;: time to first reaction. Interactive systems live here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fairness&lt;/strong&gt;: every runnable thread gets a reasonable share; nobody starves.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Predictability&lt;/strong&gt;: similar jobs behave similarly; deadlines get met.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No policy maximizes all five. Throughput favors long uninterrupted runs; response time favors frequent switching; fairness costs overhead. The rest of the post is how classic policies pick their tradeoff.&lt;/p&gt;
&lt;h2&gt;Classic policies: from naive to fair&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;First-in, first-out (FIFO)&lt;/strong&gt; runs each job to completion in arrival order. Simple, no starvation, disastrous for response time: one long compile ahead of your keystroke stalls everything (the &lt;strong&gt;convoy effect&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shortest job first (SJF)&lt;/strong&gt; runs the quickest job next, minimizing average turnaround. Optimal on paper, unusable directly: the kernel cannot know job lengths in advance (it can only estimate from recent behavior, which exponential averaging approximates).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Round-robin&lt;/strong&gt; gives each runnable thread a fixed quantum in turn, preempting at the timer. Response time becomes predictable (n threads, quantum q: worst wait ≈ n × q), at the cost of a switch per slice and no regard for importance or job length. Timesharing was built on this.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;round-robin, quantum = 10 ms, threads A B C ready
A(10) → B(10) → C(10) → A(10) → …
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Priority scheduling&lt;/strong&gt; assigns each thread a level and always runs the highest ready one. Real-time and interactive work preempts background work, which is the point, but low-priority threads can &lt;strong&gt;starve&lt;/strong&gt; indefinitely under load. The standard cure is &lt;strong&gt;aging&lt;/strong&gt;: raise a waiter&amp;#39;s priority the longer it waits, guaranteeing eventual service.&lt;/p&gt;
&lt;p&gt;Modern general-purpose schedulers (Linux CFS[^3], for example) aim at &lt;strong&gt;fairness&lt;/strong&gt;: track each thread&amp;#39;s virtual runtime and always run the one owed the most CPU, weighted by niceness. The effect approximates an ideal fluid sharing where everyone advances together, with priorities expressed as weights rather than strict ranks. Interactive feel comes free: a thread that slept on I/O accrued little runtime, so it jumps ahead when it wakes.&lt;/p&gt;
&lt;h2&gt;Priorities, preemption, multicore&lt;/h2&gt;
&lt;p&gt;Three complications complete the picture.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Priorities and inversion.&lt;/strong&gt; Strict priorities create &lt;strong&gt;priority inversion&lt;/strong&gt;: a high-priority thread blocks on a lock held by a low-priority thread, which cannot run because a medium-priority thread hogs the CPU. The chain defeats the ranking. The classic fix is &lt;strong&gt;priority inheritance&lt;/strong&gt;: the lock holder temporarily borrows the waiter&amp;#39;s priority until it releases, unblocking the chain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preemptive vs cooperative.&lt;/strong&gt; Preemptive kernels (the norm) can switch on timer, I/O, or wakeup at nearly any point, which keeps response time tight at the cost of locking everywhere inside the kernel. Cooperative designs switch only at explicit yield points: simpler, but one greedy thread stalls the machine, the same lesson the OS overview drew for user programs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multicore.&lt;/strong&gt; With many cores the scheduler also places work: keep threads on their warm core (&lt;strong&gt;affinity&lt;/strong&gt;) to preserve caches, balance load across cores, and pack or spread depending on power goals. Migration is a tradeoff between hot caches and idle silicon, re-decided constantly.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Sharing CPUs means switching contexts (cheap directly, expensive in caches), choosing a policy among throughput, turnaround, response, fairness, and predictability, climbing from FIFO through round-robin and priorities to fair-share, and handling inversion, preemption, and placement on real hardware. Responsiveness is not magic: short quanta, I/O threads owed runtime, and preemption that never asks permission.&lt;/p&gt;
&lt;p&gt;But scheduling decides who runs; memory decides what each runner sees. Every process believes it owns gigabytes of private, contiguous memory on a machine with one RAM and many tenants. Maintaining that fiction, page by page, is the next post&amp;#39;s subject.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Scheduling_(computing)&quot;&gt;Scheduling (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Context_switch&quot;&gt;Context switch - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Completely_Fair_Scheduler&quot;&gt;Completely Fair Scheduler - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Round-robin_scheduling&quot;&gt;Round-robin scheduling - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Priority_inversion&quot;&gt;Priority inversion - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Starvation_(computer_science)&quot;&gt;Starvation (computer science) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Shortest_job_next&quot;&gt;Shortest job next - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Convoy_effect&quot;&gt;Convoy effect - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Processor_affinity&quot;&gt;Processor affinity - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Preemption_(computing)&quot;&gt;Preemption (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>os</category><category>scheduling</category><category>internals</category><category>how-it-works</category></item><item><title>Threads &amp; Concurrency Explained</title><link>https://www.wesleybertipaglia.com/blog/ppmfmhcv/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/ppmfmhcv/</guid><description>A deep dive into threads and concurrency: threads versus processes, shared memory and race conditions, mutexes and semaphores, deadlock and its four conditions, and why shared state is the root of all evil.</description><pubDate>Sat, 12 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/s8qmq1mn&quot;&gt;processes&lt;/a&gt; post ended with a complaint: a process is a heavy tenant, with its own address space and page table, expensive to create and switch. Programs that need many parallel activities reach for something lighter: the &lt;strong&gt;thread&lt;/strong&gt;[^1]. This post opens that lighter thing up. We will look at what a thread shares and what it keeps private, why shared memory breeds race conditions, the locks that restore order, how locks can freeze each other in deadlock, and the discipline that keeps concurrent programs alive.&lt;/p&gt;
&lt;h2&gt;Threads vs processes: tenants vs roommates&lt;/h2&gt;
&lt;p&gt;A process isolates; a thread shares. Threads in the same process run in the &lt;strong&gt;same address space&lt;/strong&gt;: same code, same heap, same open files. What each thread keeps private is only what it needs to pause and resume independently: a program counter, a stack, and registers. Everything else is communal.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;process (isolated)              threads (sharing)
──────────────────              ──────────────────
own address space               shared address space,
own page table                  shared heap and files
own files                       ─────────────
                                per thread: stack,
                                registers, program counter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The payoff is speed and easy communication. Creating a thread skips the page table and address-space setup; switching threads can skip the memory-unit reconfiguration; and threads exchange data by writing to shared memory instead of piping bytes through the kernel. The price is exactly that sharing: no isolation means every thread can corrupt every other thread&amp;#39;s data, accidentally, at full speed. Processes protect you from each other by default; threads offer no protection at all.&lt;/p&gt;
&lt;h2&gt;Races: when sharing goes wrong&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;race condition&lt;/strong&gt;[^2] happens when the result depends on the timing of uncontrollable events: two threads touching the same data with at least one writing, and no ordering enforced between them. Consider the simplest shared counter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// shared: int balance = 100;
void deposit(int amount) {
    int t = balance;      // read
    t = t + amount;       // modify
    balance = t;          // write
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run one deposit alone and 100 becomes 150. Run two deposits of 50 concurrently and the answer can be 150 instead of 200: both threads read 100, both add 50, both write 150. The three steps each run correctly; the interleaving is wrong. This read-modify-write pattern is the classic race, and it hides everywhere counters, lists, and caches are shared.&lt;/p&gt;
&lt;p&gt;Worse, races are intermittent. The same program passes a thousand tests, then fails in production under load, because the failing interleaving needs a timer interrupt at exactly the wrong instruction. Programs that &amp;quot;usually work&amp;quot; are the signature of a race, and testing alone cannot catch them: the space of interleavings is far larger than any test suite.&lt;/p&gt;
&lt;h2&gt;Mutual exclusion: taking turns&lt;/h2&gt;
&lt;p&gt;The fix for a race is &lt;strong&gt;mutual exclusion&lt;/strong&gt;[^3]: guarantee that only one thread at a time executes the critical section touching shared data. The standard tool is the &lt;strong&gt;mutex&lt;/strong&gt;[^4] (mutual-exclusion lock): lock before entering, unlock after leaving. The OS (or threading library) parks waiters instead of spinning them, so a blocked thread costs no CPU while it waits.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
int balance = 100;

void deposit(int amount) {
    pthread_mutex_lock(&amp;amp;m);    // wait your turn
    balance = balance + amount;
    pthread_mutex_unlock(&amp;amp;m);  // next, please
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;strong&gt;semaphore&lt;/strong&gt;[^5] generalizes the idea to a counted resource: it holds N permits, waiters take one, leavers return one. A mutex is the N = 1 case (binary semaphore, plus ownership rules). Semaphores shine for producer-consumer queues and connection pools: N slots, N permits, no counting by hand.&lt;/p&gt;
&lt;p&gt;Two rules keep locks honest. Hold them briefly (a lock serializes everything waiting on it, so long sections destroy the parallelism threads were bought for), and always pair lock with unlock on every path, including error returns, or the next thread waits forever.&lt;/p&gt;
&lt;h2&gt;Deadlock: everyone waits for everyone&lt;/h2&gt;
&lt;p&gt;Locks compose badly. A &lt;strong&gt;deadlock&lt;/strong&gt;[^6] is a circle of waiting: thread A holds lock 1 and wants lock 2, thread B holds lock 2 and wants lock 1, and neither ever yields. Both sleep forever, holding what the other needs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;thread A                  thread B
────────                  ────────
lock(1) ✓                 lock(2) ✓
          ... time passes ...
want lock(2) ✗            want lock(1) ✗
  (held by B)               (held by A)
     ↓                         ↓
  waits forever             waits forever
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four &lt;strong&gt;conditions&lt;/strong&gt; must all hold for deadlock (the Coffman conditions): mutual exclusion (resources need exclusive access), hold-and-wait (threads hold resources while requesting more), no preemption (nobody can forcibly take a held lock away), and circular wait (the chain of &amp;quot;waits for&amp;quot; loops back). Break any one and deadlock becomes impossible. The practical fix targets the last: impose a &lt;strong&gt;global lock order&lt;/strong&gt; (always take lock 1 before lock 2, everywhere) so circles cannot form. Alternatives include lock timeouts with backoff, or designs that need only one lock at a time.&lt;/p&gt;
&lt;h3&gt;Preventing, detecting, recovering&lt;/h3&gt;
&lt;p&gt;Three strategies answer deadlock, and they differ in when they do the work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Prevention&lt;/strong&gt; rules it out by design, so at least one Coffman condition can never hold (the global lock order is exactly this). The cost is conservatism: some programs genuinely need one lock held while acquiring another, and the rule forbidding that also forbids the program.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoidance&lt;/strong&gt; grants a request only when doing so keeps the system in a &lt;strong&gt;safe state&lt;/strong&gt;, one where every thread can still finish[^7]. That verdict needs each thread to declare its maximum demand up front (the &lt;strong&gt;Banker&amp;#39;s algorithm&lt;/strong&gt;), which few real programs can supply.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detection&lt;/strong&gt; bets that deadlock will happen and looks for it: the kernel keeps a &lt;strong&gt;wait-for graph&lt;/strong&gt;, an edge from each waiter to whoever holds what it wants, and a cycle means deadlock. &lt;strong&gt;Recovery&lt;/strong&gt; then breaks it by killing a victim thread, preempting a resource it holds, or rolling it back to a checkpoint.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most systems take the last road and react late: the deadlock surfaces as a frozen application, a supervisor kills and restarts the culprit, and the machine carries on.&lt;/p&gt;
&lt;p&gt;Deadlock&amp;#39;s cousins deserve a mention. &lt;strong&gt;Livelock&lt;/strong&gt; is motion without progress (two threads keep yielding to each other, forever polite, never done). &lt;strong&gt;Starvation&lt;/strong&gt; is indefinite postponement (a low-priority thread never wins the lock against greedy rivals). Fairness, priorities, and bounded waiting are the scheduling post&amp;#39;s territory; the mechanism is noted here so the symptom is recognized.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;Threads trade isolation for speed: shared address space, private stacks, cheap creation, instant communication, and zero protection. Sharing without discipline produces races, intermittent and untestable; mutexes and semaphores restore order by serializing critical sections; and locks in combination demand a global order, or circles of waiting freeze the program solid, met at last by prevention, detection, or a kill.&lt;/p&gt;
&lt;p&gt;But who decides which thread runs next, on which core, for how long, and at what priority? Threads made concurrency possible; the scheduler decides what it costs. That is the next post&amp;#39;s subject.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Thread_(computing)&quot;&gt;Thread (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Race_condition&quot;&gt;Race condition - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Mutual_exclusion&quot;&gt;Mutual exclusion - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Lock_(computer_science)&quot;&gt;Lock (computer science) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Semaphore_(programming)&quot;&gt;Semaphore (programming) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Deadlock&quot;&gt;Deadlock - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Banker%27s_algorithm&quot;&gt;Banker&amp;#39;s algorithm - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/POSIX_Threads&quot;&gt;Pthreads - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^12]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Livelock&quot;&gt;Livelock - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^13]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Starvation_(computer_science)&quot;&gt;Starvation (computer science) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^14]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Critical_section&quot;&gt;Critical section - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>os</category><category>threads</category><category>concurrency</category><category>how-it-works</category></item><item><title>Processes Explained</title><link>https://www.wesleybertipaglia.com/blog/s8qmq1mn/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/s8qmq1mn/</guid><description>A deep dive into processes: programs versus processes, the process control block, process states, fork and exec, isolation through virtual memory, inter-process communication, and how the OS keeps a hundred programs believing they are alone.</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/zkb3xkdy&quot;&gt;OS overview&lt;/a&gt; promised an illusion: dozens of programs sharing one machine, each believing it owns the hardware. The mechanism behind that illusion is the &lt;strong&gt;process&lt;/strong&gt;[^1]. This post opens it up. We will look at what separates a program from a process, the record the kernel keeps for each one, the states a process moves through, how processes are born and reaped, the isolation that keeps one crash from taking down the machine, and how processes walled off from each other still find a way to talk.&lt;/p&gt;
&lt;h2&gt;Programs vs processes: recipe vs cooking&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;program&lt;/strong&gt; is inert: bytes on disk, a recipe sitting in a drawer. A process is that program in motion: code loaded into memory, with an address space, open files, and a position in its execution. The same program can run as many processes at once (open two terminals, run the same shell twice); each gets its own memory and its own state, and neither knows about the other.&lt;/p&gt;
&lt;p&gt;What turns the recipe into cooking is state. A process is its code plus everything needed to pause it and resume it later: register values, the program counter, the stack pointer, the page table describing its memory, the list of open files, and accounting (who owns it, how much CPU it has used). Stop saving that state exactly and the illusion of many machines collapses into one.&lt;/p&gt;
&lt;h2&gt;The PCB: the kernel&amp;#39;s index card&lt;/h2&gt;
&lt;p&gt;For every process the kernel keeps a &lt;strong&gt;process control block&lt;/strong&gt;[^2] (PCB, often called the process descriptor or task struct): one record holding everything the OS needs to manage that process. The exact fields vary by system, but the shape is universal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;PCB for pid 4217 (&amp;quot;editor&amp;quot;)
─────────────────────────────
pid, parent pid, owner (uid)
state: ready / running / blocked
CPU context: rip, rsp, general registers
memory: pointer to page table
files: table of open file descriptors
accounting: CPU time used, priority
signals and exit status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;process identifier&lt;/strong&gt; (pid)[^3] names the process; &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, and small numbers traditionally belong to the kernel and the first user process. The CPU context is the pause button: when the timer interrupt fires, the kernel copies the registers into the PCB; when the process runs again, it restores them. Switching that context is literally what a &lt;strong&gt;context switch&lt;/strong&gt; is: save one PCB&amp;#39;s registers, load another&amp;#39;s, switch page tables, resume.&lt;/p&gt;
&lt;h2&gt;States: running, ready, blocked&lt;/h2&gt;
&lt;p&gt;A process is always in one &lt;strong&gt;state&lt;/strong&gt;[^4], and the kernel moves it between states as events happen:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;          created
             │
             ▼
         ┌───────┐  dispatch   ┌─────────┐
         │ ready │ ──────────▶ │ running │ ───▶ terminated
         └───────┘             └─────────┘         ▲
             ▲                   │ wait for I/O    │ exit
             │ I/O completes     ▼                 │
             │              ┌─────────┐            │
             └──────────────│ blocked │ ───────────┘
                            └─────────┘   (via zombie,
                                          parent reaps)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three states carry the whole story. &lt;strong&gt;Running&lt;/strong&gt; means on a CPU right now (at most one process per core). &lt;strong&gt;Ready&lt;/strong&gt; means runnable but waiting for a CPU. &lt;strong&gt;Blocked&lt;/strong&gt; (waiting) means stuck on something else: disk read, network packet, keyboard input. A blocked process cannot use a CPU even if one is free, so the kernel parks it and runs someone ready instead. That single decision, never idle a CPU while work is ready, is why your machine stays busy during a slow download.&lt;/p&gt;
&lt;p&gt;Two more states round out the picture. &lt;strong&gt;Created&lt;/strong&gt; (new) covers the brief setup before a process is ready. &lt;strong&gt;Terminated&lt;/strong&gt; covers death: on Unix a dead process lingers as a &lt;strong&gt;zombie&lt;/strong&gt; until its parent collects the exit status with &lt;code&gt;wait&lt;/code&gt;, a small bookkeeping debt the next section explains.&lt;/p&gt;
&lt;h2&gt;Born and reaped: fork, exec, exit, wait&lt;/h2&gt;
&lt;p&gt;On Unix, processes are born by cloning. The &lt;strong&gt;fork&lt;/strong&gt;[^5] system call duplicates the calling process: parent and child emerge running the same code, differing only in the return value (0 for the child, the child&amp;#39;s pid for the parent). The child then typically calls &lt;strong&gt;exec&lt;/strong&gt;[^6] to replace itself with a new program. Shells do exactly this dance for every command you type: fork, exec the command in the child, &lt;code&gt;wait&lt;/code&gt; for it in the parent.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;pid_t pid = fork();
if (pid == 0) {
    // child: become the new program
    execl(&amp;quot;/bin/ls&amp;quot;, &amp;quot;ls&amp;quot;, NULL);
    _exit(1); // exec only returns on failure
} else {
    // parent: wait for the child to finish
    int status;
    waitpid(pid, &amp;amp;status, 0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Death is symmetric. &lt;strong&gt;exit&lt;/strong&gt; tears the process down (closing files, freeing user memory, keeping the exit status), and the parent&amp;#39;s &lt;strong&gt;wait&lt;/strong&gt; collects that status and finally frees the PCB. A parent that never waits accumulates zombies; a child whose parent died first gets adopted by the first process (pid 1), which reaps dutifully. None of this is hardware. It is kernel bookkeeping, conventions enforced in C.&lt;/p&gt;
&lt;h2&gt;Isolation: why one crash kills one program&lt;/h2&gt;
&lt;p&gt;Sharing a machine is only half the job; the other half is keeping tenants apart. Isolation rests on two mechanisms from earlier posts.&lt;/p&gt;
&lt;p&gt;First, &lt;strong&gt;virtual memory&lt;/strong&gt;: each process gets its own page table, hence its own private address space. Process A cannot name process B&amp;#39;s memory because its addresses translate elsewhere (or to nothing, producing a segmentation fault instead of corruption). The &lt;a href=&quot;/blog/5atxjdiq&quot;&gt;memory&lt;/a&gt; post described the hardware; the process is its customer.&lt;/p&gt;
&lt;p&gt;Second, &lt;strong&gt;privilege levels&lt;/strong&gt;: user code cannot touch devices, reconfigure memory, or meddle with other PCBs. Anything dangerous requires a syscall, where the kernel validates first. A wild pointer in an editor kills the editor; the kernel, running isolated in its own mappings, survives to clean up.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;process A              kernel              process B
private mappings  →  own mappings,   ←  private mappings
page table A         full hardware        page table B
                     access, guards
                     every crossing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Files and permissions complete the fence: every process runs as a user, every file carries an owner and mode bits, and the kernel checks on every open. Isolation is not one trick. It is address spaces plus privilege plus permissions, layered so that each covers the others&amp;#39; gaps.&lt;/p&gt;
&lt;h2&gt;Talking across the fence: inter-process communication&lt;/h2&gt;
&lt;p&gt;But a fence keeps things out in both directions. A process cannot read another&amp;#39;s memory, cannot poke its variables, cannot even learn where that memory lives. The same wall that keeps one crash from taking down the machine keeps two cooperating programs from touching. Something has to carry data between them, and the only party that can see both sides is the kernel. That broker is &lt;strong&gt;inter-process communication&lt;/strong&gt;[^7] (IPC), and the wall is what turns it into a category of its own.&lt;/p&gt;
&lt;p&gt;The cheapest channel is the &lt;strong&gt;pipe&lt;/strong&gt;[^8]: a kernel buffer with two ends, one file descriptor to write into, another to read from. Fork hands both ends to parent and child, which is why a pipeline is three lines of plumbing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int fd[2];
pipe(fd);                  // fd[0] = read end, fd[1] = write end
if (fork() == 0) {
    close(fd[1]);         // child keeps the read end
    execl(&amp;quot;/usr/bin/wc&amp;quot;, &amp;quot;wc&amp;quot;, &amp;quot;-l&amp;quot;, NULL);
} else {
    close(fd[0]);         // parent keeps the write end
    execl(&amp;quot;/usr/bin/ls&amp;quot;, &amp;quot;ls&amp;quot;, NULL);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Neither program knows the other exists, and the two halves need not even run at the same time: the buffer lives in kernel memory and absorbs the speed mismatch. The waiting is the &lt;strong&gt;blocked&lt;/strong&gt; state from earlier. Writing to a full pipe parks the writer, reading an empty one parks the reader, and whichever side still has work is the side the scheduler picks. What moves is a byte stream with no message boundaries and only one direction; want an answer back, build a second pipe pointing the other way. Two pipes are how a client talks to a server without either one holding an object the other can reach.&lt;/p&gt;
&lt;p&gt;When two processes share no parent and must find each other by name, the same idea changes shape. A &lt;strong&gt;named pipe&lt;/strong&gt; (FIFO)[^9] is a pipe with an entry in the filesystem, so unrelated programs agree on a path and open it. A &lt;strong&gt;message queue&lt;/strong&gt;[^10] carries records instead of bytes: each write is a discrete message with its own length, delivered in order, so a receiver can take one message without parsing the stream around it. A &lt;strong&gt;socket&lt;/strong&gt; goes furthest, becoming a bidirectional channel that works between two processes on the machine and, with a network address instead of a name, between two machines. A Unix domain socket[^11] is a TCP conversation with the network removed, and the same code serves both.&lt;/p&gt;
&lt;p&gt;Shared memory[^12] is the exception that proves the rule. Rather than moving bytes through a kernel buffer, two processes map the same physical pages into their separate address spaces: the &lt;a href=&quot;/blog/j5n50ly4&quot;&gt;virtual memory&lt;/a&gt; post&amp;#39;s machinery, aimed at a shared target. No copies, no syscall per byte, and for bulk payloads dramatically faster than a pipe. But the kernel steps out of the loop entirely: it never sees the individual loads and stores, so it cannot stop one process from scribbling over the other&amp;#39;s data. Ordering has to come back explicitly, through semaphores, mutexes, or atomic instructions: the same bill the &lt;a href=&quot;/blog/ppmfmhcv&quot;&gt;threads&lt;/a&gt; post charges for sharing memory inside a single process. Shared memory buys its speed with precisely the safety isolation was selling, which is the trade every IPC mechanism makes.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;A program is bytes; a process is a program plus the state to pause and resume it. The kernel tracks each one in a PCB, moves it between ready, running, and blocked, creates it with fork and exec, reaps it with exit and wait, and isolates it with private address spaces and privilege checks. Cut off from each other&amp;#39;s memory, those processes still trade data through the kernel: pipes, message queues, sockets, or pages mapped in common, each channel paying in copies, in ordering, or in safety. One executor, a hundred tenants, each convinced the machine is theirs.&lt;/p&gt;
&lt;p&gt;But a process is a heavy tenant: its own address space, its own page table, expensive to create and switch. Programs that need many parallel activities inside one tenant reach for something lighter, and that lighter thing shares memory instead of isolating it, with all the danger that implies. That is the next post&amp;#39;s subject.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Process_(computing)&quot;&gt;Process (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Process_control_block&quot;&gt;Process control block - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Process_identifier&quot;&gt;Process identifier - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Process_state&quot;&gt;Process state - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Fork_(system_call)&quot;&gt;Fork (system call) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Exec_(system_call)&quot;&gt;Exec (system call) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Inter-process_communication&quot;&gt;Inter-process communication - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Pipe_(Unix)&quot;&gt;Pipe (Unix) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Named_pipe&quot;&gt;Named pipe - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Message_queue&quot;&gt;Message queue - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Unix_domain_socket&quot;&gt;Unix domain socket - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^12]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Shared_memory&quot;&gt;Shared memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^13]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Zombie_process&quot;&gt;Zombie process - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^14]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Context_switch&quot;&gt;Context switch - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^15]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Virtual_memory&quot;&gt;Virtual memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^16]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Memory_protection&quot;&gt;Memory protection - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>os</category><category>processes</category><category>ipc</category><category>internals</category><category>how-it-works</category></item><item><title>The OS Explained</title><link>https://www.wesleybertipaglia.com/blog/zkb3xkdy/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/zkb3xkdy/</guid><description>A broad tour of what an operating system does: kernel versus userspace, system calls as the boundary, processes, memory, files, and how the OS stays in control of one machine running many programs.</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Everything you do on a computer runs through software you never see. You open a file or press play, and something quietly decides which program gets the processor, how much memory it can use, and whether it&amp;#39;s allowed to touch the disk. That invisible layer is the &lt;strong&gt;operating system&lt;/strong&gt;[^1], the manager of the machine&amp;#39;s physical resources. It is the software sitting on top of everything covered in the &lt;a href=&quot;/blog/2lfin2nu&quot;&gt;Computers Explained&lt;/a&gt; series.&lt;/p&gt;
&lt;p&gt;This post is the panoramic view of that job: what the OS is for, why the machine has two levels of trust and two separate halves, how programs ask for help, the four core resources it manages, and how it stays in complete control of a machine it is barely running.&lt;/p&gt;
&lt;h2&gt;What the OS is for&lt;/h2&gt;
&lt;p&gt;An operating system does two things, and every feature it offers serves one of them.&lt;/p&gt;
&lt;p&gt;The first is &lt;strong&gt;making the hardware usable&lt;/strong&gt;. The disk knows nothing about files or folders; the processor knows nothing about documents or windows. A program should not have to know which disk sectors hold its data or how to make the controller read them. The OS takes care of that part: the program asks to open a file, read some bytes, or write them back, and the OS works out the machine steps. You ask for the result instead of the machinery.&lt;/p&gt;
&lt;p&gt;The second is &lt;strong&gt;sharing the machine&lt;/strong&gt;. There is one processor, one block of memory, one disk, but many programs that all want them at once. The OS decides who runs next, who gets how much memory, and what happens when two programs want the same thing. Sharing fairly between competing programs is the harder of the two jobs, and it is what most of this series is about.&lt;/p&gt;
&lt;p&gt;Both jobs have standard names: the OS as an &lt;strong&gt;extended machine&lt;/strong&gt;, the hardware hidden behind cleaner interfaces, and as a &lt;strong&gt;resource manager&lt;/strong&gt;, the one doing the sharing. Same thing seen from two sides.&lt;/p&gt;
&lt;h2&gt;Kernel mode and user mode: the two levels of trust&lt;/h2&gt;
&lt;p&gt;Not all programs can be trusted equally, so the processor itself draws a line. Every modern processor runs in one of two modes[^2]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kernel mode&lt;/strong&gt;, also called supervisor mode: nothing is forbidden. The program can touch any memory, change any setting, and command any device.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User mode&lt;/strong&gt;: restricted. The processor refuses instructions that would affect the machine as a whole, and refuses memory that does not belong to the program asking.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That rule lives in the hardware, not in the OS. No program, however buggy or malicious, can talk its way past it. A user program that reaches for something forbidden does not fail politely: the processor halts it on the spot, and whatever it was doing stops with it.&lt;/p&gt;
&lt;h2&gt;Kernel and user space: the two halves of the machine&lt;/h2&gt;
&lt;p&gt;Someone has to run in kernel mode, or nothing would get decided. That someone is the &lt;strong&gt;kernel&lt;/strong&gt;[^3], the part of the OS that sits there. Everything else, your browser, your editor, your games, runs in &lt;strong&gt;user space&lt;/strong&gt;[^4].&lt;/p&gt;
&lt;p&gt;The split exists so that mistakes stay small. A music player with a bug should stop the music, not the whole machine. Because a user-mode program is stopped by the hardware whenever it reaches for something that is not its own, a crash stays inside that program.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;user mode                      kernel mode
─────────                      ───────────
browser, editor, games    →    kernel: processes, memory,
shell, compiler                  files, devices, network

   │ restricted: cannot touch  │ privileged: full access to
   │ the hardware directly      │ the hardware, enforces the line
   └───────── asks through ─────┘
              syscalls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anything a user program cannot do on its own, it asks the kernel to do. That request is the only channel between the two halves.&lt;/p&gt;
&lt;p&gt;There are different ways to place that channel inside the OS. A &lt;strong&gt;monolithic kernel&lt;/strong&gt;[^11], like Linux, keeps most services in kernel mode, where they are fast. A &lt;strong&gt;microkernel&lt;/strong&gt;[^10] keeps the kernel small and moves drivers and filesystems into ordinary user-mode programs, where a failure is easier to contain. The tradeoff is the familiar one: a single large piece is fast, but one bug takes down everything, while many small pieces isolate failures better and cost more work crossing between them.&lt;/p&gt;
&lt;h2&gt;Syscalls: the boundary you program against&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;system call&lt;/strong&gt;[^5] is the only bridge between user space and the kernel. An ordinary program has no permission to touch the hardware directly, neither the disk nor the screen, so it has to make a formal request to the kernel and wait.&lt;/p&gt;
&lt;p&gt;In practice, this synchronous dialogue happens in three steps.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The request:&lt;/strong&gt; The program needs to save a file or display some text, so it makes a system call, pauses its own execution, and hands control to the kernel, which takes over in kernel mode.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The check:&lt;/strong&gt; The kernel takes the command, verifies the program is allowed to perform that action, and does the work with full privileges.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The return:&lt;/strong&gt; The kernel sends the answer back, and the program wakes up exactly where it stopped, with the result in hand.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of that back and forth happens millions of times per second, keeping the machine safe without you ever noticing a hitch.&lt;/p&gt;
&lt;h2&gt;What the OS manages&lt;/h2&gt;
&lt;p&gt;Four kinds of resource take up nearly all of an OS&amp;#39;s attention.&lt;/p&gt;
&lt;h3&gt;Process&lt;/h3&gt;
&lt;p&gt;A &lt;strong&gt;process&lt;/strong&gt;[^6] is a running program along with the metadata the OS must maintain: its compiled code, memory allocations, open file descriptors, and its current execution pointer. The OS manages the lifecycle of each process, starting, pausing, resuming, and cleaning them up, in order to give each program exclusive virtual access to the hardware.&lt;/p&gt;
&lt;h3&gt;Memory&lt;/h3&gt;
&lt;p&gt;The OS implements &lt;strong&gt;virtual memory&lt;/strong&gt;[^7] to give each program a private, isolated address space, even though the underlying physical RAM is shared. It translates virtual addresses to physical locations on the fly to prevent programs from colliding or accessing each other&amp;#39;s data, and transparently pages data between RAM and secondary storage as needed.&lt;/p&gt;
&lt;h3&gt;Storage&lt;/h3&gt;
&lt;p&gt;The OS provides a &lt;strong&gt;file system&lt;/strong&gt;[^8] to bridge the gap between raw hardware blocks and human concepts. It abstracts unnamed, unorganized blocks of bytes into structured files and folders, manages access permissions, tracks metadata, and ensures data integrity during write operations and power failures.&lt;/p&gt;
&lt;h3&gt;Devices&lt;/h3&gt;
&lt;p&gt;The OS uses &lt;strong&gt;Device drivers&lt;/strong&gt;[^12] to standardize communication with heterogeneous hardware, such as keyboards, displays, disks, and network cards. It translates uniform system requests into device-specific commands and manages input/output queues to optimize throughput and minimize processor idle time.&lt;/p&gt;
&lt;h2&gt;How the OS stays in control while barely running&lt;/h2&gt;
&lt;p&gt;Here is the puzzle: most of the time the OS is not running. Your editor is. So if the OS is in charge, who actually is?&lt;/p&gt;
&lt;p&gt;The answer is that the machine keeps handing control back on its own. Three events can pull the OS in, whether the running program likes it or not:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The program asks for something, through a system call. This one is voluntary.&lt;/li&gt;
&lt;li&gt;A device needs attention, because a key was pressed, a disk finished reading, or a network packet arrived. The hardware raises an &lt;strong&gt;interrupt&lt;/strong&gt;[^9] by itself.&lt;/li&gt;
&lt;li&gt;A timer runs out, many times per second. Nothing on the machine can prevent it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The third is what holds everything together. When the timer fires, the OS takes the processor back, decides which program should run next, and hands it over for a short slice. That happens whether the program being interrupted agrees or not, so the OS does not need to be running to stay in charge. It only needs to be woken up often enough.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;program A runs … timer fires … OS decides … program B runs …
              ↑              ↑                 ↑
          interrupt      saves A, picks     A waits, B
            (forced)       who runs next     starts fresh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Early systems asked programs to be polite: yield the processor when you finish, and everyone else gets a turn. This is called &lt;strong&gt;cooperative&lt;/strong&gt;[^13] sharing, and it fails in an obvious way. One program that forgets to yield, or crashes while holding the processor, freezes every other program on the machine. Modern systems are &lt;strong&gt;preemptive&lt;/strong&gt;[^14]: the OS takes the processor back on its own schedule and gives it to someone else. Which program that is, for how long, and at what priority is the subject of scheduling, which gets its own post.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;An operating system is software that manages the machine&amp;#39;s hardware on behalf of every program on it. It keeps the hardware details to itself and answers simple requests instead. It splits the machine into a trusted half, where the kernel may do anything, and a restricted half, where everything else runs and can only ask. Behind that single door it manages processes, memory, files, and devices. And a timer that nobody can ignore brings it back into control over and over, no matter what any program would prefer.&lt;/p&gt;
&lt;p&gt;What is actually inside a running program, how one gets started, and what it takes to keep a hundred of them believing they are alone on the machine: that is the next post, where we pop the hood on a single process and read the OS&amp;#39;s bookkeeping for it line by line.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Operating_system&quot;&gt;Operating system - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Protection_ring&quot;&gt;Protection ring - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Kernel_(operating_system)&quot;&gt;Kernel (operating system) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/User_space_and_kernel_space&quot;&gt;User space and kernel space - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/System_call&quot;&gt;System call - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Process_(computing)&quot;&gt;Process (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Virtual_memory&quot;&gt;Virtual memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/File_system&quot;&gt;File system - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Interrupt&quot;&gt;Interrupt - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Microkernel&quot;&gt;Microkernel - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Monolithic_kernel&quot;&gt;Monolithic kernel - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^12]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Device_driver&quot;&gt;Device driver - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^13]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Cooperative_multitasking&quot;&gt;Cooperative multitasking - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^14]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Preemptive_multitasking&quot;&gt;Preemptive multitasking - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>os</category><category>operating-systems</category><category>internals</category><category>how-it-works</category></item><item><title>The Most Flexible Machine Ever Built</title><link>https://www.wesleybertipaglia.com/blog/eyhl1ygp/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/eyhl1ygp/</guid><description>The finale of the Computers Explained series: stepping back from transistors and buses to ask why computers matter: the stored-program idea, the handful of patterns behind everything, and why fundamentals outlive every fashion.</description><pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Across this series we took a computer apart: the &lt;a href=&quot;/blog/2lfin2nu&quot;&gt;computers&lt;/a&gt;, &lt;a href=&quot;/blog/ylhwiwpb&quot;&gt;representation&lt;/a&gt;, &lt;a href=&quot;/blog/cmvoxsqk&quot;&gt;CPU&lt;/a&gt;, &lt;a href=&quot;/blog/5zpz59bz&quot;&gt;digital logic&lt;/a&gt;, &lt;a href=&quot;/blog/5atxjdiq&quot;&gt;memory&lt;/a&gt;, &lt;a href=&quot;/blog/s6kfogsy&quot;&gt;storage&lt;/a&gt;, &lt;a href=&quot;/blog/x0g3dogf&quot;&gt;I/O&lt;/a&gt;, and &lt;a href=&quot;/blog/es12ffyu&quot;&gt;interconnects&lt;/a&gt;. No new layer today. This final post steps back to ask why this machine matters so much.&lt;/p&gt;
&lt;h2&gt;One machine, every job&lt;/h2&gt;
&lt;p&gt;Most machines do one thing. A toaster toasts. A calculator adds. A &lt;strong&gt;computer&lt;/strong&gt;[^1] is different because its behavior is not fixed in hardware. Thanks to the &lt;strong&gt;stored-program concept&lt;/strong&gt;[^2], instructions live in memory like data, and the processor fetches them as part of normal work. Load different bytes, get a different machine.&lt;/p&gt;
&lt;p&gt;Your watch, your phone, and a supercomputer differ in size, not in essence. All three fetch stored instructions and all three can run an enormous range of tasks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; the same laptop edits video in the morning, joins calls at noon, and plays chess at night. Nothing changed inside except the bytes loaded from SSD into RAM.&lt;/p&gt;
&lt;h2&gt;The same handful of ideas, everywhere&lt;/h2&gt;
&lt;p&gt;Look back and notice how rarely ideas change. Small and fast on top, big and slow below: that is memory, storage, and caches. Agree on the interface, change behind it: that is ISA, USB, and PCIe. Keep recent work close: that is locality and caching. Let helpers do bulk work: that is controllers and DMA. Fast, big, cheap: pick two, everywhere.&lt;/p&gt;
&lt;p&gt;Learning specs expires with the chip. Learning patterns transfers to every machine you meet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; once you understand why a cache helps RAM, you also understand why a browser cache helps the network and why a CDN helps video. Same locality idea, new clothes.&lt;/p&gt;
&lt;h2&gt;Why fundamentals outlive fashions&lt;/h2&gt;
&lt;p&gt;Tools churn yearly. The ground barely moves. Bits are still bits. Addition still ripples carries. Processors still fetch, decode, execute. Hierarchies still trade speed for size. The posts here describe ideas many programmers never touch directly yet use daily.&lt;/p&gt;
&lt;p&gt;Going deeper makes knowledge more stable, not less. Chasing each new tool keeps you running. Learning how the machine works lets the landscape settle around you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a framework from 2020 already feels old. Addresses, caches, and fetch decode execute from decades ago still describe your new phone.&lt;/p&gt;
&lt;h2&gt;The lever under everything&lt;/h2&gt;
&lt;p&gt;The flexible machine became a lever under science, medicine, and art. Genomes read by matching letters. Black hole images built from scattered radio samples. Grandparents meeting as pixels and sound. All of it ends as switches changing state in patterns we wrote, billions of times per second.&lt;/p&gt;
&lt;p&gt;Older machines extended muscle and reach. This one extends what we can calculate, model, and automate. Any process described precisely enough can run on it. That is why software keeps eating the world, one precise description at a time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a vaccine search compares millions of letter strings. A video call compresses light and sound into packets. Both are just loops, addresses, and arithmetic on the same humble parts.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;switches + wires + agreements on meaning
  ↓
gates -&amp;gt; adders, muxes, registers
  ↓
CPU + memory + storage + I/O + roads
  ↓
one universal machine, any job by loading bytes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The series started with a map and ends with a moral. Details will keep changing: denser chips, faster roads, new materials. Ideas will keep rhyming.&lt;/p&gt;
&lt;p&gt;We have seen the machine. Next is what software can make it do.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer&quot;&gt;Computer - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Stored-program_computer&quot;&gt;Stored-program computer - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><enclosure url="https://www.wesleybertipaglia.com//img/cover/he02mcu7.webp" type="image/webp"/><category>computer</category><category>computer-architecture</category><category>internals</category><category>how-it-works</category></item><item><title>Computer Buses &amp; Interconnects Explained</title><link>https://www.wesleybertipaglia.com/blog/es12ffyu/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/es12ffyu/</guid><description>A deep dive into how computer parts communicate: data, address, and control buses, CPU-memory and CPU-device paths, memory interconnects, PCIe, USB, and the bandwidth and latency of every road in the machine.</description><pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every past post assumed one thing: parts can reach each other. Registers feed the ALU, the CPU reads memory, controllers deliver device data, all across wires. This post explains those wires. Not a PCIe manual, just what each road is for and what it costs.&lt;/p&gt;
&lt;h2&gt;Three buses: what, where, and when&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;bus&lt;/strong&gt;[^1] is a set of wires between parts. The classic model splits it in three:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CPU -------- address bus (WHERE: which place?) ------&amp;gt; elsewhere
CPU &amp;lt;------&amp;gt; data bus (WHAT: the bytes) &amp;lt;------------&amp;gt; elsewhere
CPU -------- control lines (WHEN: read? write?) -----&amp;gt; elsewhere
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;address bus&lt;/strong&gt;[^2] names the target: a memory spot or a device register. Its width sets reach: 32 bits name 4 GiB. The data bus carries the payload both ways: 64 bits move 8 bytes per trip. Control lines coordinate: read or write, size, clock, plus ready signals meaning data is valid now.&lt;/p&gt;
&lt;p&gt;Where, what, when. That is the whole model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; &lt;code&gt;mov rax, [0x2000]&lt;/code&gt; puts &lt;code&gt;0x2000&lt;/code&gt; on the address bus, raises read on control, and grabs 8 bytes from the data bus.&lt;/p&gt;
&lt;h2&gt;CPU to memory: the fastest road&lt;/h2&gt;
&lt;p&gt;The CPU to memory path is the busiest road: every instruction and most data cross it, but caches save most trips. The talk is simple: put address out, signal read, collect bytes. Writes go in reverse.&lt;/p&gt;
&lt;p&gt;Old machines shared one bus. New machines use dedicated point-to-point &lt;strong&gt;memory interconnects&lt;/strong&gt;[^3]: memory channels plus links like Infinity Fabric or Intel UPI in big servers, each moving tens of GB/s. Caches exist to avoid this travel: hits stay on chip, only misses pay for DRAM.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; loop over a 1 MB array with a 12 MB cache and almost nothing leaves the chip. Jump across gigabytes at random and every miss waits tens of nanoseconds for DRAM.&lt;/p&gt;
&lt;h2&gt;CPU to devices: the I/O road&lt;/h2&gt;
&lt;p&gt;CPU to devices uses the same idea with slower timing. The CPU talks to controller registers with &lt;strong&gt;memory-mapped I/O&lt;/strong&gt;[^4], where device registers just live at special addresses next to RAM. It writes a command like start read, the controller does the slow work alone, then signals done with an interrupt or a DMA copy into memory.&lt;/p&gt;
&lt;p&gt;Speeds differ wildly. A keyboard needs almost nothing. A GPU or fast SSD needs gigabytes. Separate roads let each run at its own price.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; type a letter and one interrupt carries one byte. Start a disk read and the controller moves megabytes by DMA while the CPU does other work.&lt;/p&gt;
&lt;h2&gt;PCIe: the general highway&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;PCI Express&lt;/strong&gt;[^5] is the main I/O road inside modern boxes. It links processor to GPU, SSD, and network card with fast serial &lt;strong&gt;lanes&lt;/strong&gt;. Each lane sends both ways at once. A x16 graphics slot has sixteen lanes, roughly sixteen times one lane. An NVMe SSD often uses four.&lt;/p&gt;
&lt;p&gt;Two ideas matter without internals. Generations: each generation roughly doubles speed per lane, so ceiling depends on lane count times generation. Packets: unlike simple address plus data, PCIe wraps traffic in packets with headers and error checks, like tiny network messages inside the box. That lets one fabric serve GPU, storage, and network alike.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; plug an NVMe SSD into a 4-lane slot. Sequential reads hit several GB/s because four lanes in parallel feed it. Move it to a 1-lane slot of an old generation and the same drive crawls.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SATA is the older storage road, capped near 0.5 GB/s. Computers keep it because HDDs use it: they are large mechanical drives with SATA plugs and cables, not cards that fit PCIe slots, and they remain the cheapest terabytes for backups and archives. Many SATA SSDs reuse the same plugs and cables.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;USB: the road to the outside&lt;/h2&gt;
&lt;p&gt;If PCIe is the highway inside, &lt;strong&gt;Universal Serial Bus&lt;/strong&gt;[^6] is the road out. It joins keyboards, mice, disks, and phones, carrying data plus power so small devices just work. Devices join a host controlled bus: the host schedules everything, peripherals stay simple.&lt;/p&gt;
&lt;p&gt;History is generations keeping compatibility: megabits on USB 2.0, gigabits on USB 3.x, tens of gigabits on USB4. Plugs changed (Type-A, Micro, Type-C) but the flow stayed: plug in, identify, exchange packets. The port on the case is where an internal road changes medium.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; plug a keyboard into USB-C. It reports it is a keyboard, the host polls it on schedule, key presses arrive as small packets. Same port, plug a disk, and bulk packets carry files plus charging power.&lt;/p&gt;
&lt;h2&gt;Bandwidth and latency: the cost of every road&lt;/h2&gt;
&lt;p&gt;Every road has two numbers. &lt;strong&gt;Bandwidth&lt;/strong&gt; is flow per second: tens of GB/s per memory channel, GB/s on multi-lane PCIe, megabits to tens of gigabits on USB. Wider and faster raises it, sharing lowers each share.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Latency&lt;/strong&gt; is trip time: single nanoseconds on chip, tens of nanoseconds to DRAM, microseconds across PCIe to a device, more over USB cables. Each border crossed adds delay, so urgent traffic stays on the shortest roads.&lt;/p&gt;
&lt;p&gt;Bulk wants bandwidth. Start wants latency. DMA moves bulk without the CPU. Interrupts signal done without polling. Those two tricks from the I/O post are answers to these costs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; copy a 10 GB file and bandwidth rules. Click a mouse and latency rules: even tiny delay feels laggy.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;need: parts must reach each other
  ↓
model: address (where) + data (what) + control (when)
  ↓
CPU &amp;lt;-&amp;gt; memory (fastest, caches avoid trips)
CPU &amp;lt;-&amp;gt; devices (mapped addresses + interrupts + DMA)
  ↓
PCIe (inside highway, lanes and packets)
USB (outside road, host schedules, power plus data)
  ↓
price every road with bandwidth and latency
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every link answers where, what, and when. Old buses show it as wires, new links pack it into packets. The physical machine is now complete. The last post steps back to see the whole.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Bus_(computing)&quot;&gt;Bus (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Address_bus&quot;&gt;Address bus - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer_bus&quot;&gt;Computer bus - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Memory-mapped_I/O&quot;&gt;Memory-mapped I/O - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/PCI_Express&quot;&gt;PCI Express - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/USB&quot;&gt;USB - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><enclosure url="https://www.wesleybertipaglia.com//img/cover/l85gtyud.webp" type="image/webp"/><category>buses</category><category>interconnects</category><category>computer-architecture</category><category>internals</category><category>how-it-works</category></item><item><title>Input and Output Explained</title><link>https://www.wesleybertipaglia.com/blog/x0g3dogf/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/x0g3dogf/</guid><description>A deep dive into input and output: keyboards, mice, displays, audio, storage and network devices, controllers, buses, ports, interrupts, and DMA: how the computer talks to the world beyond memory.</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The last posts built a machine that can compute, remember, and persist, yet one that is deaf, mute, and blind. With no way in and no way out it would be useless. This post covers &lt;strong&gt;input/output (I/O)&lt;/strong&gt;[^1]: devices that cross the machine boundary, plus the two tricks (interrupts and DMA) that let a fast CPU work with slow devices. Drivers and filesystems belong to a future OS series. Here we stay in hardware.&lt;/p&gt;
&lt;h2&gt;Input devices: bringing the world in&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Input devices&lt;/strong&gt;[^2] turn the outside world into bits. A &lt;strong&gt;keyboard&lt;/strong&gt;[^3] is a grid of switches. A controller scans rows and columns many times per second, finds which crossing closed, and reports a position code. Which letter that means (&lt;code&gt;q&lt;/code&gt;, upper or lower case) is decided later by software.&lt;/p&gt;
&lt;p&gt;An optical &lt;strong&gt;mouse&lt;/strong&gt;[^4] reports movement, not position. Its sensor photographs the desk thousands of times per second and sends how far the image moved. The machine adds those steps into cursor position. Touch, microphones, and cameras do the same at higher rates: sample the world on a schedule, send numbers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; press &lt;code&gt;A&lt;/code&gt;. The keyboard sends position code 30. Your OS layout turns it into &lt;code&gt;a&lt;/code&gt; or &lt;code&gt;A&lt;/code&gt; depending on Shift. The hardware never knew letters.&lt;/p&gt;
&lt;h2&gt;Output devices: carrying bits back out&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Output devices&lt;/strong&gt;[^5] run the reverse way. A &lt;strong&gt;display&lt;/strong&gt;[^6] is a grid of pixels. The machine writes color values into a memory region called the &lt;strong&gt;framebuffer&lt;/strong&gt;. The display controller reads it 60 or more times per second and turns each value into light. What you see is memory made visible, refreshed faster than eyes follow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Audio&lt;/strong&gt;[^7] does the same in time. Values for air pressure sit in a buffer, and the audio controller feeds them to a converter tens of thousands of times per second. Miss the schedule and you hear a click. Video forgives a late frame, audio never forgives a late sample.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; move a window and you write new pixels into the framebuffer. The next 60 Hz refresh shows them. Play a song and 44100 samples per second must leave on time or you hear gaps.&lt;/p&gt;
&lt;h2&gt;Storage and network devices: I/O in disguise&lt;/h2&gt;
&lt;p&gt;Storage disks from the last post are I/O devices to the CPU. The processor never touches platters or flash directly. It sends commands like &amp;quot;read blocks 100 to 107&amp;quot; and waits for done.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Network devices&lt;/strong&gt;[^8] are both directions at once. The same card sends and receives packets. That is why a machine with only Wi-Fi stays fully useful. The network is a universal I/O channel to other machines.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; open a file and the OS sends a read command to the SSD. Load a page and the Wi-Fi card brings packets in while it sends requests out.&lt;/p&gt;
&lt;h2&gt;Controllers: translators at the border&lt;/h2&gt;
&lt;p&gt;No CPU speaks keyboard or display natively. A &lt;strong&gt;controller&lt;/strong&gt;[^9] sits between: it speaks device language on one side and machine language on the other. Keyboard controller scans the matrix. Display controller keeps the refresh rhythm. Storage controller turns commands into seeks or flash writes. Network controller frames bits for wires or radio.&lt;/p&gt;
&lt;p&gt;Controllers also offload boring timing work. The CPU says &amp;quot;fetch these sectors&amp;quot; and goes away. The controller does the millisecond by millisecond dance alone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; your CPU does not blink each pixel 60 times per second. It draws once into the framebuffer. The display controller repaints the screen alone from there.&lt;/p&gt;
&lt;h2&gt;Buses and ports: the roads and doors&lt;/h2&gt;
&lt;p&gt;Devices plug in through shared links. A classic &lt;strong&gt;bus&lt;/strong&gt;[^10] carries address, data, and control between parts. Modern machines use fast point-to-point links like PCIe for speed, but the job is the same: roads with traffic rules.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;port&lt;/strong&gt;[^11] is where the road meets the outside: USB-C, HDMI, Ethernet, audio jack. Standards like USB or HDMI define the electric talk so makers interoperate. Plug in a mouse and you join a small network with its own schedule.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; plug a USB drive in. It identifies itself, the host schedules its packets, and files appear. Unplug HDMI and the display controller simply has nowhere to send pixels.&lt;/p&gt;
&lt;h2&gt;Interrupts: devices that raise their hand&lt;/h2&gt;
&lt;p&gt;The CPU runs billions of cycles per second. A keystroke arrives a few times per second. Asking every device in a loop (&amp;quot;anything new?&amp;quot;) wastes all attention on silence. That loop is called polling.&lt;/p&gt;
&lt;p&gt;An &lt;strong&gt;interrupt&lt;/strong&gt;[^12] flips it. The device taps the CPU shoulder instead of waiting to be asked.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;program running
  ↓ (interrupt: key pressed)
save registers + rip
  ↓
jump to handler, read key code
  ↓
restore registers + rip
  ↓
program continues, never noticed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; press a key while a game runs. The keyboard raises an interrupt, the CPU grabs code 30 in microseconds, queues it, and the game keeps rendering. No polling, no missed press.&lt;/p&gt;
&lt;h2&gt;DMA: moving data without the CPU&lt;/h2&gt;
&lt;p&gt;Interrupts fix notification, but bulk is still heavy. Dragging every disk byte through CPU registers wastes the processor. &lt;strong&gt;Direct memory access&lt;/strong&gt;[^13] fixes it: hardware moves data between device and RAM alone.&lt;/p&gt;
&lt;p&gt;The CPU sets source, destination, and size, then does other work. The DMA engine copies in bursts. When done it raises one interrupt: data is there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; play a 4K video. The SSD streams frames into RAM by DMA while the CPU decodes the last frame. Without DMA the CPU would copy every byte itself and stall.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;in              (keyboard, mouse, mic, camera)
  ↓
controllers     (translate device talk to machine talk)
  ↓
CPU &amp;lt;-&amp;gt; memory  (asks, computes)
  ↓
out             (display framebuffer, audio buffer)
  +
bulk            (storage and network by commands + DMA)
  +
signals         (interrupts tap the CPU shoulder)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Input samples the world into bits. Output renders bits into light and sound. Controllers translate at the border. Interrupts save polling. DMA saves copying. One boundary remains: the wires between machines.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Input/output&quot;&gt;Input/output - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Input_device&quot;&gt;Input device - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer_keyboard&quot;&gt;Computer keyboard - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer_mouse&quot;&gt;Computer mouse - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Output_device&quot;&gt;Output device - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer_monitor&quot;&gt;Computer monitor - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Sound_card&quot;&gt;Sound card - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Network_interface_controller&quot;&gt;Network interface controller - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Controller_(computing)&quot;&gt;Controller (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Bus_(computing)&quot;&gt;Bus (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer_port_(hardware)&quot;&gt;Computer port (hardware) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^12]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Interrupt&quot;&gt;Interrupt - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^13]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Direct_memory_access&quot;&gt;Direct memory access - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><enclosure url="https://www.wesleybertipaglia.com//img/cover/f4ri9bh6.webp" type="image/webp"/><category>io</category><category>computer-architecture</category><category>internals</category><category>how-it-works</category></item><item><title>Storage Explained</title><link>https://www.wesleybertipaglia.com/blog/s6kfogsy/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/s6kfogsy/</guid><description>A deep dive into persistent storage: what makes storage different from memory, how hard drives and SSDs work, NAND flash cells, sectors and blocks, sequential versus random access, and the latency, throughput, and capacity that describe every drive.</description><pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/5atxjdiq&quot;&gt;memory&lt;/a&gt; post ended at the bottom of the hierarchy: the world where data rests for years. This post stays there. Filesystems (how bytes become files and folders) belong to a future series, not this one.&lt;/p&gt;
&lt;h2&gt;Persistent storage: memory that survives power loss&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Computer data storage&lt;/strong&gt;[^1] keeps information without power, for years, at a price per byte far below RAM. The trade is speed and a different contract: the CPU never touches storage byte by byte. It talks in fixed chunks through a controller.&lt;/p&gt;
&lt;p&gt;Programs rest on storage. Running means copying into memory first. Boot, launch an app, open a document: each step moves up from the slow persistent world into the fast volatile one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; open a game and it starts loading content from SSD into RAM as needed, like asset streaming. Pull the plug and RAM empties, SSD keeps everything.&lt;/p&gt;
&lt;h2&gt;HDD: bits as magnetism&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;hard disk drive&lt;/strong&gt;[^2] stores bits as magnetic patterns on spinning platters. An arm with read heads floats nanometers above the surface and reads those patterns.&lt;/p&gt;
&lt;p&gt;Moving parts bring three effects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Position takes time: the arm must seek the right track, then wait for rotation to bring the sector under the head. That is several milliseconds before any byte flows.&lt;/li&gt;
&lt;li&gt;Capacity is cheap: platters are simple surfaces, so terabytes cost little.&lt;/li&gt;
&lt;li&gt;It is fragile and hungry: it spins, vibrates, and dislikes drops.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a 4 TB external drive for backups. Huge and cheap, but you hear it spin and you feel every random read stall.&lt;/p&gt;
&lt;h2&gt;SSD: bits as trapped charge&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;solid-state drive&lt;/strong&gt;[^3] has no moving parts. It stores bits as charge trapped in &lt;strong&gt;NAND flash&lt;/strong&gt;[^4] cells, transistors with an isolated pocket where charge stays put for years without power.&lt;/p&gt;
&lt;p&gt;No arm to move means silent, shock resistant, and far quicker to the first byte. But it is not just fast memory. Flash has its own rules below, and an SSD hides them with a controller, firmware, DRAM cache, and flash chips working together.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The same flash lives in many shapes: a 2.5 inch SATA SSD, a stick-like M.2 NVMe drive, or a tiny microSD card in your phone. Same bits as trapped charge, different package and speed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; swap a laptop HDD for a SATA SSD and the same machine boots in 15 seconds instead of 2 minutes. Same files, no mechanics.&lt;/p&gt;
&lt;h2&gt;NAND flash: cells, pages, blocks&lt;/h2&gt;
&lt;p&gt;A flash cell stores charge levels, not just on or off. Manufacturers name each density by how many bits it stores per cell. Common densities trade precision for capacity:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SLC (Single-Level Cell)&lt;/strong&gt;: 1 bit per cell, 2 levels. Fastest, toughest, priciest.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MLC (Multi-Level Cell)&lt;/strong&gt;: 2 bits per cell, 4 levels. Balanced.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TLC (Triple-Level Cell)&lt;/strong&gt;: 3 bits per cell, 8 levels. Dense, common in laptops.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QLC (Quad-Level Cell)&lt;/strong&gt;: 4 bits per cell, 16 levels. Densest, slowest, wears fastest.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More bits per cell means cheaper gigabytes but thinner margins, so slower writes and more error correction. No free density.&lt;/p&gt;
&lt;p&gt;Cells form &lt;strong&gt;pages&lt;/strong&gt; (4 to 16 KiB), the unit for read and write, grouped into &lt;strong&gt;blocks&lt;/strong&gt; (hundreds of pages), the unit for erase. Flash has one hard rule: you can write a page only when it is erased, and erasing wipes a whole block at once.&lt;/p&gt;
&lt;p&gt;So overwrite means write elsewhere and mark the old page invalid. Later the drive moves valid pages away and erases the block. That extra copying is &lt;strong&gt;write amplification&lt;/strong&gt;. Blocks also wear out after thousands of erases, so firmware spreads writes evenly (&lt;strong&gt;wear leveling&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; save a 4 KiB text file again. The SSD writes a fresh page elsewhere, marks the old one dead, and cleans it later. You never see this, the controller does.&lt;/p&gt;
&lt;h2&gt;Sectors and blocks: the language of drives&lt;/h2&gt;
&lt;p&gt;Drives speak in chunks. A &lt;strong&gt;disk sector&lt;/strong&gt;[^5] (classically 512 bytes, today often 4 KiB) is the smallest piece the OS reads or writes. Flash pages and blocks are the matching units inside SSDs.&lt;/p&gt;
&lt;p&gt;Byte access like RAM is gone here. Everything is parcels. That is why small scattered writes cost more than their size: seeks on disks, mapping and cleanup inside SSDs. Systems align their structures to these sizes for a reason.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a database writes in 4 KiB or 16 KiB pages to match the drive, not byte by byte. One aligned write, one parcel, no extra tax.&lt;/p&gt;
&lt;h2&gt;Sequential vs random access: the pattern decides the speed&lt;/h2&gt;
&lt;p&gt;The same drive shows two speeds depending on how you ask. &lt;strong&gt;Sequential access&lt;/strong&gt; reads one long run. &lt;strong&gt;Random access&lt;/strong&gt; jumps between scattered spots.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On HDD the gap is huge: sequential is fast once positioned, random is very slow because each jump pays a fresh seek.&lt;/li&gt;
&lt;li&gt;On SSD the gap is smaller: sequential fills the link, random is slower but still thousands of times faster than disk.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Layout matters. Logs, video, and backups are kind because they are sequential. Much of systems work (caches, prefetching, log designs) turns random into sequential before it hits the drive.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; copy a 5 GB movie file: fast. Copy 5 GB made of ten thousand tiny files: slow on the same drive. Same bytes, different pattern.&lt;/p&gt;
&lt;h2&gt;Three numbers: latency, throughput, capacity&lt;/h2&gt;
&lt;p&gt;Three numbers describe any drive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Latency&lt;/strong&gt;: wait before the first byte. HDD answers in milliseconds (mechanics). SSD answers in tens of microseconds[^3], thousands of times quicker than a disk, yet thousands of times slower than RAM. Latency is what makes a system feel snappy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Throughput&lt;/strong&gt;: flow once started. Disks hold near 100 MB/s sequential. SATA SSDs near 0.5 GB/s. NVMe drives reach several GB/s[^6]. The cable sets the ceiling: SATA, then PCIe generations carrying NVMe.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capacity&lt;/strong&gt;: how much rests there. Terabytes are normal on disks and common on SSDs, far beyond RAM at a fraction of the price per byte.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each level down the hierarchy gets larger, slower, and cheaper per byte.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; opening 50 browser tabs wants capacity. Launching an app fast wants low latency. Copying 4K video wants throughput.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;need: keep data without power, cheap per byte
  ↓
HDD                     (magnetism + moving arm, vast and cheap, seeks hurt)
SSD                     (trapped charge, silent and quick, pages and blocks rule)
  ↓
talk in parcels         (sectors, pages, blocks)
  ↓
pattern decides speed   (sequential fast, random slow)
  ↓
describe with latency / throughput / capacity
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Storage trades speed for persistence. The machine is now almost complete: processor, memory, and storage in place. What remains is how it talks to the outside world.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer_data_storage&quot;&gt;Computer data storage - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Hard_disk_drive&quot;&gt;Hard disk drive - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Solid-state_drive&quot;&gt;Solid-state drive - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Flash_memory&quot;&gt;Flash memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Disk_sector&quot;&gt;Disk sector - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/NVM_Express&quot;&gt;NVM Express - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><enclosure url="https://www.wesleybertipaglia.com//img/cover/q9y4jpc5.webp" type="image/webp"/><category>storage</category><category>computer-architecture</category><category>internals</category><category>how-it-works</category></item><item><title>Memory Explained</title><link>https://www.wesleybertipaglia.com/blog/5atxjdiq/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/5atxjdiq/</guid><description>A deep dive into memory: memory cells and addresses, RAM and ROM, SRAM and DRAM, volatile versus non-volatile storage, capacity, latency, bandwidth, the memory bus, and the hierarchy that keeps the CPU fed.</description><pubDate>Sat, 05 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/cmvoxsqk&quot;&gt;CPU&lt;/a&gt; post gave programs a place to run but nowhere to live: registers hold a few dozen values, and that is all. The &lt;a href=&quot;/blog/5zpz59bz&quot;&gt;digital logic&lt;/a&gt; post showed how a flip-flop holds one bit. This post scales that bit up to gigabytes.&lt;/p&gt;
&lt;h2&gt;Memory cells: holding one bit&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Computer memory&lt;/strong&gt;[^1] starts from tiny structures that store bits, called &lt;strong&gt;memory cells&lt;/strong&gt;. The machine needs billions of them, so one small design repeated billions of times decides everything.&lt;/p&gt;
&lt;p&gt;There are two classic designs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SRAM (Static RAM)&lt;/strong&gt;[^2]: a small loop that holds its bit steady while power is on. It uses about six transistors per bit, so it is fast and simple but large.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DRAM (Dynamic RAM)&lt;/strong&gt;[^3]: one transistor plus one tiny capacitor where the bit lives as charge. It is far smaller and cheaper, but the charge leaks in milliseconds, so the memory must refresh it again and again just to remember.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Think whiteboard versus leaky bucket. The whiteboard keeps your writing until you erase it. The bucket needs constant refilling.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neither type wins on everything, so machines use both:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;              SRAM                  DRAM
cell          loop, ~6              1 transistor +
              transistors           1 capacitor
size          large                 tiny
speed         very fast             slower
refresh       none needed           constant refresh
cost          expensive             cheap
used for      caches                main memory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SRAM buys speed with silicon area. DRAM buys density with refresh work. Architects put SRAM where speed matters most and DRAM where capacity matters most.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a few megabytes of SRAM cache sit next to the CPU. Gigabytes of DRAM sit further away as main memory.&lt;/p&gt;
&lt;h2&gt;Addresses: every byte has a number&lt;/h2&gt;
&lt;p&gt;A pile of cells is useless without names. That name is the &lt;strong&gt;memory address&lt;/strong&gt;[^4], a number for each location, like a house number on a street. Modern memory is byte-addressable: each address names one byte, and bigger values use consecutive addresses.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;n&lt;/code&gt; address bits you can name &lt;code&gt;2^n&lt;/code&gt; places. 32 bits name 4 GiB. 64 bits could name far more than any machine holds, so real chips use fewer bits in practice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a pointer in C like &lt;code&gt;0x7fff1000&lt;/code&gt; is just such a house number. &lt;code&gt;rsp&lt;/code&gt; from the CPU post is another address, pointing at the top of the stack.&lt;/p&gt;
&lt;h2&gt;RAM: the workbench&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Random-access memory&lt;/strong&gt;[^5] holds running programs and their data: code, heap, stack. Random access means any address can be read in about the same time. A tape must wind past everything before it, RAM just jumps straight there.&lt;/p&gt;
&lt;p&gt;RAM is the CPU main partner: large enough for gigabytes of work, fast enough with caches helping, but forgetful. In its usual DRAM form it loses everything when power goes out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; unsaved work vanishes in a blackout for this reason. Every boot copies the operating system from storage into RAM again before anything can run.&lt;/p&gt;
&lt;h2&gt;ROM: memory that remembers&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Read-only memory&lt;/strong&gt;[^6] is the mirror image: it keeps its contents without power instead of being fast to rewrite. Its classic job is &lt;strong&gt;firmware&lt;/strong&gt;, the first instructions on power-up, which must already be there before anything can load.&lt;/p&gt;
&lt;p&gt;Pure read-only chips are history. Modern firmware lives in rewritable &lt;strong&gt;non-volatile memory&lt;/strong&gt;[^7] like &lt;strong&gt;flash memory&lt;/strong&gt;[^8]. It keeps bits without power but can still be reprogrammed slowly in large blocks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; press the power button and the CPU starts running firmware from flash before your SSD or RAM is even ready. That code then starts the boot.&lt;/p&gt;
&lt;h2&gt;Three numbers: capacity, latency, bandwidth&lt;/h2&gt;
&lt;p&gt;Three numbers compare any memory:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Capacity&lt;/strong&gt;: how much it holds. Registers hold bytes, caches hold kilobytes to megabytes, main memory holds gigabytes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latency&lt;/strong&gt;: how long one access takes. Registers respond in a fraction of a nanosecond, fast caches in about a nanosecond, main memory in tens of nanoseconds[^9]. Latency is the wait before the first byte arrives.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bandwidth&lt;/strong&gt;: how much arrives per second once flowing, in gigabytes per second. One memory channel moves tens of gigabytes per second. Bandwidth is the width of the pipe, latency is its length.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;A sports car delivers one passenger quickly (low latency). A train delivers hundreds slowly (high bandwidth).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; chasing a linked list pointer by pointer needs low latency. Streaming a video frame needs high bandwidth.&lt;/p&gt;
&lt;h2&gt;The memory bus: talking to RAM&lt;/h2&gt;
&lt;p&gt;Registers live inside the CPU, but main memory sits outside, so the processor reaches it through shared wires called the &lt;strong&gt;bus&lt;/strong&gt;[^10]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CPU -------- address bus (which location?) -------&amp;gt; memory
CPU &amp;lt;------&amp;gt; data bus (the bytes themselves) &amp;lt;----&amp;gt; memory
CPU -------- control lines (read? write? when?) --&amp;gt; memory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To read, the CPU puts an address out, raises read, waits, and collects the bytes. A 64-bit data path moves 8 bytes per transfer. Modern DDR memory (DDR4, DDR5) transfers on both clock edges to double transfers per tick, but the talk stays the same: ask by address, answer with data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; &lt;code&gt;mov rax, [0x1000]&lt;/code&gt; puts &lt;code&gt;0x1000&lt;/code&gt; on the address bus, signals read, and &lt;code&gt;rax&lt;/code&gt; receives the 8 bytes that come back on the data bus.&lt;/p&gt;
&lt;h2&gt;The memory hierarchy: fast, big, cheap, pick two&lt;/h2&gt;
&lt;p&gt;No single memory is fastest, largest, and cheapest at once, so machines stack them into a &lt;strong&gt;memory hierarchy&lt;/strong&gt;[^11]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;registers     (bytes, fastest, program manages)
    ↓
caches        (KB to MB, ~1 ns, SRAM, hardware manages)
    ↓
main memory   (GB, tens of ns, DRAM)
    ↓
storage       (TB, microseconds to ms, non-volatile)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works because of &lt;strong&gt;locality&lt;/strong&gt;. Programs reuse the same data (temporal locality) and touch neighbors together (spatial locality). So hardware keeps copies of recent and nearby data in &lt;strong&gt;caches&lt;/strong&gt;[^12], where most accesses hit. A miss pays a trip down the ladder. Caches move whole 64-byte lines at once and track which copies are valid, all invisibly to your code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a loop over an array &lt;code&gt;for (i = 0; i &amp;lt; n; i++) sum += a[i]&lt;/code&gt; flies because each 64-byte cache line brings 16 integers at once and the next 15 are already there.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;cells       (SRAM / DRAM)
  ↓
addresses   (names for bytes)
  ↓
RAM         (work in progress) + ROM (remembers through power loss)
  ↓
bus         (ask by address, answer with data)
  ↓
hierarchy   (registers -&amp;gt; caches -&amp;gt; memory -&amp;gt; storage)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cells hold bits, addresses name bytes, RAM hosts current work while ROM survives power loss, the bus carries the talk, and the hierarchy keeps what locality predicts the CPU will want next close by.&lt;/p&gt;
&lt;p&gt;What the hierarchy pushes down is the next subject: the non-volatile world where data rests for years.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer_memory&quot;&gt;Computer memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Static_random-access_memory&quot;&gt;Static random-access memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Dynamic_random-access_memory&quot;&gt;Dynamic random-access memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Memory_address&quot;&gt;Memory address - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Random-access_memory&quot;&gt;Random-access memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Read-only_memory&quot;&gt;Read-only memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Non-volatile_memory&quot;&gt;Non-volatile memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Flash_memory&quot;&gt;Flash memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/CAS_latency&quot;&gt;CAS latency - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Bus_(computing)&quot;&gt;Bus (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Memory_hierarchy&quot;&gt;Memory hierarchy - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^12]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Cache_(computing)&quot;&gt;Cache (computing) - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><enclosure url="https://www.wesleybertipaglia.com//img/cover/puy8c93e.webp" type="image/webp"/><category>memory</category><category>computer-architecture</category><category>internals</category><category>how-it-works</category></item><item><title>The CPU Explained</title><link>https://www.wesleybertipaglia.com/blog/cmvoxsqk/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/cmvoxsqk/</guid><description>A deep dive into the processor: instruction sets, registers, the ALU, the control unit, the fetch-decode-execute cycle, basic assembly, stack and heap at machine level, and calling conventions.</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/2lfin2nu&quot;&gt;computer&lt;/a&gt; overview gave the CPU[^1] one paragraph: it fetches instructions and executes them, billions of times per second. This post opens that black box. We will look at the contract a CPU offers to software, the parts inside it, the exact loop it runs, and how lofty ideas like &amp;quot;function calls&amp;quot; and &amp;quot;the heap&amp;quot; reduce to bytes and registers.&lt;/p&gt;
&lt;h2&gt;The ISA: the contract between software and silicon&lt;/h2&gt;
&lt;p&gt;A processor does not understand Python, JavaScript, or even C. It only understands its own machine language. The &lt;strong&gt;instruction set architecture&lt;/strong&gt; (ISA)[^2] is the contract that defines that language: which instructions exist, what registers are available, and how programs talk to the machine.&lt;/p&gt;
&lt;p&gt;Each CPU family speaks one ISA. The most common in daily life are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;x86&lt;/strong&gt;[^3]: very common in laptops and desktops.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ARM&lt;/strong&gt;[^4]: present in most phones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RISC-V&lt;/strong&gt;[^5]: an open ISA growing in embedded hardware.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A program compiled for one ISA cannot run on another. That is why your phone cannot run laptop binaries, and why Apple spent years translating x86 apps to ARM. Same code, different contract, no execution.&lt;/p&gt;
&lt;p&gt;Everything below uses x86 with ARM notes where they differ, since the concepts transfer.&lt;/p&gt;
&lt;h2&gt;Registers: the CPU&amp;#39;s workbench&lt;/h2&gt;
&lt;p&gt;The CPU cannot work directly on RAM for most instructions. Memory is too slow and too far. So it keeps a few slots on the chip itself called &lt;strong&gt;processor registers&lt;/strong&gt;[^6]. They are the fastest storage the CPU has, its hands and its desk.&lt;/p&gt;
&lt;p&gt;Think of registers as named boxes, each holding one number. x86 has 16 general boxes (like &lt;code&gt;rax&lt;/code&gt;, &lt;code&gt;rbx&lt;/code&gt;, &lt;code&gt;rcx&lt;/code&gt;) and a few with special jobs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rip&lt;/code&gt;: holds the address of the next instruction to run.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rsp&lt;/code&gt;: points to the top of the stack.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rflags&lt;/code&gt;: stores small yes or no notes about the last calculation.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;You do not need to memorize these names because what matters here is understanding what they are and what they are for.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; when you write &lt;code&gt;int a = 5&lt;/code&gt;, the value &lt;code&gt;5&lt;/code&gt; waits in RAM. RAM can hold millions of values, but the CPU cannot calculate directly on RAM. So to use it, the CPU copies &lt;code&gt;5&lt;/code&gt; into a register like &lt;code&gt;eax&lt;/code&gt;, does the work there, then copies the result back to RAM.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Think of RAM as the pantry where all ingredients are stored, and the register as the cutting board holding only the one ingredient you are using right now.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Caches: data close by&lt;/h2&gt;
&lt;p&gt;Waiting for RAM costs hundreds of clock cycles. So between RAM and registers the CPU keeps two middle layers, called L1 and L2 caches[^7].&lt;/p&gt;
&lt;p&gt;They have different jobs. Registers hold the few values the CPU is using right now, with names like &lt;code&gt;rax&lt;/code&gt;. Caches hold copies of RAM data the CPU will probably need soon, with no names, managed automatically. On a miss it waits for RAM and saves a copy for next time.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;RAM (big, slow)
  ↓
L2 cache (smaller, closer)
  ↓
L1 cache (even smaller, even closer)
  ↓
registers (the values in use now)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The ALU: where the math happens&lt;/h2&gt;
&lt;p&gt;The ALU[^8] does the actual calculation. It takes two numbers plus an operation (add, subtract, AND, OR) and returns a result.&lt;/p&gt;
&lt;p&gt;The ALU is built from the circuits of the previous post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adder does addition, and subtraction reuses the same adder by flipping the number to its two&amp;#39;s complement form.&lt;/li&gt;
&lt;li&gt;Logic gates do AND, OR, and XOR.&lt;/li&gt;
&lt;li&gt;A multiplexer at the end picks which result to keep.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Many instructions also leave small notes in &lt;code&gt;rflags&lt;/code&gt; as a side effect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ZF: result was zero.&lt;/li&gt;
&lt;li&gt;SF: result was negative.&lt;/li&gt;
&lt;li&gt;CF / OF: number overflowed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; read &lt;code&gt;add rax, rbx&lt;/code&gt; as a short order with three parts: what to do (&lt;code&gt;add&lt;/code&gt;), where to save the answer (&lt;code&gt;rax&lt;/code&gt;), and which ingredient to use (&lt;code&gt;rbx&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;rax holds 10, rbx holds 7.
Command: add rax, rbx
Result: rax now holds 17, and rbx still holds 7.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;cmp rax, rbx&lt;/code&gt; instruction saves no new number. Internally it does &lt;code&gt;rax - rbx&lt;/code&gt; and throws the result away, keeping only the notes in the flags. The next instruction doesn&amp;#39;t look at &lt;code&gt;rax&lt;/code&gt; or &lt;code&gt;rbx&lt;/code&gt; again; it reads only those notes to decide whether to jump or keep going straight.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;rax and rbx both hold 10 -&amp;gt; note says &amp;quot;equal&amp;quot;, so je jumps
rax holds 10, rbx holds 7 -&amp;gt; note says &amp;quot;not equal&amp;quot;, so je stays
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The control unit: the conductor&lt;/h2&gt;
&lt;p&gt;It turns a passive byte pattern in memory into coordinated action. Registers are the workbench and the ALU is the tool, but someone must decide what to do each moment.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;control unit&lt;/strong&gt;[^9] reads the current instruction, decodes its bits (with a decoder, from the previous post), and sends control wires to the rest of the chip: send these two registers into the ALU, select addition, write the result back there, move to the next instruction.&lt;/p&gt;
&lt;p&gt;Simple RISC CPUs do this with direct hardwired logic. Big x86 chips first break complex instructions into smaller RISC-like steps called micro-operations, then schedule those. Either way the job is the same.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; for &lt;code&gt;add rax, rbx&lt;/code&gt; (with &lt;code&gt;rax = 10&lt;/code&gt; and &lt;code&gt;rbx = 7&lt;/code&gt;), the control unit says: connect box &lt;code&gt;rax&lt;/code&gt; and box &lt;code&gt;rbx&lt;/code&gt; to the ALU inputs, select ADD, write the ALU output (&lt;code&gt;17&lt;/code&gt;) back into box &lt;code&gt;rax&lt;/code&gt;, advance &lt;code&gt;rip&lt;/code&gt; to the next instruction.&lt;/p&gt;
&lt;h2&gt;State machines: how the CPU remembers what it is doing&lt;/h2&gt;
&lt;p&gt;The control unit has a problem that combinational logic alone cannot solve. A decoder or a mux forgets everything instantly. But running an instruction takes several steps, so the CPU must remember which step it is on.&lt;/p&gt;
&lt;p&gt;It solves this with a &lt;strong&gt;finite state machine&lt;/strong&gt;[^10]. That is a fancy name for a simple idea: a current state stored in flip-flops, plus combinational logic that decides the next state. The CPU lives in one state at a time and moves on each clock tick.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a traffic light is a state machine with three states: red, green, yellow. Red always goes to green, green always goes to yellow, yellow always goes to red. The CPU control unit works the same way. That stored state is what lets a pile of forgetful gates behave like a cook following a recipe step by step.&lt;/p&gt;
&lt;h2&gt;The instruction cycle: fetch, decode, execute&lt;/h2&gt;
&lt;p&gt;It runs one instruction after another, forever. This loop is the &lt;strong&gt;instruction cycle&lt;/strong&gt;[^11]. It is the state machine above in action:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;FETCH -&amp;gt; DECODE -&amp;gt; EXECUTE -&amp;gt; FETCH -&amp;gt; DECODE -&amp;gt; ...
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Fetch.&lt;/strong&gt; Copy the bytes at the address in &lt;code&gt;rip&lt;/code&gt; into the CPU, and move &lt;code&gt;rip&lt;/code&gt; past them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decode.&lt;/strong&gt; Look at those bytes: which operation, which registers, which memory address.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execute.&lt;/strong&gt; Move data through the ALU or memory, write results back, update flags, and either keep going forward or jump &lt;code&gt;rip&lt;/code&gt; somewhere new. After EXECUTE the machine returns to FETCH, unless a jump changed &lt;code&gt;rip&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;A 3 GHz clock ticks three billion times per second, but that does not mean three billion instructions per second. Modern CPUs finish several instructions per tick or take several ticks per instruction. The fetch, decode, execute contract stays the same.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Instruction anatomy&lt;/h2&gt;
&lt;p&gt;All instructions share the same basic structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Opcode&lt;/strong&gt;: what to do.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operands&lt;/strong&gt;: what to do it with.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;x86 instructions range from 1 to 15 bytes to keep common cases tiny. ARM64 instructions are typically a fixed 4 bytes, which makes decoding simpler. Operands come in three flavors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Immediate: the value is written right in the instruction. &lt;code&gt;add rax, 5&lt;/code&gt; means &lt;code&gt;rax = rax + 5&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Register: the value is in another box. &lt;code&gt;add rax, rbx&lt;/code&gt; means &lt;code&gt;rax = rax + rbx&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Memory: the value is in RAM. Square brackets mean &amp;quot;go to RAM&amp;quot;. &lt;code&gt;mov rax, [rbx+16]&lt;/code&gt; takes the number in &lt;code&gt;rbx&lt;/code&gt;, adds &lt;code&gt;16&lt;/code&gt; to find an address, reads the 8 bytes stored there, and copies them into &lt;code&gt;rax&lt;/code&gt;. This is how arrays (&lt;code&gt;array[i]&lt;/code&gt;) and structs (&lt;code&gt;object.field&lt;/code&gt;) work underneath.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Calling conventions and the stack&lt;/h2&gt;
&lt;p&gt;Caller and callee must agree on where arguments go. That agreement is the &lt;strong&gt;calling convention&lt;/strong&gt;[^12].&lt;/p&gt;
&lt;p&gt;On Linux and macOS, integer arguments go in specific registers (&lt;code&gt;rdi, rsi, rdx&lt;/code&gt; and so on), and the answer always comes back in &lt;code&gt;rax&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So a function knows where to return after it finishes, we use the &lt;strong&gt;stack&lt;/strong&gt;[^13]. It is just space in RAM controlled by the &lt;code&gt;rsp&lt;/code&gt; register (remember it?).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Think of it like a pile of plates: &lt;code&gt;push&lt;/code&gt; puts a plate on top, &lt;code&gt;pop&lt;/code&gt; takes one off. The &lt;code&gt;call&lt;/code&gt; instruction pushes the return address and jumps. The &lt;code&gt;ret&lt;/code&gt; instruction pops it and goes back to where it was.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The heap: memory on request&lt;/h2&gt;
&lt;p&gt;The heap gets memory while the program runs, when you do not know the size in advance.&lt;/p&gt;
&lt;p&gt;The heap is a region of memory managed by library code like &lt;code&gt;malloc&lt;/code&gt;, which asks the operating system for large chunks (on Linux with &lt;code&gt;brk&lt;/code&gt; and &lt;code&gt;mmap&lt;/code&gt;) and then splits them into pieces. &lt;code&gt;malloc&lt;/code&gt; and &lt;code&gt;free&lt;/code&gt; are not CPU features. They are bookkeeping lists of free blocks.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;At machine level there are no variables, no objects, no heap. There are only addresses, values, and conventions. &lt;code&gt;malloc(64)&lt;/code&gt; just finds 64 free bytes somewhere and returns the address.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Doing more at once: pipeline and cores&lt;/h2&gt;
&lt;p&gt;So far we described one executor running one instruction at a time. Modern CPUs go faster with two simple tricks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pipeline&lt;/strong&gt;[^14]: Instead of finishing fetch, decode, execute for one instruction before starting the next, the CPU overlaps them like an assembly line. While one instruction executes, the next decodes and the one after fetches. That overlap is why a CPU can finish more than one instruction per clock tick.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cores&lt;/strong&gt;[^15]: Almost every processor today has several independent executors, called cores, each running its own fetch, decode, execute loop. One core is still the lonely executor from this post, but four or eight cores run four or eight loops in parallel.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;A CPU is a contract (the ISA), a workbench (registers), a calculator (the ALU), a conductor (the control unit), and a state machine running fetch, decode, execute on loop:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ISA (contract)
  ↓
registers (workbench)
  ↓
ALU (calculator)
  ↓
control unit (conductor)
  ↓
FETCH -&amp;gt; DECODE -&amp;gt; EXECUTE (state machine loop)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stack, heap, and function calls are conventions built on addresses, values, and control flow. How an operating system turns this lonely executor into a machine running a hundred programs at once is another post.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Central_processing_unit&quot;&gt;Central processing unit - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Instruction_set_architecture&quot;&gt;Instruction set architecture - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/x86&quot;&gt;x86 - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/ARM_architecture&quot;&gt;ARM architecture - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/RISC-V&quot;&gt;RISC-V - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Processor_register&quot;&gt;Processor register - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/CPU_cache&quot;&gt;CPU cache - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Arithmetic_logic_unit&quot;&gt;Arithmetic logic unit - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Control_unit&quot;&gt;Control unit - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Finite-state_machine&quot;&gt;Finite-state machine - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Instruction_cycle&quot;&gt;Instruction cycle - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^12]: &lt;a href=&quot;https://en.wikipedia.org/wiki/X86_calling_conventions&quot;&gt;x86 calling conventions - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^13]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Call_stack&quot;&gt;Call stack - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^14]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Instruction_pipelining&quot;&gt;Instruction pipelining - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^15]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Multi-core_processor&quot;&gt;Multi-core processor - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><enclosure url="https://www.wesleybertipaglia.com//img/cover/ciu784jl.webp" type="image/webp"/><category>cpu</category><category>assembly</category><category>internals</category><category>computer-architecture</category></item><item><title>Digital Logic Explained</title><link>https://www.wesleybertipaglia.com/blog/5zpz59bz/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/5zpz59bz/</guid><description>A bottom-up tour of digital logic: transistors as switches, Boolean logic and gates, truth tables, combinational circuits like adders and multiplexers, sequential circuits like flip-flops and registers, and how they stack into the ALU and CPU.</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/cmvoxsqk&quot;&gt;CPU&lt;/a&gt; post treated the ALU and the control unit as given: boxes that add, compare, and orchestrate. This post opens those boxes. We will build upward from the humblest component, a switch, through the mathematics of true and false, the gates that embody it, the circuits that compute and remember, and finally the ladder that reaches back up to the processor.&lt;/p&gt;
&lt;h2&gt;The transistor: a switch, nothing more&lt;/h2&gt;
&lt;p&gt;At the bottom there is no Boolean logic, only physics. A &lt;strong&gt;transistor&lt;/strong&gt;[^1] is an electrically controlled switch: a small voltage on one terminal decides whether current flows between the other two. On means current can flow; off means it is largely blocked.&lt;/p&gt;
&lt;p&gt;Everything else in this post is a consequence of that one behavior plus a convention from the previous post: interpret a high voltage as &lt;code&gt;1&lt;/code&gt; and a low voltage as &lt;code&gt;0&lt;/code&gt;. Billions of such switches, etched into silicon and wired together, become a machine that computes. You do not need the semiconductor physics to continue. Only the mental model of a switch being controlled billions of times per second matters.&lt;/p&gt;
&lt;h2&gt;Boolean logic: the math of true and false&lt;/h2&gt;
&lt;p&gt;Switches need a mathematics, and it is &lt;strong&gt;Boolean algebra&lt;/strong&gt;[^2]: a system with only two values, &lt;code&gt;0&lt;/code&gt; (false) and &lt;code&gt;1&lt;/code&gt; (true), and three fundamental operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AND&lt;/strong&gt; (&lt;code&gt;A · B&lt;/code&gt;): &lt;code&gt;1&lt;/code&gt; only if both inputs are &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OR&lt;/strong&gt; (&lt;code&gt;A + B&lt;/code&gt;): &lt;code&gt;1&lt;/code&gt; if at least one input is &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NOT&lt;/strong&gt; (&lt;code&gt;¬A&lt;/code&gt;): flips the value (&lt;code&gt;¬0 = 1&lt;/code&gt;, &lt;code&gt;¬1 = 0&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two derived operations appear so often they earned their own names: &lt;strong&gt;XOR&lt;/strong&gt; (exclusive OR, &lt;code&gt;1&lt;/code&gt; when the inputs differ) and the negated pair &lt;strong&gt;NAND&lt;/strong&gt; (&lt;code&gt;NOT AND&lt;/code&gt;) and &lt;strong&gt;NOR&lt;/strong&gt; (&lt;code&gt;NOT OR&lt;/code&gt;). NAND is special because any Boolean function can be built from NAND gates alone. NOR shares this property. Chip designers love that, because mastering one gate type means being able to build everything.&lt;/p&gt;
&lt;h2&gt;The gates: logic made physical&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;logic gate&lt;/strong&gt;[^3] is the physical embodiment of a Boolean operation: a handful of transistors wired so the output voltage follows the operation&amp;#39;s rule. Each gate is fully described by its &lt;strong&gt;truth table&lt;/strong&gt;[^4]: every input combination and the resulting output.&lt;/p&gt;
&lt;p&gt;AND, OR, and NOT:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AND          OR           NOT
A B | Q      A B | Q      A | Q
0 0 | 0      0 0 | 0      0 | 1
0 1 | 0      0 1 | 1      1 | 0
1 0 | 0      1 0 | 1
1 1 | 1      1 1 | 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;XOR[^5], NAND, and NOR[^6]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;XOR          NAND         NOR
A B | Q      A B | Q      A B | Q
0 0 | 0      0 0 | 1      0 0 | 1
0 1 | 1      0 1 | 1      0 1 | 0
1 0 | 1      1 0 | 1      1 0 | 0
1 1 | 0      1 1 | 0      1 1 | 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read XOR as &amp;quot;are they different?&amp;quot; and NAND as &amp;quot;not both.&amp;quot; Once you can read these tables, you have the basic vocabulary for reading digital circuits: gates wired together.&lt;/p&gt;
&lt;h2&gt;Combinational logic: turning gates into useful machines&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;combinational circuit&lt;/strong&gt; is a circuit whose output is purely a function of the current inputs. No memory, no feedback loops: same inputs, same output, every time.&lt;/p&gt;
&lt;p&gt;Three circuits cover most of what the CPU does.&lt;/p&gt;
&lt;h3&gt;1. The adder: the math engine&lt;/h3&gt;
&lt;p&gt;Adds binary numbers. The machine never sees &lt;code&gt;2&lt;/code&gt;, only &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;, so an adder works on binary representations, never on real or floating point numbers. It does the same &amp;quot;carry the one&amp;quot; addition you learned in school, one column at a time.&lt;/p&gt;
&lt;p&gt;Example: &lt;code&gt;01 + 01 = 10&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;  01  (1)
+ 01  (1)
----
  10  (2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Right column: &lt;code&gt;1 + 1&lt;/code&gt; does not fit in a bit, so write &lt;code&gt;0&lt;/code&gt; and carry &lt;code&gt;1&lt;/code&gt;. Left column: &lt;code&gt;0 + 0 + 1 = 1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Inside, the smallest adder is the &lt;strong&gt;half adder&lt;/strong&gt;: it adds two bits, with sum &lt;code&gt;A XOR B&lt;/code&gt; and carry &lt;code&gt;A AND B&lt;/code&gt;. A &lt;strong&gt;full adder&lt;/strong&gt; adds the carry-in as well, and chaining eight of them gives a &lt;strong&gt;ripple-carry adder&lt;/strong&gt;[^7] that adds two bytes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; &lt;code&gt;2 + 3&lt;/code&gt; becomes &lt;code&gt;010 + 011 = 101&lt;/code&gt; (5). The same circuit increments the program counter to fetch the next instruction.&lt;/p&gt;
&lt;h3&gt;2. The multiplexer (mux): the selector switch&lt;/h3&gt;
&lt;p&gt;A &lt;strong&gt;multiplexer&lt;/strong&gt;[^8] is a digital selector switch: it routes one of several inputs to a single output, chosen by the select lines. A 2-to-1 mux:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S | Q
0 | D0
1 | D1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; &lt;code&gt;result = s ? b : a&lt;/code&gt; is a mux in code. The CPU uses muxes to pick which register feeds the ALU, or which result to keep.&lt;/p&gt;
&lt;h3&gt;3. The decoder: the address router&lt;/h3&gt;
&lt;p&gt;A &lt;strong&gt;decoder&lt;/strong&gt;[^9] takes an &lt;code&gt;n&lt;/code&gt;-bit binary code and activates exactly one of its &lt;code&gt;2^n&lt;/code&gt; output lines. A 2-to-4 decoder:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A1 A0 | Y0 Y1 Y2 Y3
0  0  | 1  0  0  0
0  1  | 0  1  0  0
1  0  | 0  0  1  0
1  1  | 0  0  0  1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; to write into register 2, the CPU sends &lt;code&gt;10&lt;/code&gt; and only that register&amp;#39;s wire turns on.&lt;/p&gt;
&lt;h2&gt;Sequential logic: circuits that remember&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;sequential circuit&lt;/strong&gt;[^10] is a circuit whose output depends on both the current inputs and the previous state. To keep that state, the output is fed back into the input: a feedback loop.&lt;/p&gt;
&lt;p&gt;Three components carry the state.&lt;/p&gt;
&lt;h3&gt;1. The flip-flop: one bit that stays put&lt;/h3&gt;
&lt;p&gt;A &lt;strong&gt;flip-flop&lt;/strong&gt;[^11] is a 1-bit bistable storage element: it holds a &lt;code&gt;0&lt;/code&gt; or a &lt;code&gt;1&lt;/code&gt; even after the input goes away, and it only updates at the edge of the clock signal.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tick 1, D = 1         -&amp;gt; Q becomes 1 and holds 1
between ticks, D = *  -&amp;gt; Q still holds 1, whatever D does
tick 2, D = 0         -&amp;gt; Q becomes 0 and holds 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a boolean flag like &lt;code&gt;is_logged_in = true&lt;/code&gt; is a single flip-flop, holding its value while the CPU does other work.&lt;/p&gt;
&lt;h3&gt;2. The register: bits in formation&lt;/h3&gt;
&lt;p&gt;A &lt;strong&gt;register&lt;/strong&gt;[^12] is an array of &lt;code&gt;n&lt;/code&gt; flip-flops grouped together to store an &lt;code&gt;n&lt;/code&gt;-bit binary word. Eight flip-flops sharing one clock is an 8-bit register.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;store 5:  00000101 (each bit lives in one flip-flop)
tick:     all 8 bits are captured together
later:    00000101 is still there
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; when you write &lt;code&gt;int a = 5&lt;/code&gt;, those eight bits sit in a register inside the CPU, the workbench where the processor keeps the values it is using right now. Add a mux to pick which register to read and a decoder to pick which one to write, and you get a register file.&lt;/p&gt;
&lt;h3&gt;3. The clock: everyone marches together&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;clock signal&lt;/strong&gt;[^13] is an oscillating square wave that synchronizes when state transitions happen across all flip-flops. It computes nothing: it only decides the moment everything updates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; if the clock ticks before the slowest adder has settled, a register captures the wrong value. That is why clock speed in gigahertz has a physical limit.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;The full ladder, bottom to top:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;transistors
    ↓
logic gates
    ↓
circuits
    ↓
CPU
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Transistors switch. Gates compute truth functions. Wiring gates together builds circuits: adders, multiplexers, and decoders compute without remembering, while registers remember, all synchronized by the clock. The ALU is just a circuit that combines arithmetic and logic. Add registers and control logic to it and you have a CPU, stepping through fetch-decode-execute on every clock edge.&lt;/p&gt;
&lt;p&gt;We have now reached the processor: the next posts will move upward through memory, storage, and I/O, completing the picture of the machine before we leave hardware behind.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Transistor&quot;&gt;Transistor - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Boolean_algebra&quot;&gt;Boolean algebra - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Logic_gate&quot;&gt;Logic gate - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Truth_table&quot;&gt;Truth table - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/XOR_gate&quot;&gt;XOR gate - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/NAND_logic&quot;&gt;NAND logic - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Adder_(electronics)&quot;&gt;Adder (electronics) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Multiplexer&quot;&gt;Multiplexer - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Decoder&quot;&gt;Decoder - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Sequential_logic&quot;&gt;Sequential logic - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Flip-flop_(electronics)&quot;&gt;Flip-flop (electronics) - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^12]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Processor_register&quot;&gt;Processor register - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^13]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Clock_signal&quot;&gt;Clock signal - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><enclosure url="https://www.wesleybertipaglia.com//img/cover/o9hy6trv.webp" type="image/webp"/><category>digital-logic</category><category>computer-architecture</category><category>internals</category><category>how-it-works</category></item><item><title>Binary, Decimal, and Hex Conversions 101</title><link>https://www.wesleybertipaglia.com/blog/b1nh3xc0n/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/b1nh3xc0n/</guid><description>A beginner-friendly, step-by-step guide to number bases: what bits, binary, decimal, and hexadecimal mean, and how to convert between them quickly with binary as the middle step.</description><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Computers store everything as bits, but humans think in decimal and write shorthand in hexadecimal. The good news: decimal, binary, and hexadecimal are just three spellings of the same value.&lt;/p&gt;
&lt;p&gt;This is the practical companion to &lt;a href=&quot;/blog/ylhwiwpb&quot;&gt;Information Representation Explained&lt;/a&gt;. No theory of signed integers or floats here, just how to read and convert numbers confidently.&lt;/p&gt;
&lt;h2&gt;Bits and bytes first&lt;/h2&gt;
&lt;p&gt;Before bases, you need two words:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bit:&lt;/strong&gt; one binary digit, &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt; (off or on). It is the smallest unit of information.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Byte:&lt;/strong&gt; a set of 8 bits. It is the smallest addressable memory unit on modern computers, when you read 1 byte from memory, you always get 8 bits together.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With &lt;code&gt;n&lt;/code&gt; bits you get &lt;code&gt;2^n&lt;/code&gt; values, so one byte gives &lt;code&gt;2^8 = 256&lt;/code&gt; values (&lt;code&gt;0-255&lt;/code&gt;). Half a byte (4 bits) is called a &lt;strong&gt;nibble&lt;/strong&gt;, which matters for hex later.&lt;/p&gt;
&lt;h2&gt;The three bases in 30 seconds&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Decimal (base 10):&lt;/strong&gt; digits &lt;code&gt;0-9&lt;/code&gt;. Human-readable. Positions are &lt;code&gt;(..., 10^2, 10^1, 10^0)&lt;/code&gt; or &lt;code&gt;(..., 100, 10, 1)&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;342 = 3×100 + 4×10 + 2×1
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Binary (base 2):&lt;/strong&gt; digits &lt;code&gt;0-1&lt;/code&gt;. Machine-readable, this is what hardware stores. Positions are &lt;code&gt;(..., 2^2, 2^1, 2^0)&lt;/code&gt; or &lt;code&gt;(..., 4, 2, 1)&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;101 = (right to left) 1×1 + 0×2 + 1×4 = 5
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hexadecimal (base 16):&lt;/strong&gt; digits &lt;code&gt;0-9&lt;/code&gt; plus &lt;code&gt;A-F&lt;/code&gt; for values 10-15. Binary shorthand. Positions are &lt;code&gt;(..., 16^2, 16^1, 16^0)&lt;/code&gt; or &lt;code&gt;(..., 256, 16, 1)&lt;/code&gt;. One hex digit equals exactly 4 bits (a nibble, half a byte).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0x8DA = 8×256 + 13(D)×16 + 10(A)×1 = 2048 + 208 + 10 = 2266
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. Binary → decimal: add the weights&lt;/h2&gt;
&lt;p&gt;Read right to left with weights &lt;code&gt;..., 256, 128, 64, 32, 16, 8, 4, 2, 1&lt;/code&gt;. Use as many as you need, 8 bits is just common because it is 1 byte, numbers can be larger.&lt;/p&gt;
&lt;p&gt;Multiply each bit by its weight and add, going right to left so the math flows from &lt;code&gt;1&lt;/code&gt; upward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0011 0100 = 0*1 + 0*2 + 1*4 + 0*8 + 1*16 + 1*32 + 0*64 + 0*128
         = 4+16+32 = 52
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same with the running example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;11010100 = 0*1 + 0*2 + 1*4 + 0*8 + 1*16 + 0*32 + 1*64 + 1*128
         = 4+16+64+128 = 212
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So &lt;code&gt;0b11010100 = 212&lt;/code&gt;. Zeros add nothing, you can skip them once it clicks.&lt;/p&gt;
&lt;p&gt;Quick check: &lt;code&gt;0b1011 = 1*1 + 1*2 + 0*4 + 1*8 = 1+2+8 = 11&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;2. Decimal → binary: subtract the biggest weight that fits&lt;/h2&gt;
&lt;p&gt;Go left to right through the weights. If the weight fits, write &lt;code&gt;1&lt;/code&gt; and subtract it. If not, write &lt;code&gt;0&lt;/code&gt;. Example with &lt;code&gt;212&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;212 - 128 = 84  -&amp;gt; 1
84  - 64  = 20  -&amp;gt; 1
20  - 32  = no  -&amp;gt; 0
20  - 16  = 4   -&amp;gt; 1
4   - 8   = no  -&amp;gt; 0
4   - 4   = 0   -&amp;gt; 1
0   - 2   = no  -&amp;gt; 0
0   - 1   = no  -&amp;gt; 0

result: 11010100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another one, &lt;code&gt;13&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;13 - 8 = 5 -&amp;gt; 1 (skip 128,64,32,16)
5  - 4 = 1 -&amp;gt; 1
1  - 2 = no -&amp;gt; 0
1  - 1 = 0 -&amp;gt; 1

result: 1101 (or 00001101 as a full byte)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pad with leading zeros to fill a full byte when you want: &lt;code&gt;13 = 00001101&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;3. Binary → hex: group by 4, then add&lt;/h2&gt;
&lt;p&gt;One hex digit = 4 bits with weights &lt;code&gt;8 4 2 1&lt;/code&gt;. Add the weights, write &lt;code&gt;10-15&lt;/code&gt; as &lt;code&gt;A-F&lt;/code&gt;. No table to memorize.&lt;/p&gt;
&lt;p&gt;Split the bits into groups of 4 from the right, then calculate each group by hand:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;binary 1110 0011 to hexadecimal:

1110 = 0*1 + 1*2 + 1*4 + 1*8 = 2+4+8 = 14 = E
0011 = 1*1 + 1*2 + 0*4 + 0*8 = 1+2 = 3

result = 0xE3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same idea with the running example: &lt;code&gt;1101 = 1*1 + 0*2 + 1*4 + 1*8 = 1+4+8 = 13 = D&lt;/code&gt;, &lt;code&gt;0100 = 0*1 + 0*2 + 1*4 + 0*8 = 4&lt;/code&gt;, so &lt;code&gt;11010100 -&amp;gt; 0xD4&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pad the left with zeros if the length is not a multiple of 4:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0b10111 -&amp;gt; 0001 0111 -&amp;gt; 0x17
0b11111111 -&amp;gt; 1111 1111 -&amp;gt; 0xFF (255)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is why hex is popular: &lt;code&gt;0xFF&lt;/code&gt; is much easier to read than &lt;code&gt;11111111&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;4. Hex → binary: calculate each digit to 4 bits&lt;/h2&gt;
&lt;p&gt;Do the reverse. For each hex digit, subtract the biggest weight that fits (&lt;code&gt;8, 4, 2, 1&lt;/code&gt;) and pad with zeros to 4 bits:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;hexadecimal 0x3F to binary:

3 =
3 - 2 = 1 -&amp;gt; 1
1 - 1 = 0 -&amp;gt; 1
0011 (pad left with 00 to make the nibble)

F (15) =
15 - 8 = 7 -&amp;gt; 1
7 - 4 = 3 -&amp;gt; 1
3 - 2 = 1 -&amp;gt; 1
1 - 1 = 0 -&amp;gt; 1
1111

result = 00111111
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;More, same mechanics:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0xD4 -&amp;gt; D (13) = 8+4+0+1 = 1101, 4 = 0100 -&amp;gt; 11010100
0x2F -&amp;gt; 2 = 0010, F = 1111 -&amp;gt; 00101111 (47 in decimal)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Color codes work the same way: &lt;code&gt;#FF5733&lt;/code&gt; is three bytes &lt;code&gt;0xFF 0x57 0x33&lt;/code&gt;, meaning red=255, green=87, blue=51.&lt;/p&gt;
&lt;p&gt;For longer hex, stick to the binary middle step, it&amp;#39;s less error-prone.&lt;/p&gt;
&lt;h2&gt;Quick full loop&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;255 -&amp;gt; 11111111 -&amp;gt; 1111 1111 -&amp;gt; 0xFF&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;and back:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0xFF -&amp;gt; 11111111 -&amp;gt; 1+2+4+8+16+32+64+128 = 255&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Common beginner mistakes:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Reading &lt;code&gt;0x10&lt;/code&gt; as ten. It is &lt;code&gt;16&lt;/code&gt; (&lt;code&gt;1×16 + 0&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Forgetting to group from the right. &lt;code&gt;0b10111&lt;/code&gt; is &lt;code&gt;0x17&lt;/code&gt;, not &lt;code&gt;0xB7&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Trying to convert long hex to decimal directly. Expand to binary first (direct weights are fine only for 1-2 digits).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once this feels easy, go back to &lt;a href=&quot;/blog/ylhwiwpb&quot;&gt;Information Representation Explained&lt;/a&gt; to see how the same bits become signed integers, floats, text, and files.&lt;/p&gt;
</content:encoded><category>binary</category><category>hexadecimal</category><category>basics</category><category>tutorial</category></item><item><title>Information Representation Explained</title><link>https://www.wesleybertipaglia.com/blog/ylhwiwpb/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/ylhwiwpb/</guid><description>A deep dive into how computers represent everything with bits: binary and hexadecimal, signed and unsigned integers, two’s complement, IEEE 754 floating point, Unicode text, and how raw data becomes meaningful information.</description><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At the lowest level, a computer knows only two things: on and off. Every photo, song, spreadsheet, and video call is, underneath it all, a long sequence of ones and zeros. But a pile of bits means nothing by itself. What gives bits meaning is &lt;strong&gt;representation&lt;/strong&gt;: the agreed-upon conventions that map bit patterns to numbers, text, and richer data structures.&lt;/p&gt;
&lt;p&gt;This post walks through those conventions from the ground up: binary and hexadecimal[^1], unsigned and signed integers, the elegant trick of two&amp;#39;s complement[^2], floating-point numbers[^3], characters and Unicode[^4], and finally how all of it composes into the data structures programs actually use.&lt;/p&gt;
&lt;h2&gt;Bits, binary, and hexadecimal&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;bit&lt;/strong&gt; is a single binary digit: &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt;. Eight bits form a &lt;strong&gt;byte&lt;/strong&gt;, which is the smallest addressable unit of memory on most modern machines. With &lt;code&gt;n&lt;/code&gt; bits you can represent &lt;code&gt;2^n&lt;/code&gt; distinct values: 1 bit gives 2 values, 8 bits give 256, 32 bits give about 4.3 billion.&lt;/p&gt;
&lt;p&gt;Humans rarely read raw bits, so we group them. Binary[^1] is base 2, where each position is a power of two:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1 1 0 1 0 1 0 0
128 + 64 + 0 + 16 + 0 + 4 + 0 + 0 = 212
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hexadecimal[^5] (base 16) exists for one practical reason: a single hex digit maps to exactly four bits (a nibble), so it compresses binary without the mental gymnastics of decimal conversion:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;binary:  1101 0100
hex:     D    4        -&amp;gt; 0xD4 (212 in decimal)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is why you see hex everywhere in computing: memory addresses (&lt;code&gt;0x7ffee4a3b8c0&lt;/code&gt;), color codes (&lt;code&gt;#FF5733&lt;/code&gt;), and byte dumps. The &lt;code&gt;0x&lt;/code&gt; prefix (and &lt;code&gt;0b&lt;/code&gt; for binary, as in &lt;code&gt;0b11010100&lt;/code&gt;) is just a notational convention popularized by C and adopted by many later languages. Whenever you see hex, read it as shorthand for bits, not as a different kind of number.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;New to number base conversions? See the dedicated step-by-step guide: &lt;a href=&quot;/blog/b1nh3xc0n&quot;&gt;Binary, Decimal, and Hex Conversions 101&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;How numbers are stored&lt;/h2&gt;
&lt;p&gt;Numbers are stored as fixed-width bit patterns. The width (&lt;code&gt;uint8&lt;/code&gt;, &lt;code&gt;uint16&lt;/code&gt;, &lt;code&gt;uint32&lt;/code&gt;…) decides the range. Three cases: never negative, possibly negative, and fractional.&lt;/p&gt;
&lt;h3&gt;Unsigned integers&lt;/h3&gt;
&lt;p&gt;Same binary as above. Use it for values that can never be negative: counts, sizes, pixel brightness. Range is &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;2^n - 1&lt;/code&gt; (&lt;code&gt;uint8&lt;/code&gt;: &lt;code&gt;0-255&lt;/code&gt;, &lt;code&gt;uint32&lt;/code&gt;: &lt;code&gt;0-4,294,967,295&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;It wraps like an odometer: &lt;code&gt;uint8&lt;/code&gt; &lt;code&gt;255 + 1 = 0&lt;/code&gt;. Hardware does arithmetic modulo &lt;code&gt;2^n&lt;/code&gt;; languages differ in how they expose overflow.&lt;/p&gt;
&lt;h3&gt;Signed integers&lt;/h3&gt;
&lt;p&gt;For values that can be negative: temperature, balance, offsets. Use &lt;strong&gt;two&amp;#39;s complement&lt;/strong&gt;[^2]: to negate, invert every bit and add one.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;+4  = 0000 0100
flip: 1111 1011
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add one, bit by bit from the right. A bit only holds &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt;, so &lt;code&gt;1 + 1 = 2&lt;/code&gt; does not fit, write &lt;code&gt;0&lt;/code&gt; and carry &lt;code&gt;1&lt;/code&gt; to the left:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;  1111 1011
+ 0000 0001
----------
  1111 1100 = -4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Right to left:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;-&amp;gt; 1 + 1 = 2 -&amp;gt; 0, carry 1
-&amp;gt; 1 + 0 + carry 1 = 2 -&amp;gt; 0, carry 1
-&amp;gt; 0 + 0 + carry 1 = 1
-&amp;gt; 1 + 0 = 1
-&amp;gt; 1 + 0 = 1
-&amp;gt; 1 + 0 = 1
-&amp;gt; 1 + 0 = 1
-&amp;gt; 1 + 0 = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding them gives &lt;code&gt;0000 0100 + 1111 1100 = 1 0000 0000&lt;/code&gt;, the 9th bit vanishes, leaving &lt;code&gt;0&lt;/code&gt;. Same adder works for signed and unsigned. Side effects: 8-bit range is &lt;code&gt;-128&lt;/code&gt; to &lt;code&gt;+127&lt;/code&gt; (asymmetric), single zero, top bit reads as sign (&lt;code&gt;1&lt;/code&gt; = negative).&lt;/p&gt;
&lt;h3&gt;Floating point: IEEE 754&lt;/h3&gt;
&lt;p&gt;Computers can&amp;#39;t store a floating number in a single block of memory, so they split it into 3 parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sign (1 bit)&lt;/li&gt;
&lt;li&gt;exponent (8 bits)&lt;/li&gt;
&lt;li&gt;fraction (23 bits)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So how do we transform a number like 5.75 into binary? We must write it in scientific notation in base 2:&lt;/p&gt;
&lt;h4&gt;1. Decimal to binary&lt;/h4&gt;
&lt;p&gt;Split integer and fractional parts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;5.75
- 5 -&amp;gt; stay 5 = 101 (binary)
- 0.75 -&amp;gt; 0.75 * 2 = 1.5 -&amp;gt; stay 1 = 1 (binary), carry 0.5
- 0.5 -&amp;gt; 0.5 * 2 = 1 -&amp;gt; stay 1 = 1 (binary)

=&amp;gt; 101.11 (binary)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. Normalize to &lt;code&gt;1.xxx * 2^exp&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Simply move the point until there is only a single &lt;code&gt;1&lt;/code&gt; on the left.&lt;/li&gt;
&lt;li&gt;The exponent is how many positions we had to move it. Here 2 positions to the left:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;101.11 = 1.0111 * 2^2, so exponent = 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. Fill the 3 fields&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1. Sign (1 bit) -&amp;gt; The Sign Bit&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Look at the whole number itself (&lt;code&gt;+5.75&lt;/code&gt;). Is it positive or negative?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Positive -&amp;gt; &lt;code&gt;0&lt;/code&gt; (if it were &lt;code&gt;-5.75&lt;/code&gt;, this bit would be &lt;code&gt;1&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Exponent (8 bits) -&amp;gt; The Exponent Field&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You already mastered this! Shift count = &lt;code&gt;2&lt;/code&gt;. Add bias: &lt;code&gt;2 + 127 = 129&lt;/code&gt;. In binary, grouped in nibbles:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;129 = 1000 0001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. Fraction (23 bits) -&amp;gt; The Mantissa Field&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Look at the normalized binary string: &lt;code&gt;1.0111&lt;/code&gt;. Break that string at the dot:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;1.&lt;/code&gt; before the dot: this is the implicit part. It is NOT written into memory.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;0111&lt;/code&gt; after the dot: this is the actual fraction. You take these exact 4 bits and copy them directly into the fraction field: &lt;code&gt;0111&lt;/code&gt;. Then fill the remaining empty slots with zeros to reach 23 bits, grouped in nibbles:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0111 0000 0000 0000 0000 000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Putting it together side-by-side:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Normalized: (+) 1.  0111          * 2^2
             │  │    │                │
             ▼  ▼    ▼                ▼
Field:      Sign[ ] Exponent (+127) Fraction
Stored:      0      1000 0001       01110000000000000000000
                ▲
         (Implicit 1. is dropped!)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Final 32 bits, grouped in nibbles:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;bits: 0100 0000 1011 1000 0000 0000 0000 0000 = 0x40B80000
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Special patterns &amp;amp; practical lessons&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Special values (IEEE 754 reserves):&lt;/strong&gt;[^3]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Exponent all zeros (&lt;code&gt;0000 0000&lt;/code&gt;): signed zero (&lt;code&gt;+0.0&lt;/code&gt; / &lt;code&gt;-0.0&lt;/code&gt;) or subnormals, extremely small values near zero where the implicit leading bit becomes &lt;code&gt;0.&lt;/code&gt; instead of &lt;code&gt;1.&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Exponent all ones (&lt;code&gt;1111 1111&lt;/code&gt;):&lt;ul&gt;
&lt;li&gt;Fraction all zeros -&amp;gt; &lt;strong&gt;Infinity&lt;/strong&gt; (&lt;code&gt;+inf&lt;/code&gt; or &lt;code&gt;-inf&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Fraction non-zero -&amp;gt; &lt;strong&gt;NaN&lt;/strong&gt; (Not a Number, e.g. &lt;code&gt;0.0 / 0.0&lt;/code&gt; or &lt;code&gt;sqrt(-1)&lt;/code&gt;). NaN compares unequal to everything, including itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Two golden rules for developers:&lt;/strong&gt;[^6]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Never compare floats with &lt;code&gt;==&lt;/code&gt;.&lt;/strong&gt; Most decimal fractions (like &lt;code&gt;0.1&lt;/code&gt;) repeat infinitely in binary. &lt;code&gt;0.1 + 0.2&lt;/code&gt; becomes &lt;code&gt;0.30000000000000004&lt;/code&gt;, so exact equality checks fail due to small rounding differences.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Floats are for measurements, not money.&lt;/strong&gt; Rounding error accumulates. Financial systems use integer cents or dedicated decimal types to avoid losing money to floating-point drift. A 64-bit &lt;code&gt;double&lt;/code&gt; (1 sign + 11 exponent + 52 fraction bits) helps, but does not eliminate rounding error.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;How other data is stored&lt;/h2&gt;
&lt;p&gt;Beyond numbers, everything is patterns agreed by convention, like ASCII mapping numbers to letters.&lt;/p&gt;
&lt;h3&gt;Characters and Unicode&lt;/h3&gt;
&lt;p&gt;Text is stored as numbers for letters: the industry agrees which number means which letter.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ASCII&lt;/strong&gt;[^7] (1963) assigned the English alphabet, digits, and control codes to 7-bit numbers &lt;code&gt;0–127&lt;/code&gt;: &lt;code&gt;A&lt;/code&gt; is 65, &lt;code&gt;a&lt;/code&gt; is 97. One byte per character, beautifully simple yet useless for the rest of the world&amp;#39;s writing systems. The following decades produced a chaos of incompatible 8-bit extensions (&amp;quot;code pages&amp;quot;) where the same byte meant &lt;code&gt;é&lt;/code&gt; in one country and &lt;code&gt;Ω&lt;/code&gt; in another.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unicode&lt;/strong&gt;[^4] fixed this by separating two concerns: a universal catalog assigning a permanent number to each entry (a &lt;strong&gt;code point&lt;/strong&gt;, written &lt;code&gt;U+00E9&lt;/code&gt; for &lt;code&gt;é&lt;/code&gt;), and &lt;strong&gt;encodings&lt;/strong&gt; that map code points to bytes. The dominant encoding is &lt;strong&gt;UTF-8&lt;/strong&gt;[^8], and its design is a small masterpiece:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;U+0000–U+007F:  0xxxxxxx                              (ASCII, 1 byte)
U+0080–U+07FF:  110xxxxx 10xxxxxx                     (2 bytes)
U+0800–U+FFFF:  1110xxxx 10xxxxxx 10xxxxxx            (3 bytes)
U+10000–U+10FFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (4 bytes)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;x&lt;/code&gt; bits carry the code point; the leading bits announce the length. Example: &lt;code&gt;é&lt;/code&gt; is &lt;code&gt;U+00E9&lt;/code&gt; = &lt;code&gt;1110 1001&lt;/code&gt; in binary, which fits the 2-byte template as &lt;code&gt;110_00011 10_101001&lt;/code&gt; = bytes &lt;code&gt;0xC3 0xA9&lt;/code&gt;. The euro sign &lt;code&gt;€&lt;/code&gt; (&lt;code&gt;U+20AC&lt;/code&gt;) needs three bytes: &lt;code&gt;0xE2 0x82 0xAC&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Three properties made UTF-8 win: ASCII text is valid UTF-8 unchanged (backward compatible), no ASCII byte can appear as part of a multi-byte sequence, making ASCII delimiters and control bytes easy to recognize, and you can resynchronize mid-stream after corruption.&lt;/p&gt;
&lt;p&gt;(UTF-16[^9] code units are used by Java and JavaScript, and Windows APIs use UTF-16 extensively; it encodes text in 2-byte units with &lt;strong&gt;surrogate pairs&lt;/strong&gt; for characters beyond &lt;code&gt;U+FFFF&lt;/code&gt;, which is why &lt;code&gt;&amp;quot;😀&amp;quot;.length&lt;/code&gt; is 2 in JavaScript.)&lt;/p&gt;
&lt;p&gt;Why did systems like Java, JavaScript, and Windows adopt UTF-16?&lt;/p&gt;
&lt;p&gt;In the early 1990s, Unicode was originally designed as a fixed 16-bit encoding (UCS-2) supporting 65,536 characters (U+0000 to U+FFFF). Engineers believed 16 bits would be enough for all human writing systems forever, offering fast, constant-time indexing.&lt;/p&gt;
&lt;p&gt;When Unicode later expanded beyond 65,536 characters, UCS-2 evolved into UTF-16 using surrogate pairs, but by then, these major platforms were already locked into 16-bit core runtimes.&lt;/p&gt;
&lt;p&gt;Classic text bugs all trace back to this layer: &lt;code&gt;mojibake&lt;/code&gt; (decoding bytes with the wrong encoding), confusing character count with byte count, and assuming one visible glyph equals one code point (accents and emoji can combine several).&lt;/p&gt;
&lt;h3&gt;Representing data: from bytes to meaning&lt;/h3&gt;
&lt;p&gt;Lists, records, images and sound are compositions of the above, plus &lt;strong&gt;endianness&lt;/strong&gt;[^10]: the agreed order in which the bytes of a multi-byte value like &lt;code&gt;0x12345678&lt;/code&gt; are stored in memory. Getting it wrong silently garbles every number, which is why formats like PNG and network byte order conventions specify byte order explicitly.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Little-endian: least-significant-byte-first.&lt;/strong&gt; The value &lt;code&gt;0x12345678&lt;/code&gt; is stored as &lt;code&gt;78 56 34 12&lt;/code&gt;. This is what your personal computer uses: &lt;strong&gt;x86 (Intel/AMD) and ARM&lt;/strong&gt; chips in little-endian mode store multi-byte integers this way in RAM. It is the &amp;quot;host byte order&amp;quot; for most desktops, laptops, and phones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Big-endian: most-significant-byte-first.&lt;/strong&gt; The same value is stored as &lt;code&gt;12 34 56 78&lt;/code&gt;, in the order humans naturally read hex. This is the standard for &lt;strong&gt;network protocols and many file formats&lt;/strong&gt;: TCP/IP declares big-endian as &amp;quot;network byte order&amp;quot;, so every port number, length field, and address traveling over the wire uses it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What happens when you send a message right now? When you type on your little-endian laptop and click send, your bytes must be translated twice:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[ Your Device ]                 [ Network Wire ]                [ Server / Target ]
Little-Endian                   Big-Endian                      Little-Endian
(x86 / ARM)                     (Network Byte Order)            (x86 / ARM)

RAM/CPU                         Network Interface Card          RAM/CPU
0x78 0x56 0x34 0x12 ---htons()--&amp;gt; 0x12 0x34 0x56 0x78 ---ntohs()--&amp;gt; 0x78 0x56 0x34 0x12
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Local processing (your device):&lt;/strong&gt; your CPU builds packet headers (length, port numbers) in little-endian in RAM.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Translation (&lt;code&gt;htons()&lt;/code&gt; / &lt;code&gt;htonl()&lt;/code&gt;):&lt;/strong&gt; before handing data to the network card, software calls &lt;code&gt;htons()&lt;/code&gt; (host-to-network-short) or &lt;code&gt;htonl()&lt;/code&gt; (host-to-network-long). These flip multi-byte integers from little-endian to big-endian.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transmission over the wire:&lt;/strong&gt; the data travels across the internet in big-endian network byte order.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reception and decoding:&lt;/strong&gt; the receiving server calls &lt;code&gt;ntohs()&lt;/code&gt; (network-to-host-short) to flip the bytes back into little-endian so its own x86 or ARM CPU can read the numbers correctly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Common building blocks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Booleans and enums&lt;/strong&gt;: often a single byte holding &lt;code&gt;0&lt;/code&gt;/&lt;code&gt;1&lt;/code&gt; (sometimes packed into bits or using larger units depending on the language and ABI); enums map names to small integers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arrays&lt;/strong&gt;: elements laid out back-to-back; indexing is just &lt;code&gt;base_address + index * element_size&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structs/records&lt;/strong&gt;: fields concatenated, often with &lt;strong&gt;padding&lt;/strong&gt; bytes inserted so each field lands on an aligned address the CPU can read efficiently. A struct&amp;#39;s size is not always the sum of its fields.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strings&lt;/strong&gt;: either length-prefixed (Pascal style: length first, then bytes) or null-terminated (C style: bytes ending in &lt;code&gt;0x00&lt;/code&gt;), the latter being a classic source of buffer overflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Media&lt;/strong&gt;: a pixel is commonly three bytes (red, green, blue) in basic RGB; audio is thousands of amplitude samples per second; video is images plus math that exploits what eyes won&amp;#39;t notice. JPEGs and MP3s are just very clever agreements about which details to throw away.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the deepest lesson of all: &lt;strong&gt;a type is an interpretation, not a property of the bits.&lt;/strong&gt; The 32 bits &lt;code&gt;0x40490FDB&lt;/code&gt; are the float &lt;code&gt;3.1415927&lt;/code&gt; and simultaneously the unsigned integer &lt;code&gt;1078530011&lt;/code&gt;. The bits never changed; only the lens did. Every cast, every &lt;code&gt;reinterpret&lt;/code&gt;, every debugger &amp;quot;view as&amp;quot; command is a reminder that meaning lives in the convention, agreed upon by whoever wrote the bytes and whoever reads them.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Binary_number&quot;&gt;Binary number - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Two%27s_complement&quot;&gt;Two&amp;#39;s complement - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/IEEE_754&quot;&gt;IEEE 754 - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Unicode&quot;&gt;Unicode - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Hexadecimal&quot;&gt;Hexadecimal - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Floating-point_arithmetic&quot;&gt;Floating-point arithmetic - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/ASCII&quot;&gt;ASCII - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/UTF-8&quot;&gt;UTF-8 - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/UTF-16&quot;&gt;UTF-16 - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Endianness&quot;&gt;Endianness - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><enclosure url="https://www.wesleybertipaglia.com//img/cover/e9ir4plc.webp" type="image/webp"/><category>binary</category><category>internals</category><category>computer-architecture</category><category>unicode</category><category>deep-dive</category></item><item><title>The Computer Explained</title><link>https://www.wesleybertipaglia.com/blog/2lfin2nu/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/2lfin2nu/</guid><description>A broad tour of what a computer is and how its main parts work together: processor, memory, storage, input and output, specialized processors, and the software that brings the machine to life.</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A computer is a machine that follows instructions incredibly fast. That is the entire idea. Everything else, from gigahertz and gigabytes to operating systems and apps, is detail piled on top of that one concept. This post stays at the panoramic level: what computation is, what the main parts are, how hardware evolved, and how they cooperate to run a program.&lt;/p&gt;
&lt;h2&gt;What is computation?&lt;/h2&gt;
&lt;p&gt;Strictly speaking, computation[^1] is any type of arithmetic or non-arithmetic calculation that follows a well-defined model or set of steps. Anything or anyone that performs computations, whether mechanical devices, electronic systems, or human beings, is known as a computer[^2]. A problem is considered computable if it is well-defined enough to be solved by an algorithmic process.&lt;/p&gt;
&lt;h2&gt;Computers are everywhere&lt;/h2&gt;
&lt;p&gt;Computers are not just laptops and smartphones. Strictly speaking, a computer is any machine that takes inputs, processes them by following instructions, and produces outputs. A simple calculator, a TV remote, or even a microSD card with its own tiny controller chip inside, all of them take inputs, run stored instructions, and produce outputs. They are computers too, just smaller and single-minded.&lt;/p&gt;
&lt;p&gt;They form the core of general-purpose devices and are the invisible engine that powers the modern internet and global infrastructure.&lt;/p&gt;
&lt;h2&gt;The evolutionary journey&lt;/h2&gt;
&lt;p&gt;Before computers, machines did one fixed job: a loom wove one pattern, a calculator added. The idea of a general, programmable machine predates electronics by a century.&lt;/p&gt;
&lt;p&gt;In the 1830s &lt;strong&gt;Charles Babbage&lt;/strong&gt;[^3], widely regarded as the father of computing, designed the &lt;strong&gt;Analytical Engine&lt;/strong&gt;[^4], a mechanical, program-driven computer with a &amp;quot;store&amp;quot; for data and a &amp;quot;mill&amp;quot; for processing, the direct ancestors of memory and CPU.&lt;/p&gt;
&lt;p&gt;In 1936 &lt;strong&gt;Alan Turing&lt;/strong&gt;[^5] gave that intuition a rigorous foundation with the &lt;strong&gt;universal machine&lt;/strong&gt;[^6], showing that a single device reading different instructions could compute anything computable, the theoretical blueprint of the modern computer.&lt;/p&gt;
&lt;p&gt;In 1937 &lt;strong&gt;Claude Shannon&lt;/strong&gt;[^7] closed the gap between logic and hardware by showing that &lt;strong&gt;Boolean algebra&lt;/strong&gt;[^8] could be implemented directly with electronic switching circuits. Logic was no longer just mathematics on paper: it could be built.&lt;/p&gt;
&lt;p&gt;The breakthrough that turned this theory into working machines is the &lt;strong&gt;stored-program concept&lt;/strong&gt;[^9], formalized as the &lt;strong&gt;von Neumann architecture&lt;/strong&gt;[^10] by John von Neumann[^11] in 1945: one shared memory holding both programs and data, with a processor that fetches instructions from it and executes them. Because a program is stored like data, the same machine can run a spreadsheet, a game, or a web browser, just load different instructions. Simple and general, it remains the blueprint of your laptop and phone.&lt;/p&gt;
&lt;p&gt;From there, the hardware evolved in clear steps: primitive tools to mechanical machines, then vacuum tubes to transistors to integrated circuits, down to today&amp;#39;s &lt;strong&gt;systems-on-a-chip (SoCs)&lt;/strong&gt;[^12], where CPU, memory, graphics, and radio live on a single piece of silicon.&lt;/p&gt;
&lt;h2&gt;The core anatomy of a computer&lt;/h2&gt;
&lt;p&gt;At the architecture level, a computer relies on a synchronized division of labor. Here are the parts, one by one.&lt;/p&gt;
&lt;h3&gt;The processor (CPU)&lt;/h3&gt;
&lt;p&gt;The central processing unit[^13] is the part that executes instructions. At a conceptual level, its work can be described as fetch, decode, execute: grab the next instruction from memory, figure out what it says, do it, then move to the next one, billions of times per second. Real modern cores blur this simple picture with pipelines, superscalar and out-of-order execution, and layers of cache, but the fetch-decode-execute cycle remains a useful conceptual model underneath them.&lt;/p&gt;
&lt;p&gt;Inside, a CPU has a small set of ultra-fast slots called &lt;strong&gt;registers&lt;/strong&gt; for the values it is working on right now, an &lt;strong&gt;arithmetic-logic unit (ALU)&lt;/strong&gt; that does the actual math and comparisons, and a &lt;strong&gt;control unit&lt;/strong&gt; conducting the whole dance. Modern CPUs pack multiple &lt;strong&gt;cores&lt;/strong&gt; (independent workers) and layers of &lt;strong&gt;cache&lt;/strong&gt; (tiny, blazing-fast memory sitting next to the cores) to keep that loop fed. Clock speed, measured in gigahertz, is how many clock cycles occur per second, one factor among many in determining performance.&lt;/p&gt;
&lt;h3&gt;RAM&lt;/h3&gt;
&lt;p&gt;Random-access memory[^14] is the computer&amp;#39;s workbench: fast, temporary storage where running programs and their data live. Memory is addressable, so the CPU can access locations directly, hence &amp;quot;random access.&amp;quot; Direct does not mean instantaneous, though: every access takes time (latency), and that delay is one reason machines surround the CPU with caches.&lt;/p&gt;
&lt;p&gt;Two traits define RAM: it is &lt;strong&gt;fast&lt;/strong&gt; (much faster than persistent storage) and &lt;strong&gt;volatile&lt;/strong&gt; (it forgets everything when the power goes out). That is why unsaved work vanishes in a blackout and why machines with more RAM juggle more apps at once. There is simply a bigger workbench.&lt;/p&gt;
&lt;h3&gt;Storage&lt;/h3&gt;
&lt;p&gt;Storage[^15] is the filing cabinet: slower than RAM but &lt;strong&gt;persistent&lt;/strong&gt;, since it remembers without power. Hard drives keep bits as magnetic patterns on spinning platters; SSDs[^16] store data in NAND flash memory, using electrical charge to represent information. With no moving parts, they are typically faster and quieter.&lt;/p&gt;
&lt;p&gt;The classic tradeoff of computing lives here: registers are among the fastest and tiniest, followed by cache, RAM, and persistent storage such as SSDs and hard drives, each step slower, bigger, and cheaper per byte. Remote storage adds another dimension: network latency. The whole art of performance is keeping the data the CPU needs right now as high up that ladder as possible.&lt;/p&gt;
&lt;h3&gt;Input and output&lt;/h3&gt;
&lt;p&gt;A computer with no senses and no voice would be useless. &lt;strong&gt;Input devices&lt;/strong&gt;[^17] (keyboard, mouse, touchscreen, microphone, camera, network card) bring information into the computer; &lt;strong&gt;output devices&lt;/strong&gt;[^18] (screen, speakers, printer, network card again) carry information back out. The network card is both at once, allowing a machine to communicate without any physical cable connected to it.&lt;/p&gt;
&lt;h3&gt;Software: the machine&amp;#39;s soul&lt;/h3&gt;
&lt;p&gt;Hardware without instructions is an expensive paperweight. &lt;strong&gt;System software&lt;/strong&gt;[^19], above all the operating system[^20] (Linux, Windows, macOS, Android), manages the hardware on everyone else&amp;#39;s behalf: deciding which program gets CPU time, handing out memory, talking to disks and screens through &lt;strong&gt;drivers&lt;/strong&gt;. &lt;strong&gt;Application software&lt;/strong&gt;, like your browser, editor, and games, runs on top, asking the OS for everything instead of touching hardware directly.&lt;/p&gt;
&lt;h3&gt;Specialized processors (GPU)&lt;/h3&gt;
&lt;p&gt;Not every job suits the CPU. Graphics, machine learning, and scientific simulation all share a shape: the same simple operation applied to enormous amounts of data at once. The graphics processing unit[^21] is built for exactly that, with thousands of small, simple cores optimized for throughput, while CPUs are generally optimized for low-latency, general-purpose work and complex control flow.&lt;/p&gt;
&lt;p&gt;GPUs were born to draw triangles and shade pixels, work that is embarrassingly parallel. Programmers then realized the same hardware accelerates anything with that shape, a practice called general-purpose GPU computing[^22]. Today the CPU routinely offloads suitable work to the GPU (and increasingly to NPUs for AI), an arrangement called heterogeneous computing: each processor doing what it does best.&lt;/p&gt;
&lt;h2&gt;From power button to program&lt;/h2&gt;
&lt;p&gt;Press power and the machine wakes up with RAM empty, since RAM is volatile. Firmware stored on the machine runs first, initializes the hardware, finds a bootable device, and loads the operating system&amp;#39;s kernel into memory. The OS starts its services, shows you a login screen, and waits.&lt;/p&gt;
&lt;p&gt;Launch an app and the OS loads its code from storage into memory, points the CPU at its first instruction, and the fetch-decode-execute loop takes over. The app asks the OS to draw windows and read files; the OS talks to the hardware; results flow back as pixels and sound. Billions of instruction cycles per second, all to move a cursor or play a song, and it feels instant.&lt;/p&gt;
&lt;h2&gt;The big picture&lt;/h2&gt;
&lt;p&gt;CPU executes, memory hosts the work in progress, storage remembers, input and output connect to the world, specialized processors accelerate parallel work, and software tells everyone what to do. Change any one of them and the machine changes character, but the division of labor stays the same, from a smartwatch to a supercomputer.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Computation&quot;&gt;Computation - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer&quot;&gt;Computer - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Charles_Babbage&quot;&gt;Charles Babbage - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Analytical_Engine&quot;&gt;Analytical Engine - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Alan_Turing&quot;&gt;Alan Turing - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Universal_Turing_machine&quot;&gt;Universal Turing machine - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Claude_Shannon&quot;&gt;Claude Shannon - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Boolean_algebra&quot;&gt;Boolean algebra - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^9]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Stored-program_computer&quot;&gt;Stored-program computer - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^10]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Von_Neumann_architecture&quot;&gt;Von Neumann architecture - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^11]: &lt;a href=&quot;https://en.wikipedia.org/wiki/John_von_Neumann&quot;&gt;John von Neumann - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^12]: &lt;a href=&quot;https://en.wikipedia.org/wiki/System_on_a_chip&quot;&gt;System on a chip - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^13]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Central_processing_unit&quot;&gt;Central processing unit - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^14]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Random-access_memory&quot;&gt;Random-access memory - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^15]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer_data_storage&quot;&gt;Computer data storage - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^16]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Solid-state_drive&quot;&gt;Solid-state drive - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^17]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Input_device&quot;&gt;Input device - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^18]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Output_device&quot;&gt;Output device - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^19]: &lt;a href=&quot;https://en.wikipedia.org/wiki/System_software&quot;&gt;System software - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^20]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Operating_system&quot;&gt;Operating system - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^21]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Graphics_processing_unit&quot;&gt;Graphics processing unit - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^22]: &lt;a href=&quot;https://en.wikipedia.org/wiki/General-purpose_computing_on_graphics_processing_units&quot;&gt;General-purpose computing on graphics processing units - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><enclosure url="https://www.wesleybertipaglia.com//img/cover/it9hj3o7.webp" type="image/webp"/><category>computer</category><category>hardware</category><category>internals</category><category>how-it-works</category></item><item><title>The Stellar Heritage: From Supernova to Consciousness</title><link>https://www.wesleybertipaglia.com/blog/elyudey1/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/elyudey1/</guid><description>A poetic journey from the death of a star to the birth of life and human curiosity: exploring how the universe forged the planet we call Earth and the spark that led to scientific thought.</description><pubDate>Thu, 23 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Approximately 4.6 billion years ago, the cosmos was a vast stage of darkness and primordial hydrogen. But the silence was about to be broken by creative violence.&lt;/p&gt;
&lt;p&gt;In a vacuum, the death of a colossal star older than our solar system reached its climax. The supernova explosion was not an end, but a distribution of cosmic wealth.[^1]&lt;/p&gt;
&lt;p&gt;When the stellar echo faded, only its ashes remained: a cloud of dust and gas enriched with heavy elements such as silicon, iron, carbon, and nickel. These were the building blocks of matter, forged under extreme pressure and temperature in the core of the dying star. We are, quite literally, stardust.[^2]&lt;/p&gt;
&lt;p&gt;These fragments floated, disorganized, until the silent, omnipresent force of gravity began to organize them. Slowly, grain by grain, the material began to attract, collide, and clump together.&lt;/p&gt;
&lt;p&gt;Most of the mass collapsed to ignite a new star, the Sun.[^3] Around it, the remaining material flattened into a rotating protoplanetary disk, a vast workshop of planetary creation.[^4][^5]&lt;/p&gt;
&lt;p&gt;Within this swirling disk, rocks clashed and fused. Some worlds became distant gas giants, but one, at just the right distance, found the perfect balance of mass, energy, and elements. And we call this world Earth.[^6]&lt;/p&gt;
&lt;p&gt;For hundreds of millions of years, its surface was a brewing inferno. Volcanic storms, meteorite impacts, and relentless heat shaped a wild and violent landscape. Yet beneath this fury, chemistry began to experiment.&lt;/p&gt;
&lt;p&gt;Simple molecules, in primitive broths, began to interact in complex ways. They repeated themselves, persisted... Until, in a sublime and almost unimaginable moment, matter reached a threshold: it learned to self copy. What was inert became alive.[^7][^8]&lt;/p&gt;
&lt;p&gt;This small blue planet, suspended in the vastness of the cosmos, now held an unprecedented promise. The possibility of beings capable not only of surviving, but of evolving to the point of looking up at the night sky and asking the most profound question: &lt;em&gt;Why do we exist?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It is this lineage, this heritage of the stars, that culminates in human curiosity. We are fragments of that ancient supernova which, after 4.6 billion years of journeying, finally gained the ability to look back and understand its own stellar origin.&lt;/p&gt;
&lt;h3&gt;Disclaimer&lt;/h3&gt;
&lt;p&gt;This text is a work of scientific dissemination and poetic appreciation.&lt;/p&gt;
&lt;p&gt;The events narrated here (the formation of the Sun, the Earth, and the origin of life) are based on the most widely accepted scientific models and theories in Astrophysics, Geology, and Biology.&lt;/p&gt;
&lt;p&gt;For narrative and emotional impact, the time and scale of events have been condensed. Science is a continuous process of discovery, and the exact details of our origins remain the subject of intense research. Our goal is to inspire curiosity and wonder about the cosmic journey we are part of.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Supernova&quot;&gt;Supernova - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^2]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Stellar_nucleosynthesis&quot;&gt;Stellar nucleosynthesis - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^3]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Sun&quot;&gt;Sun - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^4]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Protoplanetary_disk&quot;&gt;Protoplanetary disk - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^5]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Formation_and_evolution_of_the_Solar_System&quot;&gt;Formation and evolution of the Solar System - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^6]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Earth&quot;&gt;Earth - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^7]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Abiogenesis&quot;&gt;Abiogenesis - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^8]: &lt;a href=&quot;https://en.wikipedia.org/wiki/Origin_of_life&quot;&gt;Origin of life - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded><enclosure url="https://www.wesleybertipaglia.com//img/cover/elyudey1.webp" type="image/webp"/><category>astronomy</category><category>cosmology</category><category>origin of life</category><category>science</category><category>human curiosity</category></item><item><title>What I Would Do If I Were Starting My Software Engineering Journey Today</title><link>https://www.wesleybertipaglia.com/blog/gmiondcd/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/gmiondcd/</guid><description>A concise guide for aspiring software engineers on building a solid foundation, gaining practical experience, and approaching career growth strategically.</description><pubDate>Fri, 19 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If I could go back in time and restart my coding journey, I&amp;#39;d do things a little differently. Not because I messed up before, but because I’ve since figured out a more chill, yet super effective, way to become a killer software engineer, even if you&amp;#39;re starting from zero.&lt;/p&gt;
&lt;p&gt;My old self would get all hyped up about the latest trendy languages and frameworks, jumping straight into the deep end. My new, wiser self would skip all that noise and build a rock solid foundation first. Think of it like this: I’d rather learn to be a master chef than just a pro at using one specific brand of microwave.&lt;/p&gt;
&lt;h2&gt;Forget the Fads, Learn the Fundamentals&lt;/h2&gt;
&lt;p&gt;When you&amp;#39;re first starting, the temptation is real. You want to learn React, Java Spring Boot, or some other cool, shiny tool. I get it. But that&amp;#39;s like trying to build a skyscraper without knowing how to lay a single brick. I&amp;#39;d do the opposite. I&amp;#39;d get my hands dirty with the stuff that happens &amp;quot;under the hood.&amp;quot;&lt;/p&gt;
&lt;h2&gt;C and C++ Are Your Funky New Friends&lt;/h2&gt;
&lt;p&gt;Don&amp;#39;t let these languages scare you. They&amp;#39;re not just about weird syntax; they&amp;#39;re about boot camp for your brain. They force you to learn low level stuff like memory management and pointers. Why? Because understanding how a computer handles its memory is a superpower. It helps you write lightning fast code and debug problems that would make most developers want to cry.&lt;/p&gt;
&lt;h2&gt;Python for Data Structures &amp;amp; Algorithms (DSA)&lt;/h2&gt;
&lt;p&gt;A lot of new coders run screaming from DSA, but it&amp;#39;s the secret sauce of all great engineers. I&amp;#39;d use Python because its simple syntax lets you focus on the most important part: the logic.&lt;/p&gt;
&lt;p&gt;Think of it like learning to play the guitar. You’re not just training for a future gig; you&amp;#39;re building the finger strength and rhythm that will let you play anything. When you get good at DSA, you start seeing the skeleton of every problem, and that’s when you level up from a &amp;quot;coder&amp;quot; to a true engineer.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A coder knows how to follow a recipe. An engineer understands why the ingredients work together and can invent their own dishes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Do it Yourself&lt;/h2&gt;
&lt;p&gt;Formal education is great, but it&amp;#39;s not the only way to the top. All you really need is discipline and a good dose of curiosity, just get your hands dirty and start building things. The best way to learn is by doing, so dive into projects that challenge you, frustrate you, and ultimately teach you something new, and also have fun while doing it.&lt;/p&gt;
&lt;p&gt;Be careful not to fall into the trap of just building crud project after crud project. Instead, focus on projects that push your boundaries and make you think critically.&lt;/p&gt;
&lt;p&gt;Also, don&amp;#39;t just copy code from tutorials. Try to understand it, tweak it, and make it your own. This is how you truly learn.&lt;/p&gt;
&lt;h2&gt;Pick Your Courses Wisely&lt;/h2&gt;
&lt;p&gt;Don&amp;#39;t just collect online courses like trading cards. Find one that teaches you how to think, not just what to type. A fantastic example is &lt;a href=&quot;https://pll.harvard.edu/course/cs50-introduction-computer-science&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;@Harvard&amp;#39;s CS50&lt;/a&gt;. It won&amp;#39;t hold your hand and teach you a single language. Instead, it throws you into the deep end and forces you to think like a computer scientist.&lt;/p&gt;
&lt;p&gt;But if you are looking for some courses recommendations, here are a few websites where you can find high-quality courses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.edx.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;edX&lt;/a&gt; (free)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.freecodecamp.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;freeCodeCamp&lt;/a&gt; (free)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ocw.mit.edu/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MIT OpenCourseWare&lt;/a&gt; (free)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.coursera.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Coursera&lt;/a&gt; (free and paid)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.udacity.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Udacity&lt;/a&gt; (free and paid)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.udemy.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Udemy&lt;/a&gt; (free and paid)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.khanacademy.org/computing/computer-science&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Khan Academy&lt;/a&gt; (free)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And heres a few websites where you can learn and practice your skills:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GeeksforGeeks&lt;/a&gt; (free)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;LeetCode&lt;/a&gt; (free)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.hackerrank.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;HackerRank&lt;/a&gt; (free)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.codewars.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Codewars&lt;/a&gt; (free)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.codechef.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;CodeChef&lt;/a&gt; (free)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Build Projects That Actually Solve a Problem&lt;/h2&gt;
&lt;p&gt;My biggest rookie mistake was building generic &amp;quot;portfolio projects&amp;quot; that nobody needed. The smart way to do it is to solve a real problem for yourself or someone you know.&lt;/p&gt;
&lt;p&gt;Need to organize your mess of downloads? Write a script for it. Can&amp;#39;t find the best deals online? Build a price tracker. The project doesn&amp;#39;t have to be a masterpiece. The real magic is in the messy process: planning, building, failing, yelling at your screen, and then finally getting it to work.&lt;/p&gt;
&lt;p&gt;Reading about swimming is one thing. Actually jumping in the pool is what gets you wet (and makes you a swimmer).&lt;/p&gt;
&lt;h2&gt;Your Portfolio Is Your Story&lt;/h2&gt;
&lt;p&gt;Your GitHub profile shouldn&amp;#39;t just be a graveyard of old projects; it should be a comic book of your journey, showing all your battles and victories.&lt;/p&gt;
&lt;h2&gt;Use Your README.md Like a Storybook&lt;/h2&gt;
&lt;p&gt;For every project, write a solid &lt;code&gt;README.md&lt;/code&gt;. Explain the problem you were solving, the choices you made, and the challenges you faced. This isn&amp;#39;t just showing off your code; it&amp;#39;s showing off your brain. It tells a potential employer you&amp;#39;re not just a code monkey; you&amp;#39;re a thinker.&lt;/p&gt;
&lt;h2&gt;Try to Contribute to Open Source&lt;/h2&gt;
&lt;p&gt;Instead of just building things from scratch, find an open-source project and contribute. It&amp;#39;s like joining a band instead of just playing in your bedroom. You learn how to work with a team, navigate real-world codebases, and follow professional quality standards.&lt;/p&gt;
&lt;p&gt;A great portfolio doesn&amp;#39;t just show that you can code; it shows you can think like an engineer.&lt;/p&gt;
&lt;h2&gt;How to Get Your First Gig Without Losing Your Mind&lt;/h2&gt;
&lt;p&gt;Everyone thinks they need to be 100% ready before they start applying for jobs. That&amp;#39;s a myth. The job hunt itself is one of the best ways to learn.&lt;/p&gt;
&lt;h2&gt;Hunt for Learning Opportunities, Not Just Paychecks&lt;/h2&gt;
&lt;p&gt;Skip the &amp;quot;dream job&amp;quot; fantasy for now. Instead, focus on internships and trainee programs. These positions are designed for you to grow. The mentorship and hands-on experience are worth way more than a high starting salary.&lt;/p&gt;
&lt;h2&gt;Learn to Be Your Own Hero&lt;/h2&gt;
&lt;p&gt;In the age of AI, the most valuable skill you can have is the ability to figure things out on your own. Before you run to Stack Overflow or ChatGPT for help, try to debug the problem yourself. This doesn&amp;#39;t mean you have to work in isolation; it means you&amp;#39;re training your brain to be resourceful and independent.&lt;/p&gt;
&lt;h2&gt;Final Thoughts (The Pep Talk)&lt;/h2&gt;
&lt;p&gt;Every mistake is a lesson. Every interview is a practice round. Every line of code you write is a step forward.&lt;/p&gt;
&lt;p&gt;Think about the trade-offs of every decision you make. Why did you use that data structure? Why did you choose that API architecture? Thinking about these choices early on is what forges an engineering mind.&lt;/p&gt;
&lt;p&gt;If you’re just starting, remember this: the path to becoming a great software engineer isn&amp;#39;t a race. It&amp;#39;s more like growing a tree. You don&amp;#39;t get fruit overnight, but if you keep watering it, it’ll grow strong roots that will support you for your entire career.&lt;/p&gt;
</content:encoded><category>swe</category><category>career</category><category>advice</category></item><item><title>Who I Am &amp; Why This Blog Exists</title><link>https://www.wesleybertipaglia.com/blog/hozy8ovp/</link><guid isPermaLink="true">https://www.wesleybertipaglia.com/blog/hozy8ovp/</guid><description>A personal journey into programming, learning, and growth. Sharing lessons learned and advice for aspiring developers.</description><pubDate>Thu, 18 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello! First of all, thank you for your time and curiosity. This post marks the restart of a blog I began a few years ago, and my goal is to share the journey, the mistakes, and the lessons that brought me here.&lt;/p&gt;
&lt;p&gt;From the beginning, I’ve identified as a generalist programmer. My focus has always been on computational thinking rather than a specific language, which allowed me to learn new technologies quickly. I was driven by curiosity, how do computers really work? Who gives the orders? How does a machine know what to do when you press a button? These questions pushed me to truly understand.&lt;/p&gt;
&lt;p&gt;I started my journey by learning C and naturally moved on to C++, which gave me a solid foundation to understand pointers, garbage collection, and how high-level languages work &amp;quot;under the hood.&amp;quot; Later, learning Java or Python became much easier. However, at some point, I got lost in the avalanche of technologies and frameworks. In the rush to deliver quick solutions and attract recruiters, I skipped fundamental steps, building monolithic CRUD applications using MVC architecture. It was fun, but it left significant gaps in my knowledge.&lt;/p&gt;
&lt;p&gt;Without professional experience, entering the job market was tough. So I decided to pursue a three-year Information Technology degree, highly rated by the Brazilian Ministry of Education, with 90% employability and conveniently located near my home. The course felt like it was made for me and it was truly transformative. Subjects like calculus, algorithms, computer architecture, and networks gave me the depth I was missing. I built a home lab with an Ubuntu server to test and apply everything I learned in practice. From databases to Python applications, everything ran on my mini cloud.&lt;/p&gt;
&lt;p&gt;It was in this academic environment that a fundamental lesson shaped my path. I had an extraordinary and visionary professor who taught me more than just code or math, he taught me to question concepts, to seek the &amp;quot;whys&amp;quot; behind things, and to challenge what I thought I knew. That mindset changed the way I study, code, and face challenges. Digging into the fundamentals became my main tool.&lt;/p&gt;
&lt;p&gt;When I started applying for internships, this foundation proved essential. The market demanded technologies I didn’t yet master, like .NET, Spring Boot, and Node.js. I dove into studying, built projects to demonstrate my potential, and eventually landed a position as a C# developer. My solid foundation allowed me to learn quickly, deliver results, and earn my team&amp;#39;s trust. It was proof that understanding the core concepts makes any technology accessible.&lt;/p&gt;
&lt;p&gt;Despite the progress, I acknowledge that for a long time I spread myself too thin trying to learn a bit of everything without enough depth. That cost me some great opportunities during college, where I went through more than 20 technical interviews that could have changed my life. Unfortunately, I wasn’t at the level they required. Today, this restart is a commitment: to study in depth, to understand trade-offs, and to stop skipping steps. Decisions like whether to use a relational database or not, or whether to choose an event-driven architecture, are what shape a software engineer&amp;#39;s career.&lt;/p&gt;
&lt;p&gt;If you’re just starting out, here are some pieces of advice I consider valuable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Learn C and C++&lt;/strong&gt;: The foundation these languages provide is unmatched.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Master Data Structures and Algorithms (DSA)&lt;/strong&gt;: Use Python to speed up the implementation of your solutions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invest in formal education&lt;/strong&gt;: If possible, a degree in Computer Science or a related field makes a huge difference.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pursue internships early&lt;/strong&gt;: Apply to companies you admire. Send lots of applications and take advantage of opportunities as soon as you can.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don’t give up&lt;/strong&gt;: The journey is tough, but the rewards are immense.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Remember: your career is a marathon, not a 100 meter sprint. There will be moments of doubt and frustration, and the temptation to take shortcuts will be constant. In those moments, return to the fundamentals and reconnect with the curiosity that made you start. Every challenge you overcome not only adds a line to your résumé, but also strengthens the engineer you are becoming.&lt;/p&gt;
&lt;p&gt;This is my restart, and I hope that by sharing my journey, we can learn and grow together. Let’s move forward.&lt;/p&gt;
</content:encoded><category>swe</category><category>career</category><category>advice</category></item></channel></rss>