I'm trying to understand memory models, specifically segmented and flat. Most of the reference material is too technical, it seems however, that segmentation can be used to "expand" the memory but what does that mean and how is it accomplished? Also, when using the segmented memory model, why could use only address 64KB at once?
2 Answers
why could use only address 64KB at once?
Because that's how much you can address with a 16-bit offset into a segment.
x86 segmentation in real mode is just linear address = segreg<<4 + offset
, where offset is a regular addressing mode like [bx + di]
.
A linear address is a physical address if paging is disabled, otherwise it's a virtual address to be translated by the page tables pointed to by CR3.
With a 32-bit addressing mode, you can access 4GiB relative to a segment. Protected mode uses segments as indexes into a table of descriptors, rather than as a base value directly, so you can have large segments and large bases. (But at least with paging enabled, it's all mapped to the same 32-bit linear address space, so you can't use segmentation to have a 32-bit OS expose more than 4GiB of memory to a user-space process, even with segments with different bases and 4GiB limits.)
Using different values in segment registers allows accessing more memory, just not all at once which makes it really inconvenient for languages like C where "a pointer" is just a pointer. You have to introduce "far pointer" vs. "near pointer".

- 328,167
- 45
- 605
- 847
-
Could you explain the purpose of the CR3 register? the wikipedia article is too succinct – Trey Oct 20 '17 at 06:01
-
1@Trey: it points to the top level page directory. See https://stackoverflow.com/questions/46509152/why-in-64bit-the-virtual-address-are-4-bits-short-48bit-long-compared-with-the for a diagram. See also other links in the [x86 tag wiki](https://stackoverflow.com/tags/x86/info). Also http://wiki.osdev.org/Setting_Up_Paging – Peter Cordes Oct 20 '17 at 06:03
-
2You "could" have non-overlapping 4G segments but you won't on x86-32, because all descriptors are then mapped to the **same** linear 4G address space. 32 bit code in long mode is different of course. – Antti Haapala -- Слава Україні Oct 20 '17 at 06:19
-
1@AnttiHaapala hrm, that makes sense because they become virtual addresses, which are only 32 bits. Would it be possible for a 32-bit OS to expose more than 4GB of memory to user-space with paging disabled? [Ira Baxter said](https://stackoverflow.com/a/10810340/224132) "The segment registers on the x86-32 machine can still be used for real segment registers, but nobody has bothered", so I was assuming that this meant you could really address more than 4GB of memory with them. – Peter Cordes Oct 20 '17 at 06:27
-
1@PeterCordes no, because by the time x86's realistically could have >4G RAM then by then everyone knew that segments were not the way to do it. – Antti Haapala -- Слава Україні Oct 20 '17 at 08:45
-
@Peter - I have seen the "didn't bother" part in an old interview with Windows NT designers. They realized that you could swap a 4GB segment to disk and then load another one. What they didn't see was that PC hard disks could ever be large enough to hold a 4 GB swap file. So didn't bother. – Bo Persson Oct 20 '17 at 12:12
The 64 KiB limitation is not inherent in using segments, but of the context where x86 segments were born and were used the most, i.e. 16 bit x86 processors, where addresses were 16 bit wide, and thus could only index 64 KiB of memory.1
Segments were essentially a trick to work around this limitation: each segment (roughly) contains a base address, to which you could implicitly or explicitly refer a 16 bit address. This allowed you to have simultaneously 5 64 KiB "views" inside the larger physical address space (later, the virtual address space); such views are also movable by loading a different address in the relevant segment registers.
The trick works particularly well as as different instructions work by default using different instructions - normally working on mostly segregated data - refer to different segments by default.
Code is read from CS (Code Segment), thus the 16 bit instruction pointer and the near call
refer to it. Stack instructions (push
, pop
and stuff like call
and ret
) operate on the stack segment; SP and BP are normally interpreted relative to SS (Stack Segment).
Every other instruction that can operate on a memory operand works by default on DS (Data Segment) unless you use SP or BP as base register, when it's implicit that you are operating on the stack, so SS is assumed; still, you can add a prefix that allows to operate on another segment.
Finally, string instructions operate on DS on read (overridable) and ES on write. This allows easy and efficient copy of data between the data space (or another segment) and another location further away than 64 KiB. A typical arrangement was to point ES to the video memory and use string instructions to copy image data onscreen.
Thus, in general even without playing tricks by changing the segments you could have your different 64 KiB of stack, code, data and one "accessory" address spaces via ES (three since the 80386, where FS and GS were added), so you could address up to2 256 KiB (384 KiB) of memory at the same time, still keeping (mostly) just 16 bit pointers around - although you had to remember what segment they referred to.
Now, stuff gets more difficult once you need to actually address even more memory, thus changing the segment registers while your program is running.
Code is simple enough - if you need to call into a function that is in a zone outside the current CS you need to perform a "far call" passing a 32 bit far pointer; as long as far calls and far returns are matched and nobody tries to do a near call to code that is actually far everything goes smoothly.
Data is more delicate; enter far pointers, near pointers, the various bizarre DOS memory models and stuff gets messy quite fast - when programming in a higher level language such as C you had to worry about this kind of ultra-low level details. I don't really have first hand experience about writing software in a memory model with far pointers around, so I cannot share my own experiences, but everybody who talks about it pretty much agrees that it was not pretty at all, and we can all be glad to have moved to a world where a pointer is just a pointer inside a single flat address space.
Notes
- Even in 32 bit flat-addressing mode you are still using segment registers, but they are all set to zero, so all segments are the same and perform no translation between the addresses you use and the actual memory accessed. FS/GS are an exception, as they are generally used by modern operating systems to refer to a block of memory containing thread-context data.
- Remember that segments may overlap. Incidentally, this caused further complications in high level languages, as numerically comparison of far pointers gives no information about the equality of the actual pointed address.

- 123,740
- 17
- 206
- 299
-
So basically the way that segmentation allows you to "expand the memory" is by having each register(CS, SS, DS) pointing to a different part of the memory? – Trey Oct 20 '17 at 06:04
-
Minor mistake: Addressing modes with `[BP]` as the base also imply `SS:` (with any instruction), so you don't need SS overrides to access local variables or function args on the stack (or things you just pushed). – Peter Cordes Oct 20 '17 at 06:08
-
Also, [string instructions use `ES:(E)DI` and `DS:(E)SI`](http://felixcloutier.com/x86/REP:REPE:REPZ:REPNE:REPNZ.html). (FS was new with 386). You're probably thinking of a use case where you use an FS segment override with `rep movs`. But yes, using 2 different segment registers was (/is) a feature of `movs` and `cmps`, and `scas` also uses `es:edi`, I guess so you can do something manually in a loop with `lodsb` – Peter Cordes Oct 20 '17 at 06:13
-
@PeterCordes: aaah, right! Unfortunately I have relatively minor experience with this stuff, most of my 16 bit coding was done in the happy COM files world, where CS=DS=SS=ES and you can pretend that all this stuff doesn't really exist (besides the occasional ES set to video memory). I'll fix these right now. – Matteo Italia Oct 20 '17 at 06:20
-
I had to look up which of ES and DS went with DI and SI, but I knew it wasn't FS because 8086 has movs but not FS. I'm *really* happy that I never had write code with segments. I learned on m68k (Atari ST), then only occasional stuff on i386 and x86-64 Linux until getting into asm again while hanging around on SO and optimizing stuff. – Peter Cordes Oct 20 '17 at 06:23
-
2@Trey: essentially, yes. It's like having several security cameras on the same yard, each with some controls to adjust what zone you actually want to see. In flat memory mode you just have a single wide angle camera from where you can look at whatever zone of the yard without touching the controls or moving between different cameras. – Matteo Italia Oct 20 '17 at 06:39
-
1@PeterCordes with string instructions you can override only the source segment `ds`, i.e. `fs rep movsb` will copy from `[fs:si]` to `[es:di]`. The destination `es` can't be overridden (even in `SCAS` when used alone it can not be overridden). – Ped7g Oct 20 '17 at 09:59
-
1@Ped7g: Oops, yeah that's exactly what [the documentation I copy-pasted](http://felixcloutier.com/x86/MOVS:MOVSB:MOVSW:MOVSD:MOVSQ.html) said, but then I mixed it up. (Deleted the old comment). So if you were using `fs` for the VGA base, you'd need to temporarily set `es = fs` to blit some memory there with a rep movs. – Peter Cordes Oct 20 '17 at 10:20