在英特尔手册的RDTSC指令警告说,当实际执行RDTSC乱序执行可以改变,所以他们建议将在它前面的CPUID指令,因为CPUID将序列指令流(CPUID是永远不会乱序执行).我的问题很简单:如果他们有能力进行序列化指令,他们为什么不进行RDTSC序列化?它的全部要点似乎是获得周期精确的时间.是否存在一种情况,您不希望在它之前加上序列化指令?
较新的Intel CPU具有单独的序列化RDTSCP指令.英特尔选择引入一个单独的指令,而不是改变RDTSC的行为,这表明我必须存在一些可能出现故障时序的情况.它是什么?
我认为2的补码的重点是对于有符号和无符号数字的操作可以采用相同的方式.维基百科甚至特别列出了多重作为其中一项有益的操作.那么为什么x86对每个都有单独的指令,mul并且imul?x86-64仍然如此吗?
我很想知道我的64位应用程序是否存在对齐错误.
在Windows中,生成对齐错误的应用程序将引发异常,
EXCEPTION_DATATYPE_MISALIGNMENT.
- 在x64体系结构上,默认情况下禁用对齐异常,并且修复由硬件完成.应用程序可以通过设置几个寄存器位来启用对齐异常,在这种情况下,除非用户使操作系统屏蔽异常,否则将引发异常
SEM_NOALIGNMENTFAULTEXCEPT.(有关详细信息,请参阅AMD体系结构程序员手册第2卷:系统编程.)[编者按:强调我的]
在x86体系结构中,操作系统不会使对齐故障对应用程序可见.在这两个平台上,您还会在对齐故障方面遇到性能下降,但它的严重程度将远远低于Itanium,因为硬件将对内存进行多次访问以检索未对齐的数据.
在Itanium上,默认情况下,操作系统(OS)将使该异常对应用程序可见,并且在这些情况下终止处理程序可能很有用.如果您没有设置处理程序,那么您的程序将挂起或崩溃.在清单3中,我们提供了一个示例,说明如何捕获EXCEPTION_DATATYPE_MISALIGNMENT异常.
忽略了参考AMD架构程序员手册的方向,我将参考英特尔64和IA-32架构软件开发人员手册
5.10.5检查对齐
当CPL为3时,可以通过设置CR0寄存器中的AM标志和EFLAGS寄存器中的AC标志来检查存储器参考的对齐情况.未对齐的内存引用会生成对齐异常(#AC).在特权级别0,1或2下操作时,处理器不会生成对齐异常.有关在启用对齐检查时对齐要求的说明,请参阅表6-7.
优秀.我不确定这意味着什么,但非常好.
然后还有:
2.5控制寄存器
控制寄存器(CR0,CR1,CR2,CR3和CR4;见图2-6)确定处理器的操作模式和当前正在执行的任务的特性.在所有32位模式和兼容模式下,这些寄存器均为32位.
在64位模式下,控制寄存器扩展到64位.MOV CRn指令用于操作寄存器位.这些指令的操作数大小前缀将被忽略.
控制寄存器总结如下,并且这些控制寄存器中的每个架构定义的控制字段被单独描述.在图2-6中,64位模式下寄存器的宽度用括号表示(CR0除外).- CR0 - 包含控制处理器的操作模式和状态的系统控制标志
AM
对齐掩码(CR0的第18位) - 设置时启用自动对齐检查; 清除时禁用对齐检查.仅当AM标志置位,EFLAGS寄存器中的AC标志置位,CPL为3,处理器工作在受保护或虚拟8086模式时,才执行对齐检查.
我实际使用的语言是Delphi,但假装它是与语言无关的伪代码:
void UnmaskAlignmentExceptions()
{
asm
mov rax, cr0; //copy CR0 flags into RAX
or rax, 0x20000; //set bit 18 (AM)
mov cr0, rax; //copy flags back
}
Run Code Online (Sandbox Code Playgroud)
第一条指令
mov rax, cr0;
Run Code Online (Sandbox Code Playgroud)
没有特权指令例外.
如何在x64上为我的进程启用对齐异常?
我发现x86有以下指令:
PUSHF,POPF …我是一个操作系统爱好者,我的内核运行在80486+,并且已经支持虚拟内存.
从80386开始,英特尔的x86处理器系列及其各种克隆通过分页支持虚拟内存.众所周知,当设置PG位时CR0,处理器使用虚拟地址转换.然后,CR3寄存器指向顶级页面目录,即用于将虚拟地址映射到物理地址的2-4级页表结构的根.
处理器不会为生成的每个虚拟地址查询这些表,而是将它们缓存在名为Translation Lookaside Buffer或TLB的结构中.但是,当对页表进行更改时,需要刷新TLB.在80386处理器上,此刷新将通过使用顶级页面目录地址或任务切换重新加载(MOV)来完成CR3.这应该无条件地刷新所有TLB条目.据我所知,虚拟内存系统在任何更改后总是重新加载CR3是完全有效的.
这是浪费,因为TLB现在会抛出完全好的条目,因此在80486处理器INVLPG中引入了指令.INVLPG将使与源操作数地址匹配的TLB条目无效.
然而,从Pentium Pro开始,我们还拥有全局页面,这些页面不会被移动到CR3任务切换; 和AMD x86-64 ISA表示某些高级页面表结构可能会被高速缓存而不会失效INVLPG.为了获得每个ISA所需内容和不需要内容的连贯图片,我们真的需要为80年代以来发布的大量ISA下载1000页的数据表来阅读其中的几页,即使这样,文档似乎也是如此对TLB失效特别模糊,如果TLB未正确无效,会发生什么.
为简单起见,可以假设我们正在讨论单处理器系统.此外,可以假设在更改页面结构后不需要任务切换.(因此INVLPG总是被认为至少是重新加载CR3寄存器的好选择).
基本假设是CR3每次更改页面表和页面目录后都需要重新加载,这样的系统是正确的.但是,如果想要避免不必要地冲刷TLB,则需要回答2个问题:
如果INVLPGISA支持,经过哪种更改可以安全地使用它而不是重新加载CR3?例如"如果一个取消映射一个页面框架(将相应的表条目设置为不存在),则可以始终使用INVLPG"?
在不触及CR3或执行的情况下,可以对表和目录进行哪些更改INVLPG?例如"如果一个页面根本没有映射(不存在),那么就可Present=1以为它编写一个PTE 而不用刷新TLB"?
即使在阅读了大量的ISA文档以及与INVLPGStack Overflow 相关的所有内容之后,我也不确定我在那里提供的任何一个例子.事实上,一篇值得注意的帖子马上说:"我不知道你何时应该使用它,什么时候不应该使用它." 因此,您可以提供任何特定的,正确的示例,最好是文档,以及您可以给出的IA32或x86-64.
我最终确定了一个开源C编码的CometD库,我认为将它打开给像我这样的OSX/iOS用户是一个好主意.
为了简化OSX/iOS开发人员的工作,我想从静态C库切换到Xcode iOS Static Libary.所以我按照网上的建议,生成了一个静态的iOS兼容库.
问题是,每当我尝试使用它时,我都会收到类型错误:
架构x86_64:******的未定义符号,引用自: - **********in*******.a(*******.o)
我的库中几乎每个C函数都会重复此错误.
首先我想也许库不是x86_64 compatible,空的,或者真的没有任何x86_64符号.
所以我在图书馆检查了"lipo -info",这是答案:
确实我还在库上使用了"nm -arch x86_64",并将Xcode报告的几个未定义函数作为错误.我以为我会错,除了猜猜什么都找不到?
所以我的问题是:
如果符号存在于x86_64兼容库中,为什么Xcode会提示此错误?即使我为所有arm*/s类型编译库,我仍然会收到此x86_64错误.
我没有意识到某事或我只是做错了吗?
您的答案总是受到赞赏.
更新(这是Xcode项目的链接):https://github.com/GhostGumm/CometD-x86_64-issue
更新2:Trojanfoe先生引起了我对编译时的链接警告的注意.实际上,链接器似乎错过了加载库以寻找不存在的目录.将库移动到右侧目录,手动链接后,x86_64错误消失.
可悲的是,问题仍然存在.在成功编译之后,我尝试使用lib,但是,当我"分配"和"初始化"我的主类时,如下所示:
ZetaFactory *Client = [[ZetaFactory alloc] init];
Run Code Online (Sandbox Code Playgroud)
x86_64错误随着十亿个太阳的真正激烈而咆哮.我发布了Xcode项目的链接,请随意测试它,因为我没有更多关于如何解决它的想法,但更重要的是为什么以及在哪里有这个错误.
尽管如此,我还是会继续调查.
当编译为C程序或C++程序(对于Linux x86-64)时,小程序的程序集之间存在奇怪的差异.
有问题的代码:
int fun();
int main(){
return fun();
}
Run Code Online (Sandbox Code Playgroud)
将其编译为C程序(带gcc -O2)产生:
main:
xorl %eax, %eax
jmp fun
Run Code Online (Sandbox Code Playgroud)
但将其编译为C++ - 程序(带g++ -02)产生:
main:
jmp _Z3funv
Run Code Online (Sandbox Code Playgroud)
我觉得很困惑,C版本用0(xorl %eax, %eax)初始化主函数的返回值.
C语言的哪个特性对这种必要性负责?
编辑:确实,因为int fun(void);没有初始化eax寄存器.
如果根本没有原型fun,即:
int main(){
return fun();
}
Run Code Online (Sandbox Code Playgroud)
然后C编译器再次将eax寄存器归零.
Valgrind选择了一个条件跳转或移动取决于我的一个单元测试中未初始化的值.
检查程序集,我意识到以下代码:
bool operator==(MyType const& left, MyType const& right) {
// ... some code ...
if (left.getA() != right.getA()) { return false; }
// ... some code ...
return true;
}
Run Code Online (Sandbox Code Playgroud)
在哪里MyType::getA() const -> std::optional<std::uint8_t>,生成以下程序集:
0x00000000004d9588 <+108>: xor eax,eax
0x00000000004d958a <+110>: cmp BYTE PTR [r14+0x1d],0x0
0x00000000004d958f <+115>: je 0x4d9597 <... function... +123>
x 0x00000000004d9591 <+117>: mov r15b,BYTE PTR [r14+0x1c]
x 0x00000000004d9595 <+121>: mov al,0x1
0x00000000004d9597 <+123>: xor edx,edx
0x00000000004d9599 <+125>: cmp BYTE PTR [r13+0x1d],0x0 …Run Code Online (Sandbox Code Playgroud) 我发现手动计算%运算符__int128比内置的编译器运算符要快得多。我将向您展示如何计算模 9,但该方法可用于计算模任何其他数字。
首先,考虑内置编译器操作符:
uint64_t mod9_v1(unsigned __int128 n)
{
return n % 9;
}
Run Code Online (Sandbox Code Playgroud)
现在考虑我的手动实现:
uint64_t mod9_v2(unsigned __int128 n)
{
uint64_t r = 0;
r += (uint32_t)(n);
r += (uint32_t)(n >> 32) * (uint64_t)4;
r += (uint32_t)(n >> 64) * (uint64_t)7;
r += (uint32_t)(n >> 96);
return r % 9;
}
Run Code Online (Sandbox Code Playgroud)
测量超过 100,000,000 个随机数给出以下结果:
mod9_v1 | 3.986052 secs
mod9_v2 | 1.814339 secs
Run Code Online (Sandbox Code Playgroud)
GCC 9.3.0 with-march=native -O3用于 AMD Ryzen Threadripper 2990WX。
这是godbolt的链接。
我想问一下它在你这边的行为是否相同?(在向 GCC Bugzilla …
我们正在使用 Windows 和 Mac M1 机器使用 Docker 进行本地开发,并且需要在我们的 docker 环境中获取并安装 .deb 包。
该软件包需要 amd64/arm64,具体取决于所使用的架构。
有没有办法在 docker 文件中确定这一点
if xyz === 'arm64'
RUN wget http://...../arm64.deb
else
RUN wget http://...../amd64.deb
Run Code Online (Sandbox Code Playgroud) 我对手册页中的措辞感到困惑arch_prctl(2).具体来说,它指出:
64位段基的上下文切换相当昂贵. 通过在内核2.5或更高版本中使用modify_ldt(2)或使用set_thread_area(2)系统调用设置LDT,使用段选择器设置32位基址可能是更快的替代方法.只有当您想要设置大于4GB的基数时,才需要arch_prctl().可以使用带有MAP_32BIT标志的mmap(2)来分配前2GB地址空间中的内存.
这是否意味着使用此系统调用的进程的上下文切换将受到性能损失或具有哪些确切含义?
查看Linux内核的源代码后,似乎对于<4 GiB的地址使用LDT,而> 4 GiB地址使用特定于模型的寄存器.
case ARCH_SET_FS:
/* handle small bases via the GDT because that's faster to
switch. */
if (addr <= 0xffffffff) {
set_32bit_tls(task, FS_TLS, addr);
if (doit) {
load_TLS(&task->thread, cpu);
loadsegment(fs, FS_TLS_SEL);
}
task->thread.fsindex = FS_TLS_SEL;
task->thread.fs = 0;
} else {
task->thread.fsindex = 0;
task->thread.fs = addr;
if (doit) {
/* set the selector to 0 to not confuse
__switch_to */
loadsegment(fs, 0);
ret = wrmsrl_safe(MSR_FS_BASE, addr);
} …Run Code Online (Sandbox Code Playgroud)