若干汇编语言指令具有“读--修改--写”类型 。也就是说,他们访问存储单元两次,第一次读原值,第二次写新值。
假定运行在两个CPU上的两个内核控制路径试图通过执行非原子操作来同时“读--修改--写”同一个存储器单元。首先,两个CPU都试图读同一个单元,但是存储器仲裁器(对访问RAM芯片的操作进行串行化的硬件电路)插手,只允许其中一个访问而不让另一个延迟。然而,当第一个读操作已经完成后,延迟的CPU从哪个存储器单元正好读到同一个(旧)值。然而,两个CPU都试图向那个存储器单元写一新值,总线存储器访问再一次被存储器总裁器串行化,最终,两个写操作都成功。但是,全局的结果是不对的,因为两个CPU写入同一(新)值。因此,两个交错的“读--修改--写”操作了一个单独的操作。
避免由于“读--修改--写”指令引起的竞争条件的最容易的办法,就是确保这样的操作在芯片级是原子的。任何一个这样的操作都必须以单个指令执行,中断不能中断,且避免其他的CPU访问同一存储器单元。这些很小的原子操作可以建立在其他更灵活进制的基础之上以创建临界区。
在你编写C代码程序时,并不能保证编译器会为a = a +1 或甚至像 a ++这样的操作使用一个原子指令。因此Linux内核提供了一个专门的 atomic_t 类型(一个原子访问计数器)和一些专门的函数和宏, 这些函数和宏作用于atomic_t 类型的变量,并当做单独的、原子的汇编语言指令来使用。在多处理器系统中,每条这样的指令都有一个lock字节的前缀。
1. Linux 中原子操作
atomic_read(v) 返回*V
atomic_set(v, i) 把*v 置成 i
atomic_add(i,v) 给*v 增加 i
atomic_add_return(i,v) 把i 加到*v,返回*V的新值
atomic_sub(i, v) 从*v 中减去i
atomic_sub_reurn(i,v) 从*v减i, 返回*v的新值
atomic_sub_and_test(i,v) 从*v中减去i, 如果结果为0 则返回1;否则,返回0
atomic_inc(v) 把1加到*v
atomic_dec(v) 从*v减 1
atomic_inc_return(v) 把1加到*v,返回*v新值
atomic_dec_return(v) 从*v减1 ,返回* V的新值
2. Linux 中原子位操作
test_bit(nr, addr) 返回*add的第nr位的值
set_bit(nr,addr) 设置*addr的第nr位
clear_bit(nr,addr) 清*addr的第nr位
change_bit(nr, addr) 转换*addr的第nr位,并返回他的原值
3. Linux读写锁
读/写自旋锁的引入是为了增加内核的并发能力。只要没有内核控制路径对数据结构进行修改,读/写自旋锁就允许多个内核控制路径同时读取同一个数据结构,如果一个内核控制路径相对这个结构进行写操作,那么他必须首先获取读/写锁的写锁,写锁授权独占访问这个资源。当然,允许对数据结构并发读可以提高系统性能。
下图显示两个受读/写锁保护的临界区(C1 和 C2 )。内核控制路径R0和R1正在同时读取C1 中数据结构,而W0,正等待获取写锁。内核控制路径W1 正对C2 中 的数据结构进行写操作,而R2 和W2 分别等待获取读锁和写锁
linux 信号量