这个问题和我下面的回答主要是针对另一个问题中的一个困惑的地方。

在答案的最后,有一些问题 WRT“易失性”和线程同步,我对此并不完全有信心 - 我欢迎评论和替代答案。然而,问题的重点主要涉及 CPU 寄存器及其使用方式。

有帮助吗?

解决方案

CPU 寄存器是 CPU 芯片上的小数据存储区域。对于大多数架构来说,它们是所有操作发生的主要场所(数据从内存加载、操作和推回)。

无论正在运行的线程都使用寄存器并拥有指令指针(指示下一条指令)。当操作系统交换到另一个线程时,所有 CPU 状态(包括寄存器和指令指针)都会保存在某个地方,从而有效地冻结线程的状态,以便下次恢复时使用。

当然,到处都有关于这一切的更多文档。 关于寄存器的维基百科。 关于上下文切换的维基百科。 对于初学者。编辑:或者阅读 Steve314 的回答。:)

其他提示

寄存器是一个CPU中的“工作存储器”。他们是非常快的,但非常有限的资源。通常情况下,一个CPU有一个小的固定组命名寄存器,名字是汇编语言约定为CPU的机器代码的一部分。例如,32位Intel x86 CPU的具有四个主数据寄存器命名EAX,EBX,ECX及EDX,沿着与多个索引和其它更专门的寄存器。

严格地说,这是不完全正确,这些天 - 寄存器重命名,例如,是常见的。某些处理器具有足够的寄存器,它们数得过来,而不是对其进行命名等。但是,仍存在良好的基础模型,从工作。例如,寄存器重命名是用来保存该基本模型的错觉尽管乱序执行。

在手写汇编寄存器的使用趋向于具有寄存器使用的一个简单的图案。一些变量将纯粹寄存器保存子程序的持续时间,或者它的一些重要部分。其他寄存器在读 - 修改 - 写模式中使用。例如...

mov eax, [var1]
add eax, [var2]
mov [var1], eax

IIRC,那是有效的(虽然可能低效)x86汇编代码。在摩托罗拉的68000,我可能会写...

move.l [var1], d0
add.l  [var2], d0
move.l d0, [var1]

这个时间,源通常是左参数,与右侧的目的地。 68000有8个数据寄存器(d0..d7)和8个地址寄存器(a0..a7)中,用A7 IIRC兼作堆栈指针。

在一个6510(在良好的旧的Commodore 64回)我可能会写...

lda    var1
adc    var2
sta    var1

这里的寄存器是在指令大多隐式 - 上述所有使用A(累加器)寄存器

请在这些例子原谅任何愚蠢的错误 - 我没有写的“真实”(而不是虚拟的)汇编任何显著量为至少15年。其原理是点,虽然。

寄存器的用法是特定于特定的代码片段。什么是寄存器保存基本不管它留下的最后一条指令。这是程序员的责任跟踪的是什么在代码中的每个点每个寄存器。

当调用一个子程序,无论是主叫还是被叫方必须承担保证没有冲突,这通常意味着寄存器在呼叫开始被救出来到堆栈中,然后读回末责任。发生与中断类似的问题。像谁负责保存寄存器(主叫方或被叫方)的东西通常每个子程序的文档的一部分。

一个编译器通常会决定如何在比人类程序员一个更为复杂的方式使用寄存器,但同样的原则进行操作。从寄存器到特定变量的映射是动态的,并且改变显着根据你正在寻找在哪个的代码片段。保存和恢复寄存器根据标准惯例大多处理,虽然编译器可以在某些情况下即兴“自定义调用约定”。

典型地,在函数的局部变量被想象到活在堆栈中。这与C.“自动”变量的一般规则由于“自动”是默认的,这些都是正常的局部变量。例如...

void myfunc ()
{
  int i;  //  normal (auto) local variable
  //...
  nested_call ();
  //...
}

在上面的代码中,“i”可以很好地主要保存在寄存器中。甚至可以移动从一个寄存器到另一个早在功能进展。然而,当“nested_call”之称,从该寄存器中的值几乎肯定会在堆栈上 - 或者是因为这个变量是一个堆栈变量(不是寄存器),或因为寄存器的内容保存到允许nested_call自己的工作存储

在多线程应用程序,正常局部变量是局部的特定线程。每个线程都有自己的堆栈,并且在运行时,独占使用CPU寄存器。在上下文切换时,这些寄存器被保存。无论是我n个寄存器或堆栈上,局部变量是不线程之间共享。

此基本情况是保存在多核应用程序,即使两个或多个线程可同时处于活动状态。每个芯具有它自己的堆栈和它自己的寄存器中。

存储在共享存储器中的数据需要更多的护理。这包括全局变量,无论是类和函数内静态变量和堆分配的对象。例如...

void myfunc ()
{
  static int i;  //  static variable
  //...
  nested_call ();
  //...
}

在这种情况下,值“i”被函数调用之间保留下来。主存储器的静态区域被保留以存储该值(因此命名为“静态”)。原则上,没有必要进行特别行动,以保存“我”的号召,以“nested_call”期间,并一见钟情,变量可以从任何核心运行的任何线程访问(甚至在单独的CPU)。

但是,编译器仍然在努力来优化代码的速度和大小。重复的读取和写入主存比寄存器的访问慢得多。编译器将几乎肯定选择的遵循读 - 修改 - 写上述图案,而是将保持在寄存器对相对延长的时间段值,避免了重复进行简单的读取和写入到相同的存储器中。

这意味着在一个线程中进行的修饰可以不通过另一个线程的一段时间中看到。两个线程可能最终具有约的值非常不同的想法“i”的上方。

有没有这种魔硬件解决方案。例如,存在一种用于同步线程之间的寄存器的机制。到了CPU中,变量和寄存器是完全独立的实体 - 它并不知道他们需要同步。当然有一个在不同的线程或在不同的核心上运行的寄存器之间没有同步 - 没有理由相信另一线程正在使用用于相同的目的相同的寄存器在任何特定时间

一个部分解决方案是将标记的变量为“易失性” ...

void myfunc ()
{
  volatile static int i;
  //...
  nested_call ();
  //...
}

这告诉编译器不以优化读取和写入到该变量。该处理器不具有波动性的概念。这个关键字告诉编译器生成不同的代码,做即时读取并通过分配指定的写入,而不是通过使用一个寄存器避免那些对存储器的访问,。

这是的一个多线程同步解决方案,但是 - 至少在本身。一个适当的多线程解决方案是使用某种类型的锁来管理上网本“共享资源”。例如...

void myfunc ()
{
  static int i;
  //...
  acquire_lock_on_i ();
  //  do stuff with i
  release_lock_on_i ();
  //...
}

有更回事,这里比是显而易见。原则上,“我”回到它的变量准备了“release_lock_on_i”通话,而不是写的值,它可以被保存在堆栈中。至于编译器而言,这也不是没有道理。它做栈接入反正(例如保存返回地址),因此保存在堆栈寄存器可以比写回“i”的效率更高。 - 多个高速缓存比访问的存储器中的完全独立的块友好

不幸的是,释放锁定功能,不知道该变量尚未写回内存还,所以也没有办法解决它。毕竟,这功能只是一个库调用(真正的松锁可以在更深入的嵌套调用被隐藏)和库可能您的应用程序之前已经被编译年 - 它不知道的如何它的调用者使用寄存器或堆栈。这就是为什么我们使用堆栈的重要组成部分,为什么调用约定必须是标准化(例如谁保存寄存器)。释放锁定功能无法强制呼叫者“同步”的寄存器。

同样的,你可能会重新链接一个旧的应用程序有一个新的图书馆 - 调用方不知道什么是“release_lock_on_i”做还是怎么的,它只是一个函数调用。它确实不知道它需要保存寄存器的第一背出到存储器中。

要解决这个问题,我们可以带回了“波动”。

void myfunc ()
{
  volatile static int i;
  //...
  acquire_lock_on_i ();
  //  do stuff with i
  release_lock_on_i ();
  //...
}

我们可暂时使用正常的局部变量在锁处于活动状态,以得到编译器使用一个寄存器,用于该短暂的时间的机会。原则上,虽然,一个锁应释放尽快,所以不应该在那里,很多代码。如果我们这样做,虽然,我们写我们的临时变量回到“我”之前释放锁,和“我”,确保波动,它的写回主内存。

在原则上,这是不够的。写主内存并不意味着你已经写入主存储器 - 有缓存层之间穿过,和您的数据可能在这些层一会儿的任一坐。有一个“记忆障碍”的问题在这里,我不知道这个很大 - 但好在这个问题线程同步的责任要求,如锁获取和释放呼叫上述

此存储器屏障问题不会删除的“挥发性”的关键字的需要,但是。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top