大多数操作系统使用页面调度来存储虚拟内存。为什么是这样?为什么不使用细分?仅仅是因为硬件问题吗?在某些情况下,一个比另一个好吗?基本上,如果您必须选择一个,则要使用哪个?为什么?
出于参数考虑,我们假设它是x86。
为什么英特尔选择将段的基数和限制划分为段描述符中的不同部分而不是使用连续位?
见http://css.csail.mit.edu/6.858/2014/readings/i386/s05_01.htm的图5-3
为什么它们不将位地址存储在位0到31中,限制位32到51并将剩余位置用于其他位(或某些类似的布局)?
在研究Linux内部和内存管理时,我偶然发现Linux使用的分段分页模型。
如果我错了,请纠正我,但是Linux(保护模式)确实使用分页将线性虚拟地址空间映射到物理地址空间。由页面组成的线性地址空间,对于进程平面内存模型分为四个部分,即:
__KERNEL_CS);__KERNEL_DS);__USER_CS);__USER_DS);存在第五个内存段,称为Null段,但未使用。
这些段的CPL(当前特权级别)为0(主管)或3(用户区域)。
为简单起见,我将集中讨论32位内存映射,其中4GiB可寻址空间,3GiB用于用户空间进程空间(以绿色显示),1GiB用于主管内核空间(以红色显示):
因此红色的部分由两个部分__KERNEL_CS和组成,__KERNEL_DS绿色的部分由两个部分__USER_CS和组成__USER_DS。
这些段彼此重叠。分页将用于用户空间和内核隔离。
但是,从Wikipedia 此处提取:
daccess-ods.un.org daccess-ods.un.org许多32位操作系统都通过将所有段的基数都设置为0来模拟平面存储器模型,以使分段对程序无关。
在这里查看GDT的linux内核代码:
[GDT_ENTRY_KERNEL32_CS] = GDT_ENTRY_INIT(0xc09b, 0, 0xfffff),
[GDT_ENTRY_KERNEL_CS] = GDT_ENTRY_INIT(0xa09b, 0, 0xfffff),
[GDT_ENTRY_KERNEL_DS] = GDT_ENTRY_INIT(0xc093, 0, 0xfffff),
[GDT_ENTRY_DEFAULT_USER32_CS] = GDT_ENTRY_INIT(0xc0fb, 0, 0xfffff),
[GDT_ENTRY_DEFAULT_USER_DS] = GDT_ENTRY_INIT(0xc0f3, 0, 0xfffff),
[GDT_ENTRY_DEFAULT_USER_CS] = GDT_ENTRY_INIT(0xa0fb, 0, 0xfffff),
Run Code Online (Sandbox Code Playgroud)
正如Peter所指出的,每个段都从0开始,但是那些标志分别是0xc09b,0xa09b等等?我倾向于认为它们是段选择器,如果不是,那么如果它们的寻址空间都从0开始,我将如何从内核段访问userland段?
不使用细分。仅使用分页。段的seg_base地址设置为0,将其空间扩展到0xFFFFF,从而提供完整的线性地址空间。这意味着逻辑地址与线性地址没有区别。
另外,由于所有段彼此重叠,提供内存保护(即内存分离)的是分页单元吗?
分页提供保护,而不是分段。内核将检查线性地址空间,并根据边界(通常称为 …
如果使用分段内存模型运行的 32 位操作系统仍然是4GB限制吗?
我正在阅读Intel Pentium Processor Family Developer's Manual,它指出使用分段内存模型可以映射高达64TB的内存。
“在内存组织的分段模型中,逻辑地址空间由多达 16,383 个分段组成,每个分段最多 4 GB,或总共有 2^46 字节(64 TB)。处理器映射这个 64 TB 的逻辑地址通过第 11 章中描述的地址转换机制将空间转移到物理地址空间。应用程序程序员可以忽略这种映射的细节。分段模型的优点是每个地址空间内的偏移量是单独检查的,并且可以单独访问每个段受控。

这不是一个复杂的问题。我只是想确保我正确理解了文本。如果 Windows 或任何其他操作系统在分段模型而不是平面模型中工作,内存限制是否为 64TB?
更新:

英特尔的 3-2 3a 系统文档。

http://pdos.csail.mit.edu/6.828/2005/readings/i386/c05.htm
不应将段寄存器视为传统实模式意义上的。段寄存器充当全局描述符表的选择器。
在保护模式下,您使用 A:B 形式的逻辑地址来寻址内存。与实模式一样,A 是段部分,B 是该段内的偏移量。> 保护模式下的寄存器限制为 32 位。32 位可以表示 0 到 4Gb 之间的任何整数。因为 B 可以是 0 到 4Gb 之间的任何值,所以我们的段现在的最大大小为 4Gb(与实模式中的推理相同)。现在的区别。在保护模式下,A 不是段的绝对值。在保护模式下,A 是一个选择器。选择器表示在称为全局描述符表 (GDT) 的系统表中的偏移量。GDT 包含一个描述符列表。这些描述符中的每一个都包含描述段特征的信息。
Segment Selector 提供了分页无法实现的额外安全性。
这两种方法【Segmentation and Paging】各有优势,但分页要好得多。分段虽然仍然可用,但作为一种内存保护和虚拟内存方法,它很快就会过时。事实上,x86-64 架构需要一个平面内存模型(一个以 0 为基数且限制为 0xFFFFFFFF 的段),以便它的一些指令正常运行。
然而,分段完全内置于 x86 架构中。绕过它是不可能的。所以在这里我们将向您展示如何设置您自己的全局描述符表 - 一个段描述符列表。 …
我对实际模式中的段大小有一个疑问,因为它们不能超过64K但可能小于 64K .我的问题是如何初始化这些段大小和基址?就像GDT和LDT处于保护模式一样.实模式段也可以重叠,不相交或相邻.像BIOS有一些保留区域用于特定的事情,如启动代码,视频缓冲区等装配程序需要做类似的事情吗?
我已经学习了编译器和汇编语言,所以我想编写自己的汇编程序作为练习。但我有一些问题;
如何计算@DATA 或 OFFSET/ADDR VarA 等段的地址?
以一个简单的汇编程序为例:
.model small
.stack 1024
.data
msg db 128 dup('A')
.code
start:
mov ax,@data
mov ax,ds
mov dx, offset msg
; DS:DX points at msg
mov ah,4ch
int 21h ; exit program without using msg
end
Run Code Online (Sandbox Code Playgroud)
那么汇编器是如何计算段的段地址的@data呢?
它如何知道将什么放入立即数mov dx, offset msg?
compiler-construction assembly masm memory-segmentation x86-16
我正在尝试使用gdb在KMines中查找当前的标志计数.我知道我应该首先寻找内存映射以避免不存在的内存位置.所以我运行info proc mappings命令来查看内存段.我0xd27000-0x168b000从结果中获取了一个随机内存gap()并执行了如下命令:find 0x00d27000, 0x0168b000, 10
但我得到了warning: Unable to access 1458 bytes of target memory at 0x168aa4f, halting search.错误.虽然地址0x168aa4f介于0xd27000和0x168b000之间,但gdb表示它无法访问它.为什么会这样?我该怎么做才能避免这种情况?或者有没有办法忽略未映射/不可访问的内存位置?
编辑:我试图将地址0x168aa4f的值设置为1并且它可以工作,因此gdb实际上可以访问该地址,但在与find命令一起使用时会出错.但为什么?
我目前正在关注操作系统开发教程,其中包括有关引导加载程序的讨论。
我的引导加载程序当前处于 16 位实模式,因此,我能够使用提供的 BIOS 中断(例如 VGA 视频中断等)。
BIOS 提供视频中断0x10(即视频电传输出)。视频中断具有功能0x0E,它允许我将字符打印到屏幕上。
这是这个基本的引导加载程序:
org 0x7c00 ; Set program start (origin) address location at 0x7c00.
; This program is loaded by the BIOS at 0x7c00.
bits 16 ; We live in 16-bit Real Mode.
start:
jmp loader
bootmsg db "Welcome to my Operating System!", 0 ; My data string.
;-------------------------------------------------------
; Description: Print a null terminating string
;-------------------------------------------------------
print:
lodsb ; Load string byte at address DS:SI …Run Code Online (Sandbox Code Playgroud) 我阅读了许多文章和 S/O 答案说(在 linux x86_64 上)FS(或某些变体中的 GS)引用了一个特定于线程的页表条目,然后它给出了一个指向可共享的实际数据的指针数组数据。当线程被交换时,所有的寄存器都被切换,因此线程基页会发生变化。线程变量通过名称访问,只需要 1 个额外的指针跃点,并且引用的值可以共享给其他线程。一切都好,说得通。
事实上,如果你查看__errno_location(void)背后的函数的代码errno,你会发现类似的东西(这是来自 musl,但 gnu 并没有太大的不同):
static inline struct pthread *__pthread_self()
{
struct pthread *self;
__asm__ __volatile__ ("mov %%fs:0,%0" : "=r" (self) );
return self;
}
Run Code Online (Sandbox Code Playgroud)
来自 glibc:
=> 0x7ffff6efb4c0 <__errno_location>: endbr64
0x7ffff6efb4c4 <__errno_location+4>: mov 0x6add(%rip),%rax # 0x7ffff6f01fa8
0x7ffff6efb4cb <__errno_location+11>: add %fs:0x0,%rax
0x7ffff6efb4d4 <__errno_location+20>: retq
Run Code Online (Sandbox Code Playgroud)
所以我的期望是 FS 的实际值会因每个线程而改变。例如,在调试器下, gdb: info regor p $fs,我会看到 FS 的值在不同的线程中是不同的,但没有: ds, es, fs, gs 一直都为零。
在我自己的代码中,我写了类似下面的内容并得到相同的结果 - FS 没有改变,但 TLV “有效”:
struct …Run Code Online (Sandbox Code Playgroud)