23

The Linux Programming Interface shows the layout of a virtual address space of a process. Is each region in the diagram a segment?

enter image description here

From Understanding The Linux Kernel,

is it correct that the following means that the segmentation unit in MMU maps the segments and offsets within segments into the virtual memory address, and the paging unit then maps the virtual memory address to the physical memory address?

The Memory Management Unit (MMU) transforms a logical address into a linear address by means of a hardware circuit called a segmentation unit; subsequently, a second hardware circuit called a paging unit transforms the linear address into a physical address (see Figure 2-1).

enter image description here

Then why does it say that Linux doesn't use segmentation but only paging?

Segmentation has been included in 80x86 microprocessors to encourage programmers to split their applications into logically related entities, such as subroutines or global and local data areas. However, Linux uses segmentation in a very limited way. In fact, segmentation and paging are somewhat redundant, because both can be used to separate the physical address spaces of processes: segmentation can assign a different linear address space to each process, while paging can map the same linear address space into different physical address spaces. Linux prefers paging to segmentation for the following reasons:

• Memory management is simpler when all processes use the same segment register values—that is, when they share the same set of linear addresses.

• One of the design objectives of Linux is portability to a wide range of architectures; RISC architectures, in particular, have limited support for segmentation.

The 2.6 version of Linux uses segmentation only when required by the 80x86 architecture.

  • Can you specify the editions please. It might also be helpful to specify author names. I know at least the first is from a prominent figure. However, both of the title names are a bit generic, it was not clear to me at first that you were talking about books :-). – sourcejedi Sep 15 '18 at 15:42
  • 2
    Re "Segmentation has been included in 80x86 microprocessors...": That's just not true. It's a legacy of the 808x processors, which had 16-bit data pointers and 64 Kb memory segments. Segment pointers allowed you to switch segments to address more memory. That architecture carried over to the 80x86 (with pointer size increased to 33 bits). Nowadays in the x86_64 model, you have 64 bit pointers which can (theoretically - I think only 48 bits are actually used) address 16 exabytes, so segments aren't necessary. – jamesqf Sep 15 '18 at 17:01
  • 2
    @jamesqf, well, the 32-bit protected mode in the 386 supports segments that are quite different from the 16-byte scaled pointers they are in the 8086, so it's not just plain legacy. That's not to say anything about their usefulness, of course. – ilkkachu Sep 15 '18 at 20:33
  • @jamesqf 80186 had the same memory model as 8086, no "33 bits" – Jasen Sep 16 '18 at 20:42
  • No worthy of answer, hence just a comment: Segments and Pages are only comparable in context of swapping (e.g. page swapping vs segment swapping) and in that context Page swapping just blows segment swapping out of the water. If you swap segments in/out, you'd need to swap the whole segment, which might be 2-4GB. This has never been a real thing to be used on x86. With pages you can always work on 4KB units. When it comes to accessing memory then segments and pages are related through page tables and comparison would be apples to oranges. – vhu Sep 17 '18 at 6:06
19

The x86-64 architecture does not use segmentation in long mode (64-bit mode).

Four of the segment registers: CS, SS, DS, and ES are forced to 0, and the limit to 2^64.

https://en.wikipedia.org/wiki/X86_memory_segmentation#Later_developments

It is no longer possible for the OS to limit which ranges of the "linear addresses" are available. Therefore it cannot use segmentation for memory protection; it must rely entirely on paging.

Do not worry about the details of x86 CPUs which would only apply when running in the legacy 32-bit modes. Linux for the 32-bit modes is not used as much. It may even be considered "in a state of benign neglect for several years". See 32-Bit x86 support in Fedora [LWN.net, 2017].

(It happens that 32-bit Linux does not use segmentation either. But you don't need to trust me on that, you can just ignore it :-).

  • That's a bit of an overstatement. base/limit are fixed at 0/-1 in long mode for the legacy original-8086 segments (CS/DS/ES/SS), but FS and GS still have arbitrary segment base. And the segment descriptor loaded into CS determines whether the CPU executes in 32 or 64-bit mode. And User-space on x86-64 Linux uses FS for thread-local storage (mov eax, [fs:rdi + 16]). The kernel uses GS (after swapgs) to find the current process's kernel stack in the syscall entry point. But yes, segmentation isn't used as part of the main OS memory management / memory-protection mechanism. – Peter Cordes Sep 17 '18 at 8:05
  • This is basically what the quote in the question meant by "The 2.6 version of Linux uses segmentation only when required by the 80x86 architecture." But your 2nd paragraph is basically wrong. Linux uses segmentation basically identically in 32 and 64-bit modes. i.e. base=0 / limit=2^32 or 2^64 for the classic segments (CS/DS/ES/SS) that are used implicitly by normal instructions. There's nothing "extra" to worry about in 32-bit Linux code; the HW functionality is there but not used. – Peter Cordes Sep 17 '18 at 8:09
  • @PeterCordes you're basically interpreting the answer wrong :-). So I've edited it to try and make my argument less ambiguous. – sourcejedi Sep 17 '18 at 9:37
  • Good improvement, now it's not misleading. I totally agree with your real point, which is that you can and should totally ignore x86 segmentation, because it's only used for osdev system-management things, and for TLS. If you want to eventually learn about it, it's a lot easier to understand after you already understand x86 asm with a flat memory model. – Peter Cordes Sep 17 '18 at 9:44
8

As the x86 has segments, it is not possible to not use them. But both cs (code segment) and ds(data segment) base addresses are set to zero, so the segmentation is not really used. An exception is thread local data, one of the normally unused segment registers points to thread local data. But that is mainly to avoid reserving one of the general purpose registers for this task.

It doesn't say that Linux doesn't use segmentation on the x86, as that would not be possible. You already highlighted one part, Linux uses segmentation in a very limited way. The second part is Linux uses segmentation only when required by the 80x86 architecture

You already quoted the reasons, paging is easier and more portable.

7

Is each region in the diagram a segment?

No.

While the segmentation system (in 32-bit protected mode on an x86) is designed to support separate code, data and stack segments, in practice all the segments are set to the same memory area. That is, they start at 0 and end at the end of memory(*). That makes the logical addresses and linear addresses equal.

This is called a "flat" memory model, and is somewhat simpler than the model where you have distinct segments and then pointers within them. In particular, a segmented model requires longer pointers, since the segment selector has to be included in addition to the offset pointer. (16-bit segment selector + 32 bit offset for a total of 48 bit pointer; versus just a 32-bit flat pointer.)

64-bit long mode doesn't really even support segmentation other than the flat memory model.

If you were to program in 16-bit protected mode on the 286, you'd have more need for segments, since the address space is 24 bits but the pointers are only 16 bits.

(* Note that I can't remember how 32-bit Linux handles the kernel/userspace separation. Segmentation would allow that via setting the userspace segments limits so that they don't include the kernel space. Paging allows for it since it provides a per-page protection level.)

Then why does it say that Linux doesn't use segmentation but only paging?

The x86 still has the segments and you can't disable them. They're just used as little as possible. In 32-bit protected mode, the segments need to be set up for the flat model, and even in 64-bit mode they still sort of exist.

  • Huh, I guess a 32-bit kernel could maybe mitigate Meltdown more cheaply than changing page tables by setting segment limits on CS/DS/ES/SS that prevent user-space from accessing above 2G or 3G. (The Meltdown vuln is a workaround for the kernel/user bit in page-table entries, allowing user-space to read from pages that are mapped kernel-only). The VDSO pages might be mapped at the top of the 4G, though :/ wrfsbase is illegal in protected/compat mode, only long mode, so on a 32-bit kernel user-space couldn't set FS base high. – Peter Cordes Sep 17 '18 at 8:18
  • On a 64-bit kernel, 32-bit user-space could potentially far jump to a 64-bit code segment, so you couldn't depend on segment limits for Meltdown protection, only maybe in a pure 32-bit kernel. (Which has large disadvantages on machines with lots of physical RAM, e.g. running out of low mem for thread stacks.) Anyway, yes Linux protects kernel memory with paging, leaving base/limit = 0/-1 in user-space for the normal segments (not FS/GS which are used for thread-local storage). – Peter Cordes Sep 17 '18 at 8:21
  • Before the NX bit was supported in hardware page tables (PAE), some early security patches used segmentation to make non-executable stacks for user-space code. e.g. linux.com/news/exec-shield-new-linux-security-feature (Ingo Molnar's post mentions "Solar Designer's excellent "non-exec stack patch"".) – Peter Cordes Sep 17 '18 at 8:23
3

Linux x86/32 doesn't use segmentation in the sense that it initializes all segments to the same linear address and limit. x86 architecture requires the program to have segments: code can only be executed from the code segment, stack can only be located in the stack segment, data can only be manipulated in one of the data segments. Linux bypasses this mechanism by setting all segments in the same way (with exceptions which your book doesn't mention anyway), so that the same logical address is valid in any segment. This is in fact equivalent to having no segments at all.

2

Is each region in the diagram a segment?

These are 2 almost-totally-different uses of the word "segment"

  • x86 segmentation / segment registers: modern x86 OSes use a flat memory model where all segments have the same base=0 and limit=max in 32-bit mode, the same as hardware enforces that in 64-bit mode, making segmentation kind of vestigial. (Except for FS or GS, used for thread-local storage even in 64-bit mode.)
  • Linker / program-loader sections / segments. (What's the difference of section and segment in ELF file format)

The usages have a common origin: if you were using a segmented memory model (especially without paged virtual memory), you might have data and BSS addresses be relative to the DS segment base, stack relative to the SS base, and code relative to the CS base address.

So multiple different programs could be loaded to different linear addresses, or even moved after starting, without changing the 16 or 32-bit offsets relative to the segment bases.

But then you have to know which segment a pointer is relative to, so you have "far pointers" and so on. (Actual 16-bit x86 programs often didn't need to access their code as data, so could use a 64k code segment somewhere, and maybe another 64k block with DS=SS, with the stack growing down from high offsets, and data at the bottom. Or a tiny code model with all segment bases equal).


How x86 segmentation interacts with paging

Address mapping in 32 / 64-bit mode is:

  1. segment:offset (segment base implied by the register holding the offset, or overridden with an instruction prefix)
  2. 32 or 64-bit linear virtual address = base+offset. (In a flat memory model like Linux uses, pointers / offsets = linear addresses too. Except when accessing TLS relative to FS or GS.)
  3. page tables (cached by TLB) map linear to 32 (legacy mode), 36 (legacy PAE), or 52-bit (x86-64) physical address. (https://stackoverflow.com/questions/46509152/why-in-64bit-the-virtual-address-are-4-bits-short-48bit-long-compared-with-the).

    This step is optional: paging has to be enabled during bootup by setting a bit in a control register. Without paging, linear addresses are physical addresses.

Notice that segmentation does not let you use more than 32 or 64 bits of virtual address space in a single process (or thread), because the flat (linear) address space everything is mapped into only has the same number of bits as offsets themselves. (This wasn't the case for 16-bit x86, where segmentation was actually useful for using more than 64k of memory with mostly 16-bit registers and offsets.)


The CPU caches segment descriptors loaded from the GDT (or LDT), including the segment base. When you dereference a pointer, depending on what register it's in, it defaults to either DS or SS as the segment. The register value (pointer) is treated as an offset from the segment base.

Since the segment base is normally zero, CPUs do special-case this. Or from another perspective, if you do have a non-zero segment base, loads have extra latency because the "special" (normal) case of bypassing adding the base address doesn't apply.


How Linux sets up x86 segment registers:

The base and limit of CS/DS/ES/SS are all 0 / -1 in 32 and 64-bit mode. This is called a flat memory model because all pointers point into the same address space.

(AMD CPU architects neutered segmentation by enforcing a flat memory model for 64-bit mode because the mainstream OSes weren't using it anyway, except for no-exec protection which was provided in a much better way by paging with the PAE or x86-64 page-table format.)

  • TLS (Thread Local Storage): FS and GS are not fixed at base=0 in long mode. (They were new with 386, and not used implicitly by any instructions, not even the rep-string instructions which use ES). x86-64 Linux sets the FS base address for each thread to the address of the TLS block.

    e.g. mov eax, [fs: 16] loads a 32-bit value from 16 bytes into the TLS block for this thread.

  • the CS segment descriptor chooses what mode the CPU is in (16/32/64-bit protected mode / long mode). Linux uses a single GDT entry for all 64-bit user-space processes, and another GDT entry for all 32-bit user-space processes. (For the CPU to work right, DS/ES also have to be set to valid entries, and so does SS). It also chooses the privilege level (kernel (ring 0) vs. user (ring 3)), so even when returning to 64-bit user-space, the kernel still has to arrange for CS to change, using iret or sysret instead of a normal jump or ret instruction.

  • In x86-64, the syscall entry point uses swapgs to flip GS from user-space's GS to the kernel's, which it uses to find the kernel stack for this thread. (A specialized case of thread-local storage). The syscall instruction doesn't change the stack pointer to point at the kernel stack; it's still pointing to the user stack when the kernel reaches the entry point1.

  • DS/ES/SS also have to be set to valid segment descriptors for the CPU to work in protected mode / long mode, even though the base / limit from those descriptors are ignored in long mode.

So basically x86 segmentation is used for TLS, and for the mandatory x86 osdev stuff that the hardware requires you to do.


Footnote 1: Fun history: there are mailing list archives of messages between kernel devs and AMD architects from a couple years before AMD64 silicon was released, resulting in tweaks to the design of syscall so it was usable. See links in this answer for details.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.