这个问题旨在填补关于该主题的良好免费信息的真空.
我相信一个好的答案将适合一个大的答案或至少在几个答案.
主要目标是为完整的初学者提供足够的信息,以便他们可以自己学习手册,并能够理解与分页相关的基本操作系统概念.
建议的指导方针:
相关问题以及为什么我认为它们不是愚蠢的:
x86页表如何工作?:标题与此问题几乎相同,但是正文询问与cr3和TLB相关的具体问题.那个问题是这个问题的一个子集.
x86虚拟化如何工作:正文仅询问源.
在本文件中.27它说文本段从0x400000开始.为什么选择这个特定的地址?有什么理由吗?相同的地址被选择在GNU ld上Linux:
$ ld -verbose | grep -i text-segment
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
Run Code Online (Sandbox Code Playgroud)
这是令人惊讶的,因为这个地址在32位x86可执行文件中更大:
$ ld -verbose | grep -i text-segment
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x08048000)); . = SEGMENT_START("text-segment", 0x08048000) + SIZEOF_HEADERS;
Run Code Online (Sandbox Code Playgroud)
我读了这个问题,讨论为什么为i386选择了0x080xxxxx地址,但它没有解释x86_64的变化.在这个问题上很难找到任何解释.有人有线索吗?
有人可以解释TLB(翻译后备缓冲区)未命中和缓存未命中之间的区别吗?
我相信我发现TLB指的是某种虚拟内存地址,但我并不清楚这实际意味着什么?
我理解当一块内存(缓存行的大小)被加载到(L3?)缓存中并且如果所需的地址没有保存在当前缓存行中时,会导致缓存未命中 - 这是缓存未命中.
我被告知并且从英特尔的手册中读到可以将指令写入内存,但是指令预取队列已经获取了陈旧的指令并将执行那些旧的指令.我没有成功观察到这种行为.我的方法如下.
英特尔软件开发手册从第11.6节开始说明
对当前在处理器中高速缓存的代码段中的存储器位置的写入导致相关联的高速缓存行(或多个行)无效.此检查基于指令的物理地址.此外,P6系列和奔腾处理器检查对代码段的写入是否可以修改已经预取执行的指令.如果写入影响预取指令,则预取队列无效.后一种检查基于指令的线性地址.
所以,看起来如果我希望执行陈旧的指令,我需要有两个不同的线性地址引用相同的物理页面.所以,我将内存映射到两个不同的地址.
int fd = open("code_area", O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
assert(fd>=0);
write(fd, zeros, 0x1000);
uint8_t *a1 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
uint8_t *a2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
assert(a1 != a2);
Run Code Online (Sandbox Code Playgroud)
我有一个汇编函数,它接受一个参数,一个指向我想要更改的指令的指针.
fun:
push %rbp
mov %rsp, %rbp
xorq %rax, %rax # Return value 0
# A far jump simulated with a far return
# Push the …Run Code Online (Sandbox Code Playgroud) 我正在hacks.mozilla.org上阅读64位Firefox版本的博客.
作者说:
对于
asm.js代码,增加的地址空间还允许我们使用硬件内存保护来安全地从asm.js堆访问中删除边界检查.收益非常显着:asmjs-apps上的8%-17%- * - arewefastyet.com上报告的吞吐量测试.
我试图了解64位硬件如何对C/C++进行自动边界检查(假设编译器支持硬件).我在SO中找不到任何答案.我找到了一篇关于这个主题的技术论文,但我无法理解这是怎么做到的.
有人可以在边界检查中解释64位硬件辅助吗?
c++在某些英特尔至强处理器上运行以下代码时,我偶然发现了一个特殊的性能问题:
// array_a contains permutation of [0, n - 1]
// array_b and inverse are initialized arrays
for (int i = 0; i < n; ++i) {
array_b[i] = array_a[i];
inverse[array_b[i]] = i;
}
Run Code Online (Sandbox Code Playgroud)
循环的第一行按顺序复制array_a到array_b(预期很少有缓存未命中)。第二行计算array_b(许多缓存未命中,因为array_b是随机排列)的倒数。我们也可以将代码分成两个单独的循环:
for (int i = 0; i < n; ++i)
array_b[i] = array_a[i];
for (int i = 0; i < n; ++i)
inverse[array_b[i]] = i;
Run Code Online (Sandbox Code Playgroud)
我原以为这两个版本(单循环与双循环)在相对现代的硬件上的性能几乎相同。但是,在执行单循环版本时,某些 Xeon 处理器似乎非常慢。
您可以在下方看到以纳秒为单位n的挂机时间除以在一系列不同处理器上运行代码段的时间。出于测试目的,代码是使用 GCC 7.5.0 编译的,并-O3 -funroll-loops -march=native …
如果我想访问位于地址的X86中的VGA文本缓冲区 0xb8000:
uint16_t *VGA_buffer = (uint16_t*)0xb8000;
Run Code Online (Sandbox Code Playgroud)
然后我索引变量VGA_buffer作为一个正常的阵列,即VGA_buffer[0],VGA_buffer[1]等
但是,我在x86中读到了内存映射,其中列出的地址是物理地址.
我的问题是:
CPU如何访问此地址?CPU是否知道在代码中明确写入的任何地址是物理地址,并且不应通过地址转换机制(逻辑地址 - >虚拟地址 - >到物理地址)?
提前致谢.
在"低级编程:英特尔®64架构上的C,汇编和程序执行"一书中,我读到:
每个虚拟64位地址(例如,我们在程序中使用的地址)由几个字段组成.地址本身实际上只有48位宽; 它被符号扩展为64位规范地址.它的特点是它的17个左位是相等的.如果不满足条件,则在使用时立即拒绝该地址.然后借助特殊表将48位虚拟地址转换为52位物理地址.
为什么虚拟地址和物理地址之间的差异为4位?
assembly memory-management virtual-memory memory-address mmu
这三个片段的执行时间:
pageboundary: dq (pageboundary + 8)
...
mov rdx, [rel pageboundary]
.loop:
mov rdx, [rdx - 8]
sub ecx, 1
jnz .loop
Run Code Online (Sandbox Code Playgroud)
还有这个:
pageboundary: dq (pageboundary - 8)
...
mov rdx, [rel pageboundary]
.loop:
mov rdx, [rdx + 8]
sub ecx, 1
jnz .loop
Run Code Online (Sandbox Code Playgroud)
还有这个:
pageboundary: dq (pageboundary - 4096)
...
mov rdx, [rel pageboundary]
.loop:
mov rdx, [rdx + 4096]
sub ecx, 1
jnz .loop
Run Code Online (Sandbox Code Playgroud)
对于第一个片段,在4770K上,每次迭代大约5个周期,对于第二个片段,每次迭代大约9个周期,然后是第三个片段的5个周期.它们都访问完全相同的地址,这是4K对齐的.在第二个片段中,只有地址计算跨越页面边界:rdx并且rdx + 8不属于同一页面,负载仍然是对齐的.如果偏移量很大,则会再次回到5个周期.
这种效果一般如何起作用?
通过ALU指令从加载路由结果,如下所示:
.loop:
mov rdx, …Run Code Online (Sandbox Code Playgroud)