Esper 发布的文章

大意了,没有闪

起因是在VS新建了一个C++项目,然后试图引用一个用C写的函数库。结果编译运行的时候报错了,提示无法解析的外部符号。根据常识,这个问题一般与C++的函数重载有关,但是在仔细检查了库函数和调用时函数的签名之后发现并没有问题。用grepdumpbin对着编译出来的.lib静态库文件捣腾了老半天,也愣是没有找出问题所在。

左思右想,唯一的端倪在于用dumpbin观察.lib静态库文件时发现,编译出来的库函数是没有带C++后缀的,而报错的地方所调用的库函数是带了C++后缀的,这也正是导致出现函数名不匹配的地方。无奈之下只能去网上冲浪,带着我也描述不清的问题试图找出解决方法。

在万能的StackOverflow上看到了这个回答之后才意识到,函数名是在编译期确定的。由于头文件本身作为源文件参与编译,编译器只能通过源文件信息来确定这是C还是C++代码。很显然,.c后缀的被MSVC作为C代码编译,而.cpp文件则是作为C++代码编译。

而正是这细微的编译规则差异导致了本次的问题:在库函数源代码中,由于是以.c源文件的形式参与编译,遵循了C语言不允许函数重载的规则,编译出来的函数名就是头文件中所定义的函数名;而在我写的调用程序中,因为使用了.cpp格式的文件,代码使用了C++规则编译,因而给头文件中定义的函数名加上了C++的小尾巴(如@UAEXXZ这种)。在之后的链接过程中再用C++风格的函数名去匹配C风格的函数体,由于两者名字不一致,就产生了无法解析的外部符号问题。

解决方法也非常简单,在写C/C++混合调用的程序时,被调用者(主要是头文件)最好加上用#ifdef __cplusplus包装的extern "C"标记,以确保最大的兼容性

据MathWorks所述,在Matlab 2018之后就允许普通用户从官网下载安装器,制作离线安装包。然而,官方下载器又做的非常脑瘫,只支持单线程顺序下载,并且还没有暂停功能。

然而这些都是题外话,好不容易用100M的校园网带宽挂完了下载之后,安装出了幺蛾子:一打开,先是弹出一个错误界面,然后进入安装器界面后给出模棱两可的错误信息:There was an error communicating with the backend services.。起初我以为是某墙的问题,后来发现断网也无法避免。上网找了半天,没几个和Matlab安装有关的报错信息。最后在官方论坛某帖子的回复里看到了一小段回复(链接):

When I received this error, it was not due to proxy settings or firewall settings. Rather, when I downloaded the installer, the download location was on a remote drive (a windows remote profile DFS location). I copied the setup file to the local drive, and it ran fine.

大意是说,下载的Matlab安装程序不能放在外部文件系统上,包括可移动设备和网络驱动器等。而我恰好为了做备份,直接把Matlab下载到了外置的移动硬盘上,然后直接从移动硬盘上启动了安装程序,从而导致了该问题;而回复者通过把安装程序拷回内置磁盘的方式解决了问题。但是动辄近20GB的完整安装文件来回移动,可以说是duck不必。在Windows下,我们可以尝试活用mklink,把外置磁盘中的文件夹软链接至内置磁盘中,让Matlab以为它是从内置磁盘读取的数据。

事实证明这个方法是有效的。从内置磁盘的软连接中重新启动Matlab安装程序,启动界面正常了,报错也消失了,问题解决。

一开始看内存安全的时候,了解到从电路上窃取信息这种操作是真实存在的,专业术语称作Bus Snooping总线嗅探。然后当时就理所当然地认为,嗅探属于网络攻击的一种,应该尽可能防范。

然而gem5又给我上了一课(没错还是它)。在阅读代码的时候,看到src/mem/packet.hh的注释里提到了Snoop Flags。这是个啥?难道gem5还支持安全攻击建模的,之前咋没听说过?

查阅资料后,咕果搜索结果中出现了缓存一致性这个关键词。进一步了解后发现,嗅探是当今多核时代下,一种朴素而常见的、确保缓存一致性的通信手段。

根据英文wiki,对于内存来说,有两种常见的内存一致性的级别:

一是Cache Coherent缓存一致。在这种一致性级别下,要求对于单一内存地址的数据,满足程序所有线程的执行顺序(反过来想,我们是否可以理解为,某些内存地址的数据操作是不需要遵循全局顺序的,比如只有单个线程使用的私有内存区块?)。另一种定义是,如果对于同一内存地址的写操作是顺序排列的(即同一时刻只有一个写操作),那么该系统具有缓存一致性。

另一个则是Sequentially Cosistent顺序一致。在这种一致性级别下,要求程序的整个内存区块中的内存操作都必须满足所有线程的总体执行顺序。

然后看到了一篇介绍缓存一致性的文章(原文/译文),了解到了在MESI缓存一致性协议中,是如何通过总线嗅探来实现缓存区块的状态转移的。

MESI协议的名称由协议本身定义状态机的四个状态首字母缩写组成,其中MModified、SShared、IInvalid三个状态分别对应单核系统下缓存的三种状态:脏(dirty)、干净(clean)和无效(invalid)。MESI遵循了缓存一致性的原则,对于同一内存地址,同一时刻只允许一个线程具有写的权利(处于M状态)。

由于要尽可能地提升多核系统的工作效率,经典设计方案是为每个核分配一块独占的缓存空间,而这也是导致传统缓存状态机出现问题的关键所在:若是继续使用单核的强同步策略(写入即同步)的话,同步缓存的操作将大幅降低处理器运行效率;不使用同步策略的话,则会带来经典的写前读/写后读问题。

MESI协议则可以视作一种弱同步策略,仅在特殊事件发生的时候执行同步操作。这个特殊事件直接映射成了MESI中的第四个状态EExclusive,也就是独占状态。MESI规定,只有在进入E状态后,才能进入M状态。在MESI中,需要嗅探的内容就是进入这个E状态的内存操作。

关于MESI协议的具体内容可以参见wiki,这里就懒得写了

翻译自 http://iram.cs.berkeley.edu/kozyraki/project/ee241/report/crossbar.html

The memory crossbar connects the multiple Load/Store units of the processor to the multiple memory sections. The L/S units are capable of issuing either a load or a store on every cycle, and as a result, the memory sections should, ideally, be capable of processing these requests at the same rate. In order words, if there are n L/S units, each issuing one memory accesses per cycle, then the crossbar must have a peak bandwidth of at least n words per cycle.

内存交叉开关是一种用于将多个处理器(内存)读写单元与多个内存区块相连接的结构。由于单个(内存)读写单元可在单个时钟周期内完成一次读或写操作,我们可以认为,在理想状态下,被连接的(单个)内存区块也应该具有相同的请求处理速度(即每时钟周期处理一个读或写请求)。也可以理解为,如果(在使用了内存交叉开关的内存系统中)CPU有n个(内存)读写单元,每个单元每时钟周期可完成一次读或写请求的话,那么在该存储交叉开关处至少有等价于每周期n次读写请求的最大带宽。

largexbar.gif

上图是该文章给出的内存交叉开关一种实现的图例。图中上侧为内存读写单元,下侧为内存模块,中部的横线表示总线,一条线就是一根完整的总线;这里有每种四根则表示有四组相同的总线,实心点表示内存读写单元,叉则表示开关。由于总线只有单一的所有权,一个周期内则只能供一个内存读写单元和一个内存模块使用,也正是上文最大n倍单总线吞吐量的原理。