Memory segmentation Once upon a time, Intel introduced the 8086 microprocessor, which gave birth to the X86 family of microprocessors. It was a 16 bit processor. This means that all registers are 16 bits wide and officially, the memory bus is too. In theory, that would allow for a memory address space of only 65.
A CPU has its instruction set and deals exclusively with machine language instructions, which are nothing more than unique bitpatterns that get routed to the right place for action.
Writing in machine language directly is possible but a very tedious excercise. It is why at some point the assembly language was invented, which abstracts away some repetitive patterns and attempts at making code a bit more human readable.
What better way to learn the tools than through a “Hello world!” classic? Here we go:
; hello.asm
bits 32
SECTION .data
hello db 'Hello world!', 10
len equ $-hello
SECTION .text
GLOBAL _start
_start:
; Print message
mov eax, 4 ; 4 == "print"
mov ebx, 1 ; standard output (stdout)
mov ecx, hello ; message to print
mov edx, len ; number of bytes to print
int 80h ; make the system call
; Return cleanly
mov eax, 1 ; 1 == "exit"
mov ebx, 0 ; exit code 0 means success
int 80h ; make the system call
Some specifics about this sample that we did not cover yet in the previous chapter:
When a PC is powered on, the first thing that happens is the Power-On Self Test (POST). This is carried out by the BIOS (Basic Input/Output System) and during this process, system memory (RAM) is identified, other vital components are validated and the integrity of the BIOS code itself is checked.
Now that we have our basic bootloader ready, we can focus on the last missing piece: our actual kernel. Our bootloader so far only prints a message, so we need to alter it to make it load our kernel instead. We can do that by using another BIOS interrupt call like we did for printing the message: 0x13.
There is one final important step to know about before you start working on your real kernel: protected mode!
The easiest thing to do now is download the kernel.asm now and have it open in a text editor while you read this part.
In this series, I will try to summarize the most fundamental parts of a computer and the physics behind them. While there is much more to say about them than I will here, I try to stay concise and to the point within the context of programming.
As described in the previous part, in computers a transistor acts as a switch and either lets electricity through or it doesn’t. Being able to conditionally let electricity through, means you can work with two discrete values: electricity and no electricity. This literally translates directly to a bit: 1 means electricity (typically +3.
The Arithmetic & Logic Unit (ALU) is a component that combines multiple smaller components through hardwired circuits, each carrying out a specific operation. They are all related to logic and calculation (hence the name). The idea is that the ALU is designed to carry out only simple operations, but because it can do so many in a very short time frame, more complex logic can be carried out quickly.
To move data between registers, we already learned that we need to enable the source register on the bus, then set the target register so the value gets stored there, then unset the target register to disconnect it from the bus and then disable the source register.