This page looks best with JavaScript enabled

MercuryOS Memory Segmentation in 16-bit Real Mode

 ·  ๐ŸŽƒ kr0m

In this article, we will learn how a microprocessor works in its earliest boot phase (16-bit real mode). We will also understand why a microprocessor with 16-bit registers is not capable of addressing more than 64KB of RAM and how to increase this limit to 1MB thanks to memory segmentation.

Before we begin, it is recommended that you read these previous articles:


When processors start up, they behave like an 8086 in 16-bit real mode. It will be the operating system that prepares the equipment to enter protected mode and thus access all resources, protection mechanisms, etc., abandoning compatibility with processors prior to the 386.

In 16-bit real mode, the registers are 16 bits, and to access a memory address, we must store that address in a register. Since the registers are 16 bits, the maximum value of memory that we can access is 2^16=64k positions.

To solve this problem, CPU designers added special registers called segment registers.

  • cs: CodeSegment
  • ds: DataSegment
  • ss: StackSegment
  • es: ExtraSegment

The segment acts as an index, and the RAM is divided into segments of 64k. By combining the segment position with the offset, we obtain the desired memory address.

Let’s take an example:

mov dx,[0]

In this case, we copy what is in memory address 0 to the dx register, but from which segment? In this case, it is ds since it is the default segment when using absolute addressing, this is explained further in this same article.

If we need to access another segment, we must indicate it with a prefix:

mov dx,[es:0]

This addressing mode is called Absolute, there are several ways to indicate the offset, such as based on the value of a register, depending on the register we use to indicate the offset, the default segment will be one or the other:

Name Offset Default segment
Absolute Immediate value ds
Indirect with base bx+x ds
bp+x ss
Indirect with index di+x ds
si+x ds
Base and index bx+DI+x ds
bx+SI+x ds
bp+DI+x ss
bp+SI+x ss

In the following example, we are going to move what is in the es segment with offset the address that is in bp+100:

mov al,[es:bp+100h]

In this example, we have used the bp+x addressing mode, so the default segment is ss, we had to indicate the prefix to change it.

NOTE: In general, it is not a good idea to use segment prefixes, as the instructions that use them are encoded in more bytes and therefore are slower.

The absolute memory address is calculated by multiplying the segment value by 16 and adding the offset:

mov ds, [0x4d]  
mov ax, [0x20]

ax would have the value:

(16 * 0x4d) + 0x20

If we calculate the highest possible address using this system, it gives us 1MB of addressable memory:

(16 * 0xffff) + 0xffff = 1MB

NOTE: When working with segment registers, it is important to remember that we cannot move literals directly to them, we have to go through some intermediate general-purpose registers.

In this previous article, we had already used segmentation although it had not been detailed, the segment where the BIOS had copied our code was 0x7c00.

Let’s see an example where we use absolute addressing (Table: Default Segment DS) to different memory addresses to print their contents on the screen.

View boot_sect_segmentation.asm
mov ah, 0x0e; tty mode

mov al, [the_secret]; we are calculating the address without beeing aware of our code segment
int 0x10

mov bx, 0x7c0; we copy segment value to a general purpose register
mov ds, bx; in that way we can copy segment value from general purpose register to segment register
mov al, [the_secret]
int 0x10

mov al, [es:the_secret]; we use prefix segment annotation, es has an unknown value so memory address can be any value
int 0x10

mov bx, 0x7c0; we do the same as second try but we use es instead of ds as segment register and use prefix annotation
mov es, bx
mov al, [es:the_secret]
int 0x10


jmp $

the_secret:
    db "X"

times 510 - ($-$$) db 0
dw 0xaa55

We generate the image:

nasm -f bin boot_sect_segmentation.asm -o boot_sect_segmentation.bin

We load it into Qemu:

qemu-system-x86_64 boot_sect_segmentation.bin

SeaBIOS (version rel-1.12.1-0-ga5cab58e9a3f-prebuilt.qemu.org)  
iPXE (http://ipxe.org) 00:03.0 C980 PCI2.10 PnP PMM+07F91410+07EF1410 C980  
  
Booting from Hard Disk...  
รงXรงX

As expected, only the second and fourth attempts to print the character have worked.

If you liked the article, you can treat me to a RedBull here