- 原子操作保证了指令以原子的方式执行,即执行过程不被打断,且原子操作是其它同步方法的基石。
目前optee只实现了atomic_inc32和atomic_dec32这两个原子操作。由于架构的不同使得x86和arm上原子操作的实现也不相同,且经过分析发现optee和linux在arm架构下的原子操作的实现方式基本相同。
下面以atomic_inc函数为例来比较下在这两种架构下的不同实现。
x86
static inline void atomic_inc(atomic_t *v)
{
asm volatile(LOCK_PREFIX "incl %0" : "+m" (v->counter));
}
在多处理器系统下LOCK_PREFIX使得当前处理器锁定总线来独占该内存,这时其它处理器不可以访问该内存。
arm
/* uint32_t atomic_inc32(uint32_t *v); */
FUNC atomic_inc32 , :
ldaxr w1, [x0]
add w1, w1, #1
stxr w2, w1, [x0]
cmp w2, #0
bne atomic_inc32
mov w0, w1
ret
END_FUNC atomic_inc32
ldaxr可从内存加载数据
ldaxr会将该物理地址标记为由当前处理器独占访问,并且会清除该处理器对其他任何物理地址的任何独占访问标记。
stxr可在一定条件下向内存存储数据
条件具体如下:
- 如果物理地址已被标记为由执行处理器独占访问,那么将进行存储,清除该标记,并在Rd 中返回值 0。
- 如果物理地址没有标记为由执行处理器独占访问,那么不会进行存储,且会在Rd 中返回值 1。
通过ldaxr/stxr指令实现在SMP系统中多核共享内存的互斥访问,也就是说原子操作是由独占访问指令”Load-Exclusive and Store-Exclusive”来实现的,其中最核心的地方在于系统通过exclusive monitor(一种简单的状态机)来监控独占访问。ldaxr/stxr可以保证任何情况下(包括被中断)的访问原子性。
- 在内核中,对于复杂的竞争条件,需要提供比原子操作更加复杂的同步方法,即给临界区加锁。
目前linux和optee分别使用了下面这些锁
Linux | Optee |
自旋锁(spin-lock) | 自旋锁 |
读写自旋锁 | 互斥体 |
互斥体(mutex) |
|
顺序锁 |
|
信号量 |
|
读写信号量 |
|
自旋锁(spin-lock)
内核中最常见的锁就是自旋锁。自旋锁最多只能被一个可执行线程持有。如果一个执行线程试图获取一个被已经持有的自旋锁,那么这个线程就会一直处于忙等待—旋转—等待锁再次可用。因为一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋,这样会特别浪费处理器时间,所以线程不应该长时间持有自旋锁。
在optee中,一个自旋锁通过一个unsigned int代表。并且在自旋锁的获取到释放期间需要关中断。
下面是optee中有关于spin-lock的函数。
功能 | 目的 |
cpu_spin_lock() | 加自旋锁,失败则忙等待 |
cpu_spin_trylock() | 尝试加自旋锁,失败则退出 |
cpu_spin_unlock() | 解自旋锁 |
一个简短的例子说明spin-lock加锁和解锁的过程:
互斥体(mutex)
互斥体(mutex)指得是一种实现互斥的特定睡眠锁。在optee当中一个互斥体通过struct mutex表示。当一个普通线程使用mutex加锁或者解锁时不要求关中断。mutex不能在中断上下文中使用。
下面是optee中有关于mutex的函数。
函数 | 目的 |
mutex_lock() | 对mutex加锁,如果mutex原先没有加锁的话则这是个快操作否则就要向普通域发RPC使得在普通域等待 |
mutex_unlock() | 对mutex解锁,如果等待队列没有等待的线程则这是个快操作否则就要向普通域发送RPC让其唤醒普通域的一个等待者使其回到安全域 |
mutex_trylock() | 尝试对mutex加锁,失败的话也不需要加入等待队列直接退出 |
一个简短的例子说明mutex加锁和解锁的过程:
spin-lock和mutex的比较
需求 | 加锁方式 |
低开销加锁 | 优先spin-lock |
短期锁定 | 优先spin-lock |
长期锁定 | 优先mutex |
中断上下文中加锁 | spin-lock |
持有锁需要睡眠 | mutex |
以上只是我的个人之见,如果其中有错误的话还请见谅并指教 :-D
第一次写博客有点小紧张 /(ㄒoㄒ)/~~