linux-android 中内核同步方法

今天下午有点空,就不写代码吧,整理一下系统知识,以便自已理一下这些小知识点。


使用锁的大原则: 针对数据而不是代码加锁!!!
需要使用特定的锁来保护自已的共享数据,比如对struct foo中的lo_lock变理加锁,无论何时访问数据,一定要保证数据是安全的,而保证数据安全则意味着操作前使用恰当的锁,完成操作后再释放它。



1、原子操作
顾名思义就是保证指令以原子方式执行而不被执行过程所打断,原子操作就是不能够被分割的指令能够保证多个线程同时读取或增加变量的行为包含在一个单步中执行,绝对不可能并发地访问同一个变量。重点用于临界区共享的变量场合。

原子操作类型:atomic_t
typedef struct {
int counter;
} atomic_t;

为何不直接使用int代替这种类型呢?基于两个原因:
1、让原子操作函数只接受 atomic_t 这种类型操作数
2、可以确保编译器不对这种类型进行优化,每次获取正确的内存地址而非别名

最常见的用途就是实现计数器,使用原子操作并检查结果,常见函数如下:
ATOMIC_INIT(0) 初始化值
atomic_inc(atomic_t *v)   v值++
atomic_dec(atomic_t *v)   v值--
atomic_add(int i, atomic_t *v) v值+i
atomic_sub(int i, atomic_t *v) v值-i
atomic_sub_and_test(i, v) v值-i,如果结果等于0返回真,否则返回假
atomic_dec_and_test(v)    v值-1,如果结果等于0返回真,否则返回假
atomic_inc_and_test(v)    v值+1,如果结果等于0返回真,否则返回假

原子操作通常是内联函数,往往是通过内嵌汇编指令来实现。
以i++来说最少有三条指令:
  从内存中取数据到寄存器
  对寄存器中的数据进行递增操作,结果还在寄存器中
  寄存器的结果写回内存
  如果按正常的顺序连续执行是没有问题的,但在多线程时就不能保证了,这就需要原子操作来保证。


android系统中引申使用:
使用mutex可以保证原子操作但其带来的cpu消耗大,需要从用户态转入内核态,执行完成再回到用户态。
而在android中大量的使用的原子操作,比如dalvik进行dex优化生成代码时。如果原子操作代码有问题,
则很可能发生:DexOpt: incorrect opt magic number ... ,所以务必正常并高效实现!!!


这是一个轻量级的操作,可以不用在多线程中使用锁,提高效率
#define poll_flag_set(d,v) (android_atomic_write((v), &poll_flag))
#define poll_flag_is(d, v) (android_atomic_release_load(&poll_flag) == (v))
#define poll_ch_none(d)      (android_atomic_release_load(&poll_ch_size) == 0)
#define poll_ch_inc(d) (android_atomic_inc(&poll_ch_size))
#define poll_ch_dec(d) (android_atomic_dec(&poll_ch_size))

相关实现代码见:
X86平台的实现在system/core/libcutils/Atomic.c
ARM平台的实现在system/core/libcutils/atomic-android-arm.S汇编代码中
对于其它平台如mips或者sh4也需要相应实现这些汇编函数代码

2、旋转锁(自旋锁)
如果锁被占用,尝试获取锁的线程进入busy-wait状态,即CPU不停的循环检查锁是否可用。
自旋锁适合占用锁非常短的场合,避免等待锁的线程sleep而带来的CPU两个context switch的开销。


自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

一个被争取的自旋锁使得请深圳市它的线程在等待锁重新可用时自旋(特别浪费CPU时间),这种行为就是自旋锁的要点,所以自旋锁不应该被长时间持有。持有自旋锁的时间最好小于完成两次上下文切换的耗时。

spinlock mr_lock = SPIN_LOCK_UNLOCKED;

spin_lock(&mr_lock); 或者 spin_trylock(&mr_lock);
... 临界区代码 ...
spin_unlock(&mr_lock);


spin_trylock(lock)
该宏尽力获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则不能立即获得锁,立即返回假。
它不会自旋等待lock被释放。
spin_lock(lock)
该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋在那里,直到该自旋锁的
保持者释放,这时,它获得锁并返回。总之,只有它获得锁才返回。
spin_unlock(lock)
该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用。如果spin_trylock返回假,表明没有获得自旋锁,
因此不必使用spin_unlock释放。

这也是一个轻量级的操作,加锁时会分两个阶段,首先check一个flag,然后再决定是否切换上下文,可加锁则
立即返回,可以快速返回,而不会像mutex直接加锁的效率更高。


实现的代码如下,可以看出进行两级操作,先try(_lock)然后再执行lock_acquired操作。
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}


#define LOCK_CONTENDED(_lock, try, lock)\
do { \
if (!try(_lock)) {\
lock_contended(&(_lock)->dep_map, _RET_IP_);\
lock(_lock); \
} \
lock_acquired(&(_lock)->dep_map, _RET_IP_);\
} while (0)


所以如果加锁时间不长并且代码不会睡眠(比如中断处理程序),利用自旋锁是最佳选择。

android系统中引申使用:
对于碰撞机率很低的情况,使用自旋锁效率相当高,因为其有两级检测,能够快速返回。
而为了避免入内核进行执行,可以使用原子操作来实现自旋锁功能。

#define spinlock_tryget(L)  \
(android_atomic_cmpxchg(0, 1, (L)) == 0)
#define spinlock_get(L)\
do{while(!spinlock_tryget(L)){usleep(0);}}while(0)
#define spinlock_release(L)\
android_atomic_cmpxchg(1, 0, (L))


#define channel_try_lock(d,chi) spinlock_tryget(&(d)->poll_ch[chi].lock)
#define channel_lock(d,chi)  spinlock_get(&(d)->poll_ch[chi].lock)
#define channel_unlock(d,chi) spinlock_release(&(d)->poll_ch[chi].lock)

3、Semaphores信号量
如果锁被占用,尝试获取锁的线程进入sleep状态,CPU切换到别的线程。当锁释放之后,
系统会自动唤醒sleep的线程。信号量适合对锁占用较长时间的场合。
是一个重量级操作,尽量少用吧。

4、mutex 互斥锁
Mutex是互斥类,用于多线程访问同一个资源的时候,保证一次只有一个线程能访问该资源。
以下是使用的锁函数
int pthread_mutex_init(pthread_mutex_t *mutex,
                       const pthread_mutexattr_t *attr)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_destroy(pthread_mutex_t *mutex)
同一时间只能有一个任务持有互斥锁,而且只有这个任务可以互斥锁进行解锁。互斥锁不能进行递归锁定或解锁,不能用于中断上下文,比信号量效率高。


你可能感兴趣的:(多线程,android,汇编,struct,任务,平台)