1.原子变量
针对数值类型基础的算术运算,提供的一种保证运算原子性机制.
(1). 要求
a. 必须针对atomic_t
类型
b. 必须采用提供的原子方法实现基础算术运算原子性
(2). 原理
typedef struct { volatile int counter; } atomic_t;
通过volatile
使得每次访问counter
必须访问内存.
以atomic_add
为例说明linux
内核如何保证原子操作原子性.
通过lock
修饰汇编指令.lock
使得其后的一条内存访问汇编指令在执行时以原子方式不可分割的执行完成.
#ifdef CONFIG_SMP
#define LOCK "lock ; "
#else
#define LOCK ""
#endif
static __inline__ void atomic_add(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK "addl %1,%0"
:"=m" (v->counter)
:"ir" (i), "m" (v->counter));
}
2.自旋锁
自旋锁用于保护临界区.即同时只应该被一个访问者访问的区域.
其特性是当某个访问者申请锁时,若申请不到,此访问者以自旋方式等待.访问者会100
%占据cpu
,持续检测.
typedef struct {
volatile unsigned int lock;
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned magic;
#endif
#ifdef CONFIG_PREEMPT
unsigned int break_lock;
#endif
} spinlock_t;
(1). 定义与初始化
#define SPINLOCK_MAGIC 0xdead4ead
#ifdef CONFIG_DEBUG_SPINLOCK
#define SPINLOCK_MAGIC_INIT , SPINLOCK_MAGIC
#else
#define SPINLOCK_MAGIC_INIT /* */
#endif
#define SPIN_LOCK_UNLOCKED (spinlock_t) { 1 SPINLOCK_MAGIC_INIT }
#define DEFINE_SPINLOCK(x) spinlock_t x = SPIN_LOCK_UNLOCKED
通过DEFINE_SPINLOCK(test);
定义了一个名字为test
的自旋锁,并对其执行了初始化.
(2). 加锁
#define _spin_lock(lock) \
do { \
preempt_disable(); \
_raw_spin_lock(lock); \
__acquire(lock); \
} while(0)
#define spin_lock(lock) _spin_lock(lock)
通过spin_lock(&test);
可以申请对test
加锁.
preempt_disable();
的作用是,在中断返回时,禁止执行重新调度,来避免当前进程被切换掉.
我们分析_raw_spin_lock(lock);
#define spin_lock_string \
"\n1:\t" \
"lock ; decb %0\n\t" \
"jns 3f\n" \
"2:\t" \
"rep;nop\n\t" \
"cmpb $0,%0\n\t" \
"jle 2b\n\t" \
"jmp 1b\n" \
"3:\n\t"
static inline void _raw_spin_lock(spinlock_t *lock)
{
__asm__ __volatile__(
spin_lock_string
:"=m" (lock->lock) : : "memory");
}
上述获取锁的逻辑是:
a. 原子的递减lock->lock
.这使得内存中此变量减少1
.
b. 分析上一步结果.
若未产生负数.这意味着我们已经成功获取了锁.继续执行.
若产生负数.这意味着之前存在至少一个使用者已经获取了这个锁且未释放.进入c
.
c. 循环迭代:
空操作.
读取lock->lock
实时值,和0
比较.
若,小于等于0
,继续循环迭代.
若,大于0
,这对应锁的使用者释放了锁.跳到a
.因为多核下,可能多个等待着此时同时感知此情况.因此需让它们接下来都执行a
.首个执行成功者获得锁.后续执行者,将再次回到c
中循环迭代的等待逻辑.
__acquire(lock);
其实是一个空操作.
(3). 释放锁
#define spin_unlock(lock) _spin_unlock(lock)
通过spin_unlock(&test);
可以释放test
.
#define _spin_unlock(lock) \
do { \
_raw_spin_unlock(lock); \
preempt_enable(); \
__release(lock); \
} while (0)
我们分析_raw_spin_unlock(lock);
#define spin_unlock_string \
"movb $1,%0" \
:"=m" (lock->lock) : : "memory"
static inline void _raw_spin_unlock(spinlock_t *lock)
{
__asm__ __volatile__(
spin_unlock_string
);
}
释放锁的过程是将数值1直接设置到lock->lock
的过程.在x86
体系下,单独的读一次内存,写一次内存汇编操作无需lock
修饰也是原子的.
(4).尝试加锁
通过一个原子交换汇编指令即可.若此前未被加锁,则获得锁,同时设置了lock->lock
为0
.若此前已被加锁,再次为其设置为0
,也无错.
3.互斥锁
互斥锁用于保护临界区.即同时只应该被一个访问者访问的区域.
其特性是当某个访问者申请锁时,若申请不到,此访问者以阻塞方式等待.主动放弃cpu
,待条件满足时,由另一角色执行唤醒任务.
互斥锁在实现上可认为特殊的信号量.当某个信号量的初始资源数量为1
时,此信号量就是互斥锁.
(1). 定义与初始化
#define MUTEX_DEFAULT 0x0
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
typedef struct semaphore mutex_t;
mutex_t test;
即可定义一个名为test
的互斥锁.
#define __SEMAPHORE_INITIALIZER(name, n) \
{ \
.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), \
.count = n, \
.wait_list = LIST_HEAD_INIT((name).wait_list), \
}
static inline void sema_init(struct semaphore *sem, int val)
{
static struct lock_class_key __key;
*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}
#define mutex_init(lock, type, name) sema_init(lock, 1)
通过mutex_init(&test, MUTEX_DEFAULT, "test");
即可完成test
初始化.
(2). 申请锁
#define mutex_lock(lock, num) down(lock)
通过mutex_lock(&test, 1)
即可执行加锁申请.
void __sched down(struct semaphore *sem)
{
unsigned long flags;
might_sleep();
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
__down(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
上述过程可描述为:
a. 申请内部自旋互斥锁,以便互斥访问临界区.
b. 若sem->count
大于0
,则递减.释放锁.这时我们已经完成互斥锁的申请.
c. 若sem->count
小于等于0
,意味着此时无法得到锁.__down
内部会将当前线程作为一个等待对象放在此互斥锁的等待着链表上.然后设置自身状态为TASK_UNINTERRUPTIBLE
,并主动放弃调度.后续锁的使用者执行释放时,若释放后sem->count
大于0
,会检测是否存在等待者.存在则对其中一个等待着执行唤醒.
所以上述,在立即得到锁时继续运行.在暂时无法得到锁时,阻塞自身放弃调度,直到后续被唤醒,得到锁,再继续.
(3). 释放锁
#define mutex_unlock(lock) up(lock)
通过mutex_unlock(&test)
即可释放锁.
void __sched up(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))
sem->count++;
else
__up(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
上述过程为:
a. 获取内部自旋锁,以便互斥访问临界区.
b. 若无人等待,递增sem->count
即可完成释放.
c. 若存在等待者,唤醒一个.
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);
}
(4). 尝试加锁
int __sched down_trylock(struct semaphore *sem)
{
unsigned long flags;
int count;
raw_spin_lock_irqsave(&sem->lock, flags);
count = sem->count - 1;
if (likely(count >= 0))
sem->count = count;
raw_spin_unlock_irqrestore(&sem->lock, flags);
return (count < 0);
}
很好解释.先看能否得到锁.得不到,也立即返回.