Segment size in x86 real mode

百般思念 提交于 2019-11-28 13:45:58

In real mode segmented addresses are hardwired into memory. To get a physical address you can use this equation:

physical address = segment * 16 + offset

Both segment and offset addreses are 16 bit. By using this equation you can make one 20 bit address and access low 640kB of RAM with no problem.

There is no table that holds where some segment is located. The problem is that you have to set both segment and offset registers to be able to access any address. So you could access maximum of 64k of RAM bytes with a simple loop that just increments the offset register, which makes memory access to larger buffers less comfortable than in flat model.

The segment limit in real mode is 64k, even on a 386 or later CPU where you can use 32-bit address-size via prefixes. e.g. mov ax, [edx + ecx*4] is still limited to offsets of 64 kiB in real mode.

If you exceed this limit, it raises a #GP exception. (Or #SS if the segment was SS).

16-bit address-size can't exceed 64k segment limits, because addressing modes like [bx + si] wrap at 16 bits. So only code using the 0x67 address-size prefix (added in 386) in real mode can run into segment limits. 8086 didn't have to check limits, just add Sreg << 4 to the offset from an addressing mode, making the limit implicitly 64k.

Segments that start within 64k of the highest possible address wrap around at 1MiB on 8086, and on later CPUs if A20 is disabled. Otherwise they extend past 1MiB for an address like FFFF:FFFF seg:off = 0x10ffef linear. See What are Segments and how can they be addressed in 8086 mode?


If you switch to protected mode and set a segment register, the CPU keeps the segment description (base + limit) cached internally, even across switching back to 16-bit real mode. This situation is called unreal mode.

Writing to a segment register in 16-bit mode only sets the segment base to value << 4 without changing the limit, so unreal mode is somewhat durable for segments other than CS. CS:EIP is special, especially if you need to avoid truncating EIP to 16 bits when returning from interrupts or whatever. See that osdev wiki link.

push/pop/call/ret use SS:ESP or SS:SP according to the B flag in the current stack-segment descriptor; the address-size prefix only affects stuff like push word [eax] vs. push word [si].

The GDT / LDT are ignored when you write a value to a segment register in real mode. The value is used directly to set the cached segment base, not as a selector at all.

(Each segment is separate; unreal mode isn't an actual mode like protected vs. real; the CPU is in real mode. Writing the FS register, for example, puts that segment back into normal real-mode behaviour, but doesn't change the others. It's just a name for being in real mode with cached segment descriptors with larger limits, so you can use 32-bit address-size for a larger flat address space. Often with base=0 and limit=4G)

AFAIK, there's no way to query the internal limit value of a segment in real mode. lsl loads the segment-limit value directly from a descriptor in the GDT / LDT in memory, not from the internal value (so it's not what you want), and it's not available in real mode anyway.

See comments on this answer for more details about taking segments out of unreal mode intentionally or unintentionally.

286 and 386 CPUs supported a LOADALL instruction which could set segment limits from real mode, but later CPUs don't have it. Commenters say that SMM (system management mode) may be able to do something similar on modern x86.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!