此锁是通过原子交换指令实现的,原子交换在不同的架构上有不同的名字,在X86上,是通过指令xchgl实现的,而在ARM平台上,则提供了swp指令,无论哪种方式,原理类似,都是通过引入一种原子操作,让第一次读(发现锁空闲)和下一次写(写入数值1)操作成为一个完整的整体。期间不允许其它的核访问打断。那么便可以保证一次只能有一个核上锁成功。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
typedef struct
{
volatile unsigned long count;
} atomic_t;
void atomic_inc(unsigned long *ptr)
{
__asm__("incl %0;\n"
: "+m"(*ptr));
}
static inline int atomic_xchg(atomic_t* v, int i)
{
int ret;
asm volatile(
"xchgl %0, %1"
:"=r"(ret)
:"m"(v->count),"0"(i)
);
return ret;
}
void mutex_lock(atomic_t* v)
{
while (1 == atomic_xchg(v, 1)) {
sched_yield(); //获取锁失败后,放弃CPU
}
}
static atomic_t atomic;
unsigned long counter = 0;
void mutex_unlock(atomic_t* v)
{
v->count = 0;
}
void *read_msg_server(void *p)
{
while(1)
{
mutex_lock(&atomic);
atomic_inc(&counter);
printf("%s line %d. counter %ld.\n", __func__, __LINE__, counter);
mutex_unlock(&atomic);
}
return NULL;
}
void *write_msg_server(void *p)
{
while(1)
{
mutex_lock(&atomic);
atomic_inc(&counter);
printf("%s line %d. counter %ld.\n", __func__, __LINE__, counter);
mutex_unlock(&atomic);
}
return NULL;
}
void *final_msg_server(void *p)
{
while(1)
{
mutex_lock(&atomic);
atomic_inc(&counter);
printf("%s line %d. counter %ld.\n", __func__, __LINE__, counter);
mutex_unlock(&atomic);
}
return NULL;
}
int main(void)
{
atomic.count = 0;
pthread_t pthread1;
pthread_t pthread2;
pthread_t pthread3;
int err = pthread_create(&pthread1, NULL, read_msg_server, NULL);
if(err != 0)
{
perror("create pthread failure.");
return -1;
}
err = pthread_create(&pthread2, NULL, write_msg_server, NULL);
if(err != 0)
{
perror("create pthread failure.");
return -1;
}
err = pthread_create(&pthread2, NULL, final_msg_server, NULL);
if(err != 0)
{
perror("create pthread failure.");
return -1;
}
pthread_join(pthread2, NULL);
pthread_join(pthread1, NULL);
pthread_join(pthread3, NULL);
return 0;
}
总线锁
为了支持原子操作,以ARM指令集架构为例,ARM架构早期引入了原子交换指令SWP,该指令同事将存储器中的值读出至结果寄存器,并将另一个源操作数的值写入存储器中相同的地址,实现通用寄存器中的值和存储器中的值的交换。并且,再第一次读操作之后,硬件便将总线或者目标存储器锁定,直到第二次写操作完成之后才解锁。期间不允许其它的核访问。这便是再AHB总线中开始引入LOCK信号支持总线锁定功能的由来。
有了SWP指令和硬件锁定总线功能的支持,每个核便可以使用SWP指令进行上锁。步骤如下:
1.步骤1,使用SWP指令将锁中的值读出,并向锁中写入数值1,该过程为一个整体性的原子操作,渡河写操作之间其它核不会访问到锁。
2.步骤2,对读取的值进行判断,如果发现锁中的值为1,则意味着当前锁正在被其他的核占用,上锁失败,因此继续回到步骤1重复再读,如果发现锁中的值为0,则意味着当前锁已经空闲,同时由于SWP指令也以原子方式向其写入了数值1,则上锁成功,可以进行独占。
下图展示了一主二从和一主三从的AHB总线结构,HMASTLOCK可选,图中没有列出:
通过互斥操作解决上锁问题:
原子操作存在弊端,它会将总线锁住,导致其它的核无法访问总线,再核数众多且频繁抢锁的情况下,会造成总线长期被锁的情况,影响系统的运行性能。
因此后来ARM架构又引入了一种新的互斥类型的存储器访问指令来替代SWP指令,也就是LDREX和STREX指令。为此,AXI总线引入了互斥属性的信号用于实现此种机制。
图中的exclusive monitor有三级,每级负责自己自身范围内的独占独占访问监视,三级保证各个范围内的破坏独占访问的操作都能被监视的到。
用原子操作实现的锁在内核中广泛使用,比如下图中的代码片段来自于AMDGPU DRM设备驱动中断处理函数,在其中就使用了原子锁实现同时只处理一个中断事务的功能需求。
比较交换指令和load-link/store-condition指令
比较交换指令和和store link指令是等价的,可以互相实现,比如内核代码,ARM架构下,就用LDREX/STREX实现了compare and swap