Linux uses the same segment descriptor for SS (the stack segment) and DS (the data segment). The SS segment descriptor must have a DPL field exactly equal to CPL, i.e. the Current Privilege Level. Therefore you need separate data segment descriptors for kernel and user mode.
Sources (I am too lazy to download CPU manuals)
A comment inside the definition of gdt_page:
We need valid kernel segments for data and code in long mode too.
IRET will check the segment types. -- kkeil 2000/10/28
Web search: iret checks segment types
Abhishek Yadav :
Instructions that load selectors into SS must refer to data segment descriptors for writable data segments. The descriptor privilege (DPL) and RPL must equal CPL. All other descriptor types or a privilege level violation will cause exception 13.
Web search: iret check segment type OR "DPL" OR "CPL" "SS"
"Many But Finite" :
... The exception is for the stack segment register ss, for which the three of CPL, RPL, and DPL must match exactly.
bochs-2.6.2/cpu/iret.cc:256 :
/* stack segment DPL must equal the RPL of the return CS selector,
* else #GP(SS selector) */
if (ss_descriptor.dpl != cs_selector.rpl) {
BX_ERROR(("iret: SS.dpl != CS selector RPL"));
exception(BX_GP_EXCEPTION, raw_ss_selector & 0xfffc);
}
Background: why does Linux have separate code segments for user and kernel mode?
Related, another comment in the kernel source code:
We cannot use the same code segment descriptor for user and kernel mode,
not even in long flat mode, because of different DPL.
This is because the DPL of the current code segment is used as the CPL.
I notice you also need different code segments for 32 and 64 bit code -
https://en.wikipedia.org/wiki/Segment_descriptor -
L=Long-mode segment
If set, this is a 64-bit segment (and D must be zero), and code in this segment uses the 64-bit instruction encoding
I guess the DS segment register gets reset when entering the kernel from userspace, at least on x86-32. But I was not able to identify the code that does it.
There is also a recent LWN.net article, which makes an interesting comment about set_fs().
The original role of set_fs() was to set the x86 processor's FS segment register which, in the early days, was used to control the range of virtual addresses that could be accessed by unprivileged code. The kernel has ... long since stopped using x86 segments this way.