Author Archives: Mark

About Mark

Ten years into my journey towards becoming a pro systems programmer, sharing what I learn along the way. Also on Twitter: @offlinemark.

If you're reading this, I'd love to meet you. Please email mark@offlinemark.com and introduce yourself!

x86 kernel development lab notes

Here’s what I know about x86 kernel development. The usual caveat applies for my lab notes: this is not considered a high quality document and there may be inaccuracies.


Main Processor Modes

  • Real Mode (16 bit)
    • CPU boots into this mode for backward compatibility
    • The IDT is instead the IVT here (Interrupt Vector Table)
    • Legacy BIOS booting begins here โ€” the BIOS loads the first sector of disk into memory at a fixed address and begins executing it in Real Mode.
  • Protected Mode (32 bit)
    • Segmentation is mandatory โ€” a minimal GDT is necessary.
    • Paging is not mandatory
  • Long Mode (64 bit)
    • Paging is mandatory

Segmentation

  • Originally solved the problem of CPUs having more physical memory than could be addressed with 16 bit registers. (Note, this is the opposite situation of what we have today where virtual address spaces are vastly greater than physical ones)
  • Introduces the concept of “segments”, which are variable length “windows” into a larger address space.

Data structures

GDT (Global Descriptor Table)

  • Contains “descriptors” to describe the memory segments (“windows”) available. Segment register contain effectively an index into this table.
  • There are “normal” descriptors which describe memory segments and “system” descriptors which point to more exotic things, like Task State Segments (TSS) or Local Descriptor Tables (LDT)
  • These days OSs use the GDT as little as possible, only as much as strictly necessary. On 32 bit, this looks like 4 entries that start at base 0x0, and cover the entire 32 bit space. 2 for kernel, 2 for user โ€” 1 for code, 1 for data for each. (?)
  • On 64 bit GDT is totally unused (I believe?), as are nearly all segment registers(?), except FS and GS. (Why are they special? There is even a special MSR for GS?)

LDT (Local Descriptor Table)

  • My understanding is LDTs are really no longer used by nearly any OS. Some parts of segmentation are still required by OSs, like the GDT, but LDT is not required and almost completely unused in modern OSs.
  • These would contain segments only accessible to a single task, unlike the regions in the GDT (?)

IDT (Interrupt Descriptor Table)

  • Interrupts: Generally externally triggered, i.e. from hardware devices
  • Exceptions: Internally generated, I.e. division by zero exception, or software breakpoint
  • When the processor receives an interrupt or exception, it handles that by executing code โ€” interrupt handler routines.
  • These routines are registered via the IDT โ€” an array of descriptors that describes how to handle a particular interrupt.
  • Interrupts/exceptions have numbers which directly map to entries in the IDT.
  • IDT descriptors are a polymorphic structure โ€” there are several kinds of entities: interrupt, trap, and task gates (maybe others – call gates?).
  • Interrupt/trap gates are nearly identical and differ only in their handling of the interrupt flag. They contain a pointer to code to execute. This is expressed via a segment/offset.
  • Task gates make use of HW task switching and offer a more “turnkey” solution for running code in a separate context when an interrupt happens โ€” but generally aren’t used for other reasons (?). Context switch is automatic?
  • Task gates in the IDT point to a TSS descriptor in the GDT, which points to a TSS (?)
  • Some interrupt/exceptions have an associated error code, some don’t.
  • Interrupt gates describe a minimal privilege level required to trigger then with an int instruction โ€” this prevents userspace from triggering arbitrary interrupts with int. If userspace tries to trigger an int without permission, that is converted in to a General Protection fault (#GP)

Hardware task switching

  • Although long considered obsolete in favor of software task/context switching, x86 provides significant facilities for modeling OS “tasks” at the hardware level, and including functionality for automatic context switching between them.
  • Hardware task switching may require copying much more machine state than is necessary. Software context switches can be optimized to copy less and be faster, which is one reason why they’re preferred.
  • Hardware task switching puts a fixed limit on the number of tasks in the system (?)

TSS (Task State Segment)

  • This is a large structure modeling a “task” (thread of execution)
  • Contains space for registers & execution context
  • Even if HW task switching is not used, one TSS is still needed as the single HW Task running on the system, which internally implements all software context switching
  • TSS is minimally used for stack switches when handling interrupts across privilege levels โ€” when switching from userspace to kernel during interrupt, kernel stack is taken from TSS
  • Linux task_struct is probably named with “task” due to being original created for i386
  • The Task Register (TR) contains a descriptor pointing to the current active HW task (?)

The JOS boot process

JOS is the OS used in MIT 6.828 (2018).

Bootloader

  • JOS includes a small BIOS bootloader in addition to the kernel
  • The bootloader begins with typical 16 bit Real Mode assembly to do the typical steps to initialize the CPU (Set A20 line, etc)
  • Transition to protected mode
  • Set the stack immediately at the start of the code, and transition to C
  • The kernel is loaded from disk using Port IO
  • Loaded into physical memory around the 1MB mark, which is generally considered a “safe” area to load into. (Below the 1MB mark has various regions where devices, BIOS data, or other “things” reside and it’s best to not clobber them.)
  • Call into the kernel entrypoint

Early kernel boot

  • Receive control from the bootloader in protected mode
  • Transition to paging
  • The kernel is linked to run in high memory, starting at 0xf000000 (KERNBASE)
  • The transition from segmentation to paging virtual memory happens in a few steps. There’s first an initial basic transition using set of minimal page tables.
  • After that transition is made, a basic memory allocator is set up, which is then used to allocate memory for the production page tables which implement the production virtual memory layout used for the rest of runtime.
  • The minimal page tables contain two mappings:
    • 1 – Identity map the first 4MB to itself
    • 2 – Map the 4MB region starting at KERNBASE also to the first 4MB
  • One page directory entry maps a 4MB region, so only two page directory entries are needed
  • These page tables are constructed statically at compile time
  • The first identity mapping is critical because without it the kernel would crash immediate after loading CR3, because the next instruction would be unmapped. The identity mapping allows the low mem addresses the kernel resides in to remain valid until the kernel can jump to high mem
  • The assembly there looks a bit strange because the jump appears redundant. But all the asm labels are linked using highmem addresses, so jumping to them transitions from executing in low mem, to executing in high mem, where the kernel will remain executing for the rest of its lifetime.
  • Set the stack pointer to a global data/BSS section of internal storage within the kernel and enter C code

Memory allocators

  • The goal is transition to a production virtual memory setup
  • This requires allocating memory for page tables
  • To build the dynamic page/frame allocator, we start with a basic bump allocator
  • It starts allocating simply from the end of the kernel in memory. We have access to a symbol for the end of the kernel via a linker script.
  • Kernel queries the physical memory size of the system and dynamically allocates data structures for the dynamic page/frame allocator. This is an array of structure that correspond to each available frame of physical memory. These structures have an embedded linked list pointer (intrusive linked list) and a refcount. They are linked together into a linked list, to implement a stack data structure where frames can be popped (when allocating) and pushed (when freeing).
  • Using this frame allocator, pages for the production page tables are allocated.

You don’t even need to be successful

Something I’ve learned through my streaming project is that you don’t even need to be super “successful” at what you’re doing to build an audience of people that are excited for you and want to support you.

Based on my experience, you just need to be:

  • trying hard
  • at something hard
  • consistently
  • in public

I wouldn’t say I’ve been so “successful” at building an OS. It currently doesn’t even really boot or do anything yet. It does not support running programs at all. All it can do is kind of initialize the hardware and slowly initialize itself to the point where it’s almost ready to run programs. (And I didn’t even write a lot of that code. A lot of it was provided by the base foundation for the course I’m following.)

And none of that matters. People are still excited about what I’m doing, even though it’s not novel in the slightest, and I’m not that “successful”. What matters is simply that I’ve been trying, a lot, and talking about it.

Writing is like exercise

Sometimes I struggle to justify why I spend time writing for my blog. Here’s the argument I keep coming back to:

A regular writing habit is just like a regular exercise habit for your brain. Yes, it takes time, energy, and money, but it’s also good for you, and well worth it.

Writing and especially publishing it is just like going to the gym for your brain. Plus it increases your luck surface area and helps keep you young. So, well worth the investment.

6 months of live-streaming

I’ve been live-streaming weekly for six months now, and I’ve noticed a virtuous flywheel develop.

  • Publicly committing to doing this activity creates pressure to follow through and do it
  • The more I do it, the more of a streak develops
  • The more the streak that develops, the more I want to keep it up and not break it (Especially if I’m very public about the streak)
  • The more I want to keep it up and not break it, the more I prioritize it
  • The more I prioritize it, the more importance it gets in my schedule, and other things are scheduled around it

This has been a powerful cycle to harness because the activity (learning OS development) is deeply aligned with my interests and aspirations โ€” so I’ve effectively designed a system that provides positive pressure towards doing something good for me.

WIP: Humans need variety

I believe this is a basic insight at the core of many aspects of human life. For example:

  • Variety of diet
  • Variety of physical positions/movement (i.e. Why you shouldn’t sit all day, or why it’s good to exercise)
  • Variety of physical location (i.e. Why 100% remote work is difficult for many people)
  • Variety of daily experience (i.e. Why people travel to other places for vacation)
  • Variety of people to be around (i.e. Why people can get annoyed with each other if they’re constantly around each other too much – like families)
  • Variety of daily occupation (i.e. Why people switch jobs every few years)

How I live-stream programming

I’ve been live-streaming myself doing operating systems programming every week for 6 months now. This is not meant to be a comprehensive guide on streaming, just an overview of how I do it.

The bare basics

I stream to Youtube using OBS. I use Youtube because:

  • It automatically archives the streams indefinitely to your channel by default. (Twitch only keeps the video for a limited time IIRC).
  • It exposes you to all of Youtube for potential viewership.

I tried Twitch once but didn’t get many viewers, so I stopped trying there.

A bit more (chat widget, alert boxes, chatbot)

I use Streamlabs’ free plan for my Chat Widget, Alert Boxes, and Chatbot. It works very well for a free offering and these add some more flair and professionalism to your stream.

  • Chat Widget: The messages in the chat are rendered directly into the stream video. The chat is an important part of the streaming experience, and it would be a shame to lose those messages. This ensure they are at least captured in the video.
  • Alert Boxes: These are visual effects that show in the stream when someone subscribes or does other actions.
  • Chatbot: I have a few chatbot commands for answering common questions about my tools, discord, or recommended sources. The chatbot is also useful for moderating the chat and e.g. restricting offensive language.

After using a basic OBS setup for a while, I customized it with a text box at the top that gives viewers a quick sense of what I do (“Streaming OS/Kernel Dev, Assembly & Low Level Programming”), the topic of today’s stream, and a friendly invitation to engage in the chat.

Misc tips

I used this plugin for background removal for that streamer green-screen effect (without a green-screen). It’s quality is just ok. I eventually stopped using it and just show my background.

  • After streaming to Youtube, be careful to not use their built in trimming tools because this removes the chat replay from the video. I used to press stream, then trim the beginning, but now I press stream and immediately start to preserve the native Youtube replay.
  • Resolution โ€” Increase your screen resolution so people can read the text more easily. On my monitor, I reduce the resolution to 720p and live with the fact that it’s comically big for me when I stream.
  • Music โ€” I use a 3 hour LoFi hip hop video on Youtube that explicitly claims to be No Copyright. I have OBS set to do a macOS capture which records audio. I then play it, and actually mute my laptop so I don’t hear it (it distracts me when programming) but it gets recorded in the stream.
  • Multi-streaming โ€” You can multi-stream to Youtube and Twitch with paid Streamlabs, but there are other ways to do it, apparently even including using ffmpeg locally.
  • Youtube Thumbnails โ€” I use CapCut, which is perfect for this. I took a screenshot of one of my streams, loaded it in CapCut, then overlay a few text objects and images on top. CapCut paid version also conveniently can remove backgrounds from people and give a colored border for that authentic Youtuber effect.

WIP: We grow old because we stop making art

We don’t stop playing because we grow old; we grow old because we stop playing.

George Bernard Shaw

I’d like to offer a variation of this.

We don’t stop making art because we grow old; we grow old because we stop making art.

Children are naturally curious, creative, and artistic. They freely draw, sing, and ask questions. At the core is a youthful fearlessness. They’re not afraid โ€” of being judged or looking stupid (yet).

We lose this as we grow older. We become concerned with appearances, and learn to avoid actions that might cause us to be judged or look stupid. We become afraid.

There is something deeply healthy about engaging in a creative practice, that connects us back to this youthful fearlessness. Just like how a personal fitness practice is essential for maintaining physical function despite the natural progression of entropy, a personal creative practice is essential for resisting the tendency to become fearful.

Where do you feel creative? At a piano? Taking photos? Writing words? Cooking? Working out? Playing sports? Dancing? Look more closely โ€” that might be your fountain of youth.

To have good ideas, stop auto-rejecting your ideas

Half of having good ideas is not immediately rejecting the ideas you do have, but rather allowing yourself to respect them, give them the chance, and even consider them as worth sharing.


This is just an observation of my own shift in mental state over the last few years. I don’t consider myself particularly smart or insightful, compared to all those “wise” people with “famous quotes”.

But I’ve found that releasing myself from this automatic “self-doubt instinct” has led to a more nurturing mental space where weak, fledgling ideas have the space to potentially grow into stronger ones. And that is what eventually leads to genuinely amazing, novel ideas.

At least, I hope. We’ll see if I have one one day.

Creative pitfall: You don’t need to innovate in every area

I think a massive creative pitfall beginners make is subconsciously assuming they need to innovate in many areas, at the same time.

For example, to get started making music, a budding musician might assume she needs to:

  • 1 – Write her own lyrics
  • 2 – Write her own vocal melody
  • 3 – Write her own chord progression
  • 4 – Write her own instrumental melody
  • 5 – Write her own song arrangement

…which sounds like a tremendous amount of work! (And it is!)

But that’s not accurate assessment of the effort needed to complete the goal (make a song). It’s actually an assessment of the upper bound of the effort.

In reality, you can (and should) “steal like an artist”:

  • 1 – Use your favorite song’s lyrics
  • 2 – Use your favorite song’s vocal melody
  • 3 – Use your favorite song’s chord progression
  • 4 – Use your favorite song’s instrumental melody
  • 5 – Use your favorite song’s arrangement

(You will probably also do this subconsciously anyway, as you draw from your influences.)

Of course, these vary in terms of how acceptable they are to “ship in production” without giving credit to the original. #1 should always be done with credit, and is usually considered “Doing a cover” song. On the other hand #3 is 100% fair game to use, and release with no credit. There are only so many chord progressions actually used in popular music.

However, provided you give credit where it’s due, as you mix and match these options, you can explore the creative space of tradeoffs between originality and effort.

The lower bound is “stealing” on every dimension possible, which produces the platonic ideal of a “Cover Song”. There is nothing wrong with this, and this is how virtually every artist in the world gets started. (e.g. Justin Bieber)

At this level of your journey, the absolute most important metric to optimize (lower) is the friction & effort of doing the craft. Only once you’ve built your creative muscle stronger, can you tolerate more strenuous artistic projects that explore different parts of the effort vs originality space.

This concept applies to any other creative practice, including starting a business or learning to code. In fact, the inspiration for this idea was this Indie Hackers interview with Michael Seibel of Y Combinator, where he advises startups to select where they innovate.

It’s not enough to have good ideas

You also need to present them well.

This is one of the things I’ve learned on Twitter in my decade using it. On Twitter, the slightest change of phrasing can make or break a tweet. Part of learning the game is learning how to express information in a way that’s more likely to be received well. Usually that means being a combination of: valuable, concise, and punchy.

The general lesson is that you need to be aware of the venue, and its communication norms. What is the communication style is common, and commonly successful?

This applies not only to online platforms, but also real life communities you may be a part of, such as your work.

So if your ideas aren’t landing how you’d like them to, or you believe a great idea you have is being unfairly overlooked by others, take a moment to pause. How do the successful people in the venue communicate? And are there any ways you could restructure your presentation to better fit the venue? You might be surprised at how little it takes.