最近我偶然发现了 Rust 和 C 之间的比较,它们使用以下代码:
bool f(int* a, const int* b) {
*a = 2;
int ret = *b;
*a = 3;
return ret != 0;
}
Run Code Online (Sandbox Code Playgroud)
在 Rust(相同的代码,但使用 Rust 语法)中,它生成以下汇编代码:
cmp dword ptr [rsi], 0
mov dword ptr [rdi], 3
setne al
ret
Run Code Online (Sandbox Code Playgroud)
使用 gcc 时,它会产生以下结果:
mov DWORD PTR [rdi], 2
mov eax, DWORD PTR [rsi]
mov DWORD PTR [rdi], 3
test eax, eax
setne al
ret
Run Code Online (Sandbox Code Playgroud)
文本声称 C 函数不能优化第一行,因为a和b可能指向相同的数字。在 Rust 中这是不允许的,因此编译器可以将其优化掉。
现在我的问题:
该函数采用一个const …
我从硬件抽象级别理解端口I / O(即断言一个引脚,该引脚向总线上的设备指示该地址是端口地址,这在具有简单地址总线模型的早期CPU上是有意义的),但我不是真的确保在微体系结构上如何在现代CPU上实现它,尤其要确保端口I / O操作在环形总线上的显示方式。
首先。IN / OUT指令在哪里分配给保留站或加载/存储缓冲区?我最初的想法是,将在加载/存储缓冲区中分配它,并且内存调度程序将其识别出来,将其发送到L1d,指示它是端口映射操作。分配了一个行填充缓冲区,并将其发送到L2,然后发送到环网。我猜环上的消息有一些端口映射的指示器,只有系统代理才能接受它,然后它检查其内部组件并将端口映射的指示请求转发给它们。即PCIe根网桥将拾取CF8h和CFCh。我猜测DMI控制器是固定的,可以拾取将出现在PCH上的所有标准化端口,例如用于传统DMA控制器的端口。
我认为: 软重置:从重置向量启动。 硬复位:拉CPU的电平。
当执行用户代码的线程正在等待输入时,调度程序如何知道要中断输入或线程如何知道如何调用调度程序,因此,看到一个简单的单线程应用程序的普通程序员不太可能在各处插入sched_yield()。编译器是在优化时插入sched_yield()还是线程只是自旋锁定,直到调度程序设置的通用计时器中断触发,或者用户是否必须显式声明wait(),sleep()函数以使上下文切换?
这个问题是,如果调度不抢先因为那特别相关有来电时,它在等待输入吞吐量是有效的调度,但我不知道它是如何做到这一点。
我已经做了一些关于Spectre v2的阅读,显然你得到了非技术性的解释.彼得科德斯有一个更深入的解释,但它没有完全解决一些细节.注意:我从未进行过Spectre v2攻击,因此我没有亲身体验.我只读过关于这个理论的内容.
我对Spectre v2的理解是你做了一个间接的分支误预测if (input < data.size).如果间接目标数组(我不太确定它的细节 - 即为什么它与BTB结构分开) - 在解码时重新检查间接分支的RIP - 不包含预测那么它将插入新的跳转RIP(分支执行最终将插入分支的目标RIP),但是现在它不知道跳转的目标RIP,因此任何形式的静态预测都不起作用.我的理解是它始终预测不会采用新的间接分支,并且当端口6最终计算出跳转目标RIP并预测它将使用BOB回滚并使用正确的跳转地址更新ITA然后更新本地和全局分支历史寄存器和相应的饱和计数器.
黑客需要训练饱和计数器,以便始终预测所采取的操作,我想,通过if(input < data.size)在循环中多次运行,其中input设置为确实小于data.size(相应地捕获错误)和循环的最后一次迭代,input超过data.size(例如1000); 将预测间接分支,它将跳转到发生缓存加载的if语句的主体.
if语句包含secret = data[1000](包含秘密数据的特定内存地址(data [1000]),目标是从内存加载到缓存)然后将推测性地将其分配给加载缓冲区.前面的间接分支仍在分支执行单元中,等待完成.
我相信前提是在错误预测中刷新加载缓冲区之前需要执行加载(分配行填充缓冲区).如果已为其分配了行填充缓冲区,则无法执行任何操作.有意义的是没有取消行填充缓冲区分配的机制,因为行填充缓冲区必须在将其返回到加载缓冲区之后存储到缓存之前挂起.这可能导致行填充缓冲区变得饱和,因为它不是在需要时解除分配(保持在那里以便其他负载的速度到同一地址,但在没有其他可用的行缓冲区时解除分配).在收到一些不会发生刷新的信号之前,它将无法解除分配,这意味着它必须暂停以执行前一个分支,而不是立即使行填充缓冲区可用于其他逻辑核心的存储.这种信令机制可能难以实现,也许它并没有超出他们的想法(史前思考者),并且如果分支执行需要足够的时间来挂起行填充缓冲区以引起性能影响,即它也会引入延迟,即在循环的最后一次迭代之前data.size有目的地从cache(CLFLUSH)中刷新,这意味着分支执行可能需要多达100个周期.
我希望我的想法是正确的,但我不是百分百肯定.如果有人有任何要添加或更正,那么请做.
int a;
int a=3; //error as cpp compiled with clang++-7 compiler but not as C compiled with clang-7;
int main() {
}
Run Code Online (Sandbox Code Playgroud)
对于 C,编译器似乎将这些符号合并为一个全局符号,但对于 C++,这是一个错误。
文件 1:
int a = 2;
Run Code Online (Sandbox Code Playgroud)
文件2:
#include<stdio.h>
int a;
int main() {
printf("%d", a); //2
}
Run Code Online (Sandbox Code Playgroud)
作为用 clang-7 编译的 C 文件,链接器不会产生错误,我假设它将未初始化的全局符号“a”转换为外部符号(将其视为被编译为外部声明)。作为使用 clang++-7 编译的 C++ 文件,链接器会产生多重定义错误。
更新:链接的问题确实回答了我的问题中的第一个示例,特别是“在 C 中,如果在同一翻译单元中较早或较晚找到实际的外部定义,则暂定定义仅作为声明。” 和'C++ 没有“暂定定义”'。
至于第二种情况,如果我 printf a,那么它会打印 2,所以显然链接器已经正确链接了它(但我以前假设编译器将临时定义初始化为 0 作为全局定义,并且会导致链接错误)。
事实证明,int i[];两个文件中的暂定定义也与一个定义相关联。int i[5];也是 .common 中的一个暂定定义,只是向汇编器表达了不同的大小。前者称为类型不完整的暂定定义,而后者是类型完整的暂定定义。
C 编译器发生的情况是,int a在 .common 中将其设为强绑定弱全局,并在符号表中 …
特定向量(未屏蔽)发生硬件中断,CPU 检查 IF 标志并将 RFLAGS、CS 和 RIP 压入堆栈,同时后端仍有指令完成,其中一条指令的分支预测结果是错误的。通常管道会被刷新,前端开始从正确的地址获取,但在这种情况下,中断正在进行中。
我已经读过这篇文章,显然解决方案是立即刷新管道中的所有内容,这样就不会发生这种情况,然后生成指令将 RFLAGS、CS、RIP 推送到 TSS 中内核堆栈的位置;然而,问题出现了,它如何知道与最新架构状态关联的 (CS:)RIP,以便能够将其推送到堆栈上(假设前端 RIP 现在将领先)。这类似于 port0 上的采取分支执行单元如何知道当采取预测结果错误时应该获取的 (CS:)RIP 的问题——是编码到指令中的地址以及预言?当你想到陷阱/异常时,也会出现同样的问题,CPU需要将当前指令(故障)或下一条指令(陷阱)的地址推送到内核堆栈,但是它是如何计算出这条指令的地址的当它处于管道的中途时——这让我相信地址必须被编码到指令中并使用长度信息计算出来,这可能全部在预解码阶段完成。
pipeline intel cpu-architecture interrupt-handling branch-prediction
读完这篇文章后:
关于软件中断会发生什么情况的信息并不多,但我们确实了解到以下内容:
相反,异常(例如页面错误)会标记受影响的指令。当该指令即将提交时,异常之后的所有后续指令都将被刷新,并且指令获取将被重定向。
我想知道管道中的软件中断(INT 0xX)会发生什么,首先,它们何时被检测到?它们是否可能在预解码阶段被检测到?在指令队列中?在解码阶段?或者他们到达后端并立即完成(不进入保留站),依次退休,退休阶段发现这是一条 INT 指令(看起来很浪费)。
假设它在预解码时被拾取,必须有一种方法向 IFU 发出信号以停止获取指令,或者确实对其进行时钟/电源门控,或者如果它在指令队列中被拾取,则必须有一种在队列中之前刷新指令的方法。然后必须有一种方法向某种逻辑(“控制单元”)发出信号,例如为软件中断生成微指令(索引到 IDT、检查 DPL >=CPL >=段 RPL 等),天真的建议,但如果有人更好地了解这个过程,那就太好了。
我还想知道当这个过程受到干扰时它如何处理它,即发生硬件中断(记住陷阱不会清除 EFLAGS 中的 IF),现在必须开始一个全新的中断处理和 uop 生成过程,它将如何处理之后回到处理软件中断的状态。
我的印象是,在查看u-op缓存一段时间后,每个微操作都是8个字节,但我的问题是所有微操作都是相同的大小,甚至是融合域微操作?
这与这个问题有关
但是考虑一下,在现代的英特尔CPU上,SEC阶段是以微码实现的,这意味着将进行检查,从而使用烧入的密钥来验证PEI ACM上的签名。如果不匹配,则需要执行某些操作;如果不匹配,则需要执行其他操作。假定这是作为MSROM过程实现的,则必须有一种分支方式,但是鉴于MSROM指令没有RIP。
通常,当一个分支错误地预测到将要采取的指令然后退出时,ROB将检查异常代码,并因此将指令长度添加到ROB行的RIP或仅使用下一个ROB条目的IP,这将导致前端在分支预测更新中恢复到该地址。有了BOB,此功能现在已借给跳转执行单元。显然,这与MSROM例程不可能发生,因为前端与此无关。
我的想法是,有一条特定的跳转指令,只有MSROM例程才能发出,它会跳转到MSROM中的其他位置,并且可以进行配置,以便始终预测不采用MSROM分支指令,并且在分支执行单元遇到此指令时指令并执行分支,它会产生异常代码,并可能将特殊的跳转目标连接到它,并且在退出时会发生异常。另外,执行单元可以处理它,并且可以使用BOB,但我的印象是BOB由分支指令RIP索引,然后还存在这样一个事实,即通常会在退休时处理生成MSROM代码的异常。分支错误预测不需要我不认为的MSROM,而是所有操作都在内部执行。
I know that for hardware interrupts, when KeLowerIrql is called by KeAcquireInterruptSpinLock, the HAL adjusts the interrupt mask in the LAPIC, which will allow queued interrupts (in the IRR probably) to be serviced automatically. But with software interrupts, for instance, ntdll.dll sysenter calls to the SSDT NtXxx system services, how are they 'postponed' and triggered when the IRQL goes to passive level Same goes for the DPC dispatcher software interrupt (if the DPC is for the current CPU and of …
您是否绝对需要使用具有不完整类型的 externint a[];以便它在链接文件中使用数组和定义?我的逻辑是它不保留内存,所以它不是一个定义而是一个声明(就像一个函数原型,它也不需要 extern 编译器将它隐式留给链接器)。我会自己测试,但我目前不能。
intel ×5
x86 ×5
c ×4
pipeline ×2
c++ ×1
chipset ×1
clang ×1
clang++ ×1
embedded ×1
hardware ×1
instructions ×1
interrupt ×1
io ×1
kernel ×1
motherboard ×1
object-files ×1
reset ×1
rust ×1
scheduler ×1
scheduling ×1
spectre ×1
visual-c++ ×1
windows ×1