linux 内核同步

来源:互联网 发布:linux简单管道 编辑:程序博客网 时间:2024/06/04 19:57
现在抢占式多核环境下,需要有同步方法来保证共享数据修改正确性。
这些数据同步方法需要处理器的支持,简单的可以理解为锁总线,
保护共享数据在同一时刻访问的排他性。


linux内核提供了两种锁,原子锁和同步锁。


原子锁指的是指令执行是以原子操作的方式,不会被打断,保证了临界资源的同步。
原子操作支持两组操作,一组是针对整数类型,一组是数据位。
针对整数类型,定义了专门的数据结构:
typedef struct { //32位处理器
int counter;
} atomic_t;


#ifdef CONFIG_64BIT
typedef struct {//64位处理器
long counter;
} atomic64_t;
#endif
以arm为例,提供的接口在arch/arm/include/asm/atomic.h 中有定义,
我们分析其中的一个例子:
//以原子操作方式对v加i
static inline void atomic_add(int i, atomic_t *v)
{
unsigned long tmp;
int result;


__asm__ __volatile__("@ atomic_add\n"
"1: ldrex %0, [%3]\n"
" add %0, %0, %4\n"
" strex %1, %0, [%3]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)
: "r" (&v->counter), "Ir" (i)
: "cc");
}


这实现里面最关键的是两个汇编指令,ldrex和strex。
ldrex加载数据时,通过锁总线方式禁止其他进程对该数据地址的访问。
strex将数据写会内存,写数据时同样禁止其他进程对该数据地址的访问。
LDREX和STREX是对内存中的一个字(Word,32bit)进行独占访问的指令。
如果想独占访问的内存区域不是一个字,还有其它的指令:
1)LDREXB和STREXB:对内存中的一个字节(Byte,8 bit)进行独占访问;
2)LDREXH和STREXH:中的一个半字(Half Word,16 bit)进行独占访问;
3)LDREXD和STREXD:中的一个双字(Double Word,64 bit)进行独占访问。
它们必须配对使用,不能混用。




同步锁有两种,一种是忙等,一种是未获得锁的进程进入睡眠状态,直到锁可用再唤醒。
自旋锁spinlock就是忙等,直到锁可用,所以自旋锁一般用在加锁时间比较短的地方。
接下来看下spin lock实现,
#define raw_spin_lock(lock) _raw_spin_lock(lock)


static inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
__raw_spin_lock(lock);
}
//spin lock 首先禁止内核抢占,避免持有锁的进程被调度出去,接着调用lock实现
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);
}
void do_raw_spin_lock(raw_spinlock_t *lock)
{
debug_spin_lock_before(lock);
if (unlikely(!arch_spin_trylock(&lock->raw_lock)))
__spin_lock_debug(lock);
debug_spin_lock_after(lock);
}
//该函数原子读取锁的状态,如果没有获得锁就循环等待,直到锁可用
static inline int arch_spin_trylock(arch_spinlock_t *lock)
{
unsigned long contended, res;
u32 slock;


do {
__asm__ __volatile__(
" ldrex%0, [%3]\n"
" mov%2, #0\n"
" subs%1, %0, %0, ror #16\n"
" addeq%0, %0, %4\n"
" strexeq%2, %0, [%3]"
: "=&r" (slock), "=&r" (contended), "=&r" (res)
: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
: "cc");
} while (res);


if (!contended) {
smp_mb();
return 1;
} else {
return 0;
}
}


对于可以进入睡眠的锁有信号量(semaphore),互斥体(mutex),完成变量(completion),
这几种同步锁,当进程无法获取时,进程进入睡眠状态,等待锁可用时将进程唤醒。
我们只分析下semaphore的实现。


/*
信号量加锁时调用这个函数,该函数先是通过自旋锁保护sem数据结构,禁止抢占跟中断,
然后将当前进程进入sem的队列中,设置进程状态为TASK_INTERRUPTIBLE,接着调度其他进程
执行。
*/
int down_interruptible(struct semaphore *sem)
{
unsigned long flags;
int result = 0;


raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
result = __down_interruptible(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);


return result;
}
static noinline int __sched __down_interruptible(struct semaphore *sem)
{
return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
static inline int __sched __down_common(struct semaphore *sem, long state,
long timeout)
{
struct task_struct *task = current;
struct semaphore_waiter waiter;


list_add_tail(&waiter.list, &sem->wait_list);
waiter.task = task;
waiter.up = false;


for (;;) {
if (signal_pending_state(state, task))
goto interrupted;
if (unlikely(timeout <= 0))
goto timed_out;
__set_task_state(task, state);
raw_spin_unlock_irq(&sem->lock);
timeout = schedule_timeout(timeout);
raw_spin_lock_irq(&sem->lock);
if (waiter.up)
return 0;
}


 timed_out:
list_del(&waiter.list);
return -ETIME;


 interrupted:
list_del(&waiter.list);
return -EINTR;
}
//信号量释放时,先从sem 链表中删除进程,然后将进程加入运行队列中,
//等待执行
static noinline void __sched __up(struct semaphore *sem)
{
struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
struct semaphore_waiter, list);
list_del(&waiter->list);
waiter->up = true;
wake_up_process(waiter->task);
}
0 0