Skip to content

Latest commit

 

History

History
56 lines (46 loc) · 4.26 KB

2014-04-28-atomic-operations.md

File metadata and controls

56 lines (46 loc) · 4.26 KB
layout title category description tags
post
原子操作
内核同步
原子操作...
原子操作

有些汇编语言指令具有『读』、『写』、『修改』三种类型,也就是说,它们有时候是以只读的形式从寄存器中读取内容,也有的时候以只读的形式向寄存器中写入内容。

假定运行在两个CPU上的两个内核控制路径试图通过非原子的操作来同时修改一个存储单元,首先两个CPU都试图同时进行读操作,存储器仲裁器1进行干预,只允许其中一个访问而让另一个延迟。当第一个操作已经完成后,延迟的CPU从那个存储单元正好读到一个旧的值,这没有关系。

然后两个又尝试去写一个新的值,两次操作同样被存储器仲裁器串行化并同时写入一个新的值,最终,两个写入值的操作都成功,但全局的结果是不对的。为了避免这种情况,就是要确保这样的操作在芯片级别是原子的,任何一个这样的操作都必须以单个指令执行,中间不能中断,而且避免其他的CPU访问同一存储单元,这些很小的操作叫做原子操作。

这些原子操作可以建立在其他更灵活的机制的基础上以创建临界区,80x86的原子指令的情况可以考虑如下:

  1. 进行零次或一次对齐的内存访问的汇编指令是原子的。
  2. 如果在读操作之后,写操作之前没有其他处理器占用内存总线,那么从内存中读取数据、更新数据并把更新后的数据写回内存中的这些汇编指令如inc或dec是原子的。而且在但处理系统中,永远都不会发生内存总线窃用的情况。
  3. 操作码前缀是lock字节(0xf0)的『读写修改』的汇编语言指令即使在多处理器系统中也是原子的。当控制单元监测到这个前缀时,就『锁定』内存总线,直到这条指令执行完位置。因此,当枷锁的指令执行时,其他处理器就不能访问这个内存单元。
  4. 操作码前缀是一个rep字节(0xf2,0xf3)的汇编语言指令不是原子的,这条指令强行让控制单元多次重复执行相同的指令,控制单元在执行新的循环之前要检查挂起的中断。

在使用C代码便携程序时,并不能保证编译器会为a=a+1或者a++这样的代码的操作限定为一个原子指令,所以Linux内核提供专门atomic_t类型和一些专门的函数和宏,这些函数和宏作用于atomic_t类型的变量,并当作单独的,原子的汇编语言指令来使用。

Linux中的原子操作的函数有:

函数 说明
atomic_read(v) 返回*v
atomic_set(v, i) 把*v置为i
atomic_add(i, v) 给*v增加i
atomic_sub(i, v) 从*v减去i
atomic_sub_and_test(i, v) 从*v减去i,如果结果为0则返回1,否则返回0
atomic_inc(v) 把1加到*v
atomic_dec(v) 从*v减去1
atomic_dec_and_test(v) 从*v减去1,如果结果为0则返回1,否则返回0
atomic_inc_and_test(v) 把1加到*v,如果结果为0则返回1,否则返回0
atomic_add_negative(i, v) 把i加到*v,如果结果为负,则返回1,否则返回0
atomic_inc_return(v) 把1加到*v,返回*v的值
atomic_dec_return(v) 从*v总减去1,并返回*v的值
atomic_add_return(i, v) 把i加到*v并返回
atomic_sub_return(i, v) 从*v减去i并返回

还有一些操作掩码的函数:

函数 说明
test_bit(nr, addr) 返回*addr的nr位的值
set_bit(nr, addr) 设置*addr的nr位
clear_bit(nr, addr) 清空*addr的nr位
change_bit(nr, addr) 转换*addr的nr位
test_and_set_bit(nr, addr) 设置*addr的nr位并返回原值
test_and_clear_bit(nr, addr) 清*addr的nr位并返回原值
test_and_change_bit(nr, addr) 转换*addr的nr位并返回原值
atomic_clear_mask(mask, addr) 清mask指定的*addr的所有位
atomic_set_mask(mask, addr) 设置mask指定的*addr的所有位

Footnotes

  1. 对访问RAM芯片的操作进行串行化的一种电路。