1. 信号量
信号量(semaphone)是保护临界区的一种常用方法。当一个数据可能被多个进程访问,但是同时只能被一个进程访问,这时会用到信号量对该数据作一个保护。
一般会将该信号量和被保护的数据组合在一起形成一个结构体,如:
Struct globalmem_dev
{
Struct cdev cdev;
Unsigned char mem[SIZE];
Struct semaphore sem;
}
信号量的初始化:
Init_MUTEX(&globalmem_devp->sem);将一个互斥的信号量初始化为1,当然也可以设定为大于1的值,使用sema_init(sem, val)。注意参数是一个指针,指向信号量变量的地址。因为要对该信号量进行修改访问。
信号量的关键参数为count。因为一直只允许一个进程访问,所以有一个锁lock要对count操作进行保护。__SPIN_LOCK_UNLOCKED((name).lock),在初始化时候为unlock状态。
struct semaphore {
spinlock_tlock;
unsigned intcount;
struct list_headwait_list;
};
#define init_MUTEX(sem)sema_init(sem, 1)
#define __SEMAPHORE_INITIALIZER(name, n)\
{\
.lock= __SPIN_LOCK_UNLOCKED((name).lock),\
.count= n,\
.wait_list= LIST_HEAD_INIT((name).wait_list),\
}
LIST_HEAD_INIT((name).wait_list),为初始化一个等待队列头。等待队列头为一个双向链表的头,prev和next都为name的地址
#define LIST_HEAD_INIT(name) { &(name), &(name) }
获得信号量
/**
* down - acquire the semaphore
* @sem: the semaphore to be acquired
*
* Acquires the semaphore. If no more tasks are allowed to acquire the
* semaphore, calling this function will put the task to sleep until the
* semaphore is released.
*
* Use of this function is deprecated, please use down_interruptible() or
* down_killable() instead.
*/
void down(struct semaphore *sem);//会导致该进程睡眠,直到释放该信号量。而且不能被信号唤醒。不建议使用。
int __must_check down_interruptible(struct semaphore *sem);
int down_interruptible(struct semaphore *sem)
{
unsigned long flags;
int result = 0;
spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
result = __down_interruptible(sem);
spin_unlock_irqrestore(&sem->lock, flags);
return result;
}
down_interruptible如果获得信号量即count 值大于零,将count--,且返回值为零。
释放信号量
/**
* up - release the semaphore
* @sem: the semaphore to release
*
* Release the semaphore.Unlike mutexes, up() may be called from any
* context and even by tasks which have never called down().
*/
void up(struct semaphore *sem)
{
unsigned long flags;
spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))
sem->count++;
else
__up(sem);
spin_unlock_irqrestore(&sem->lock, flags);
}
实际上对信号量中间的count操作前也要做获取自旋锁的操作。操作完count之后解锁。
2. 自旋锁
也是对临界资源进行互斥访问的典型手段。为了获取一个自旋锁,在CPU上运行的代码先执行一个原子操作,测试并设置某个内存变量。如果锁已经空闲,则获得这个自旋锁并继续执行。如果被占用,则在一个小的循环内重复这个测试并设置的操作,即所谓的自旋。直到该自旋锁被其它持有者释放。
定义自旋锁
Spinlock_t lock
初始化自旋锁
Spin_lock_init(&lock);
void __spin_lock_init(spinlock_t *lock, const char *name,
struct lock_class_key *key)
{
#ifdef CONFIG_DEBUG_LOCK_ALLOC
/*
* Make sure we are not reinitializing a held lock:
*/
debug_check_no_locks_freed((void *)lock, sizeof(*lock));
lockdep_init_map(&lock->dep_map, name, key, 0);
#endif
lock->raw_lock = (raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED;
lock->magic = SPINLOCK_MAGIC;
lock->owner = SPINLOCK_OWNER_INIT;
lock->owner_cpu = -1;
}
初始化其关键字为0:lock->raw_lock = (raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED;
获得自旋锁
Spin_lock(lock)
#define __LOCK(lock) \
do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)
# define __acquire(x)__context__(x,1)
Preempt_disable()应该是禁止中断,保持对cpu的独占
__context__(x,1)不断地对x进行测试,看值是否为0,如果为0,则获得锁,并加1。如果不为0则不断地进行测试等待操作。
Spin_trylock(&lock) 如果不能获得,立刻返回
释放自旋锁
Spin_unlock()
#define __UNLOCK(lock) \
do { preempt_enable(); __release(lock); (void)(lock); } while (0)
# define __release(x)__context__(x,-1)
本质上是进行一个-1操作。
3. 自旋锁和信号量
信号量:进程级,用于多个进程对资源访问的互斥。如果没能获得资源,会发生进程上下文切换。当前进程会睡眠挂起,直到资源能够访问。当然这需要结合等待队列来进行。进程上下文切换开销比较大,只有当进程占用资源比较长时间时,用信号量才是较好的选择。
自旋锁:当需要保护的临界区访问时间比较短时,用自旋锁很方便,可以节省进程上下文切换的时间。但是锁不能再临界区长时间停留,否则会降低系统效率。
可能引起阻塞的代码不能用自旋锁。否则会引起死锁。
在中断的情况下保护资源,自能使用自旋锁,避免阻塞。