原子量的理解
我们知道晶体管有两种状态,通电和不通电,对应着二进制存储中的1和0,这和开关的原理是一样的,不过开关可以由人来控制,而晶体管的状态是由电来控制的;我们可以利用一种特殊的门电路组合控制一个晶体管中的状态不再受所通入电流的影响,可以说是把一位数存到了这个晶体管里面;当需要读取时,开放晶体管的后端就可以让其中存储的状态流出来;当需要改写时,通过一种特殊的门电路组合又可以控制这个晶体管受流通电流的影响,这时就可以改变存储在其中的值。
在汇编中要改写一个数据通常需要三步,从内存中取出数据到寄存器,在寄存器中修改,再将数据存回内存,即ldr r0,0x10000040 mov r1,#4 str r1,[r0] 也就是说修改一个地址处存的数值不是一步完成的,而在c语言中这是一步完成的:*p=4(假设*p=0x10000040)。
我们知道指针的本质就是对应存储的数据被解释为一个地址,而对于类似变量这种对应存储的数据被解释为一个对应类型的数值,存储的都是一个数,只是在不同环境下被解释出来的含义不一样而已。由上面解释的晶体管的物理状态可知,一个晶体管同一时间只能存储一种状态,组合起来就是一个内存块同一时间只能存储一个固定不变的值,也就是说一个指针同一时间只能指向一个固定的地址;然而一个内存块的地址却可以存储在多个指针对应的内存块中,也就是说可以同时有多个指针指向同一块内存,总结起来就是时间上唯一,空间上不唯一。
由于有这种属性,当有多个指针指向同一块内存,并通过其中某一个指针对指向的数据进行修改后,再通过其他指针取得的这个内存块的数据是那个已经被改变后的值。因为修改一个地址的值需要至少需要三步,而内核又是抢占式的,现在我们假设有两个指针A,B同时指向了同一块内存m,当我们执行线程1用指针A取出这个内存的数据准备运算时,发生了进程调度,跳到线程2执行,此时指针B也取出了这个数据准备运算时也发生了进程调度返回到线程1执行,指针A修改值后又存回这个内存,如果在进程1用的这个内存数据之前线程2执行将数据修改后存入这个内存地址,那么线程1取得的数值将不再是它本身保存的那个而且它以为它是没错的,就会发生数据错误。
针对以上假设的这种情况,经常把全局变量设置为原子量,在指令上使得对这个全局变量的取出、赋值、存回是不可被打断的,这就对应着c语言中的一条语句。
在Linux kernel里面原子量定义如下:
typedef struct {volatile int counter; } atomic_t;