在现代的Linux系统中,有非常多的并发源,因此而带来竞争情况,竞争来源于不同的进程对同一共享资源的同时存取。而Linux系统提供了一系列的处理并发和资源竞争的方法,下面介绍几个:
1、semaphore(信号量)
信号量的使用类似于一对P、V函数,通常一个想进入临界区的进程会调用P函数,返回值是可用的资源数,如果这个数大于0,负责记录资源数的变量减1进程继续,相反则进程需要等待资源被释放。而在进程释放资源的时候会调用V函数,负责记录资源数的变量加1,说明多一个空闲的资源可供进程调用。
信号量与P、V函数不同的事,他通常被用作互斥,所谓互斥就是阻止多个进程在同一临界区内运行,他的值初始化为1,并且在规定的时间内只能允许单个进程或线程持有,相当于加了一个控制互斥的锁。
先看一下定义android\kernel\include\linux\Senaphore.h:
struct semaphore {
raw_spinlock_tlock; //锁
unsigned int count; //记录资源数的变量
struct list_headwait_list; //等待该资源的进程列表
};
定义并初始化一个semaphore:
#define DEFINE_SEMAPHORE(name)struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
该函数用作定义一个名为“name”的semaphore,
并调用__SEMAPHORE_INITIALIZER(name, n)将其初始化,资源数为1,
再看__SEMAPHORE_INITIALIZER(name, n)定义:
#define __SEMAPHORE_INITIALIZER(name, n)
{
.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock),
.count = n,
.wait_list = LIST_HEAD_INIT((name).wait_list),
}
还有一种方法是对已经存在的semaphore结构体直接初始化:
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);
}
到此,关于semaphore的定义和初始化完成,下面看看怎么使用。
(1)过时的请求函数
void down(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
__down(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
这个函数用来请求一个信号量(其实就是这个信号量所标识的资源),
如果没有可用的资源就把准备调用这个资源的进程“睡眠”,直到有可用的资源释放出来。
看代码,如果请求的资源可用就把计数变量count减1,表示该资源被占用,进程继续;
如果请求的资源不可用就调用__down()把当前的进程挂到该semaphore的等待列表里。
static noinline void __sched __down(struct semaphore *sem)
{
__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
不过这个函数已经过时,很少使用。
(2)可中断的请求函数
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;
}
这个函数用来请求一个信号量(其实就是这个信号量所标识的资源),
如果没有可用的资源就把准备调用这个资源的进程“睡眠”,不过这个“睡眠”是可以中断的。
当“睡眠”被中断的时候,函数返回一个-EINTR信号,若函数成功请求到信号量则返回0,进程继续。
看代码,如果请求的资源可用就把计数变量count减1,表示该资源被占用,返回0进程继续;
如果请求的资源不可用就调用__down_interruptible()把当前的进程挂到该semaphore的等待列表里,
返回一个非0值。
static noinline int __sched __down_interruptible(struct semaphore *sem)
{
return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
(3)可结束进程的请求函数
int down_killable(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_killable(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
return result;
}
这个函数用来请求一个信号量(其实就是这个信号量所标识的资源),
如果没有可用的资源就把准备调用这个资源的进程“睡眠”,不过这个“睡眠”是可以被杀死的。
当“睡眠”被致命信号kill掉的时候,函数返回一个-EINTR信号,若函数成功请求到信号量则返回0,进程继续。
看代码,如果请求的资源可用就把计数变量count减1,表示该资源被占用,返回0进程继续;
如果请求的资源不可用就调用__down_killable()把当前的进程挂到该semaphore的等待列表里,
返回一个非0值。
static noinline int __sched __down_killable(struct semaphore *sem)
{
return __down_common(sem, TASK_KILLABLE, MAX_SCHEDULE_TIMEOUT);
}
(4)不会“睡眠”,立即返回的请求函数
int 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);
}
这个函数用来请求一个信号量(其实就是这个信号量所标识的资源),若请求成功则函数返回0,否则函数返回1。
进程不会等待资源。
(5)可以设定超时返回的请求函数
int down_timeout(struct semaphore *sem, long jiffies)
{
unsigned long flags;
int result = 0;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
result = __down_timeout(sem, jiffies);
raw_spin_unlock_irqrestore(&sem->lock, flags);
return result;
}
这个函数用来请求一个信号量(其实就是这个信号量所标识的资源),
如果没有可用的资源就把准备调用这个资源的进程“睡眠”,不过这个“睡眠”是有时间限制的。
当限制时间内没有请求到资源时,函数返回一个-EINTR信号,若函数成功请求到信号量则返回0,进程继续。
看代码,如果请求的资源可用就把计数变量count减1,表示该资源被占用,返回0进程继续;
如果请求的资源不可用就调用__down_timeout()把当前的进程挂到该semaphore的等待列表里,
返回一个非0值。
static noinline int __sched __down_timeout(struct semaphore *sem, long jiffies)
{
return __down_common(sem, TASK_UNINTERRUPTIBLE, jiffies);
}
现在看一下(1)(2)(3)(5)中,当资源不可用(即semaphore信号量请求不到时)调用的
__down_common()函数:
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 = 0;
for (;;) {
if (signal_pending_state(state, task))
goto interrupted;
if (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;
}
根据状态参数和时间限制判断是否需要把当前线程添加到等待队列或者是移除。
最后看一下信号量的释放函数:
void 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);
}
当调用释放函数up()的时候,先判断该semaphore->wait_list(等待这个信号量的等待队列)是否为空,
如果为空则说明当前没有等待使用该信号量的进程,令semaphore->count++,说明可用资源数加1;
如果等待队列不为空则调用__up()函数唤醒等待队列中的第一个进程,且将该进程从等待队列中去除。
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 = 1;
wake_up_process(waiter->task);
}
至此,关于信号量的使用告一段落,下一篇分析如何使用Completions 机制。