信号量(semaphore)是用于保护临界区的一种常用方法。只有得到信号量的进程才能执行临界区代码,而没有得到信号量的进程进入休眠等待状态。
1 定义信号量
struct semaphore sem;
include/linux/semaphore.h
struct semaphore{
spinlock_t lock;
unsigned int count;
struct list_head //管理所有在该信号量上睡眠的进程个数
};
2 初始化信号量
void sema_init(struct semaphore* sem, int val)
函数用于初始化信号量,并设置信号量sem的值为val。尽管信号量可以被初始化为大于1的值从而成为一个计数信号量, 设置为 1 或者 0 是信号量变成互斥锁
#define init_MUTEX(sem) sema_init(sem, 1) // 用于互斥
#define init_MUTEX_LOCKED(sem) sema_init(sem, 0) // 用于同步 completion完成量就根据信号量初始化为0实现的 如果信号量被初始化为0,则它可以用于同步,同步意味着一个执行单元的继续执行需要等待另一个执行单元完成某事,保证执行的先后顺序
使用init_MUTEX(sem) 初始化信号量时,表示信号量最初是可以被获取的。而使用init_MUTEX_LOCKED(sem) 初始化信号量时,此信号量只有先被释放才可以获取。
3 获取信号量
void down(struct semaphore *sem); // 该函数用于获取信号量sem,它会导致睡眠,因此不能在中断上下文使用
void down(struct semaphore *sem)
{
unsigned long flags;
spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0)) // 信号量大于0, 表示可以获得
sem->count--; // 获取信号量之后减 1
else
__down(sem); // 获取信号量失败, 进入休眠状态, 并将此进程插入到等待队列尾部
spin_unlock_irqrestore(&sem->lock, flags);
}
static noinline void __sched __down(struct semaphore *sem)
{
__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
struct semaphore_waiter {
struct list_head list;
struct task_struct *task;
int up;
};
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)) // 根据state确定是否可以被信号中断打断
goto interrupted;
if (timeout <= 0)
goto timed_out;
__set_task_state(task, state);
raw_spin_unlock_irq(&sem->lock);
timeout = schedule_timeout(timeout); // 让出cpu,进入休眠, timeoutt时间后再回来
raw_spin_lock_irq(&sem->lock);
if (waiter.up)
return 0; // 回来时如果可以获得信号量waiter.up 即不为零,则返回
}
timed_out:
list_del(&waiter.list);
return -ETIME;
interrupted:
list_del(&waiter.list);
return -EINTR;
}
int down_interruptible(struct semaphore *sem); // 获取信号量失败进入休眠后可以被信号打断, 跟down(struct semaphore *sem) 区别在于__down_common() 中的第二个 参数为TASK_INTERRUPTIBLE
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;
}
static noinline int __sched __down_interruptible(struct semaphore *sem)
{
return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); // 可参考上面__down_common()代码
}
这里我们可以看到,当获取信号量成功时,返回0,而获取信号量失败时,返回一个非0的值。在使用down_interruptible()函数获取信号量时,对返回值一般会进行检查,如果非0,通常立即返回-ERESTARTSYS。如:
if (down_interruptible(&sem))
return -ERESTARTSYS; // 如果获取不到信号量 进入休眠且被信号中断,则返回ERESTARTSYS 让用户空间重启进程
4 释放信号量
void up(struct semaphore *sem); // 该函数用于释放信号量sem,唤醒等待者。
void up(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))
sem->count++; // 信号量加1
else
__up(sem); //
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
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); // 唤醒该进程
}