我想从高层次上了解共享内存总线如何处理在多个处理器中运行的多个线程对共享数据的多个并发读写?
附言:我不需要那些细节,因为这显然是具体实现的。我只需要对幕后发生的事情有一个大致的了解。
答案1
“总线”是指组件之间的一组线路,而不是组件本身。因此,CPU 与其内存之间的线路称为内存总线;扩展卡之间的线路称为 PCI 总线,等等。
可能有多个组件驱动一条总线,但一次只能有一个组件驱动它,并且其信号会传送到总线上的所有其他组件。控制对总线的访问的组件称为总线掌握. 可以通过以下方式更改哪个组件是总线主控器:仲裁。
在多核系统中,缓存一致性非常重要。因此,当一个核心写入共享内存总线时,其他核心将窥探总线(检查写入,即使它是从另一个核心到内存)。如果写入的是该核心缓存的位置,则该核心将驱逐或替换其缓存条目。这样它就不会有过时的信息。
答案2
内存模块 (DIMM) 每次只能执行一项操作:读取或写入。这由 DIMM 边缘连接器上的协议决定;一旦您向 DIMM 发送读取或写入命令,它甚至无法“听到”另一个命令,直到前一个命令完成。
公共内存总线上的所有 DIMM 都具有这种一次只能使用一个的限制。但在许多 PC 平台上,有两个或三个内存频道,并且一个通道上的所有 RAM 每次只能集体做一件事。
在 NUMA 机器(具有多个 CPU 插槽且每个插槽都有“私有”内存的机器)上,所有这些都乘以 nCPU。即每个 CPU 都可以访问其本地内存,而不管其他 CPU 正在做什么它是内存。但是,所有 CPU 仍然可以访问所有 RAM,只是如果必须通过另一个 CPU 的话,需要的时间会稍微长一些。
从根本上讲,应用程序代码需要实现访问共享数据所需的任何序列化要求。这是在操作系统的帮助下完成的,操作系统将提供各种同步对象和函数供应用程序使用。这些对象的名称包括信号量(借用自铁路信号的名称)、互斥体(“互斥”的缩写)等。根据操作系统的不同,它们可以实现一次一个、一次 n 个、独占写入与共享读取等语义。这些函数通常涉及“等待”或“阻塞”:例如,一个线程尝试获取一个互斥体,该互斥体根据设计“保护”共享资源。如果互斥体已被其他线程拥有,则第二个(也是第 n 个)请求线程将被阻塞,即等待并且不会从获取调用返回,直到前一个所有者释放它。
应用程序可以在必要时使用这些技术;操作系统无法要求您必须拥有某个互斥锁才能访问您希望互斥锁保护的数据。这是程序设计的一个领域,很难做到正确并具有高性能。
数据库等高级子系统内置了这种编码,用于管理数据,并向程序员提供一个界面,该界面以数据库元素而不是同步对象的形式“说话”:程序员将调用函数来锁定表中的一行、单个单元格、一组行或整个表,同时执行更新。数据库引擎将使用前面描述的操作系统的功能。
在内部,操作系统通常借助内存子系统中实现的某种原子测试和修改操作来实现这些同步功能。我所说的“原子”是指一旦开始,该操作就保证在受影响内存上开始任何其他原子操作之前完成。Windows 中常用的一个示例是“互锁比较和交换”指令。x86/x64 实际上提供了一系列此类指令,可处理各种数据大小。
有一些方法可以在不使用这些技术的情况下允许对共享数据进行安全的序列化访问,但它们只适用于少数特殊情况(例如“只有一个读取器和一个写入器”)或某些类型的数据。