> 文章列表 > 【Linux驱动篇】同步机制(3)—信号量

【Linux驱动篇】同步机制(3)—信号量

【Linux驱动篇】同步机制(3)—信号量

Linux驱动同步机制(3)—信号量

  • 一、概述
  • 二、信号量接口
  • 三、源码分析
  • 四、使用信号量的注意事项:

一、概述

信号量同互斥锁类似,也是Linux操作系统中典型的同步手段,信号量的值可以是0、1或者n。
①当值为0时,代表没有可获得的信号量,当前进程则会进入睡眠状态,排入信号量的等待队列,直到有进程释放信号量,
②当值大于0时,代表有多余的信号量,此时进程成功获取信号量,不会进入睡眠状态

二、信号量接口

/* Please don't access any members of this structure directly */
struct semaphore {raw_spinlock_t		lock;unsigned int		count;struct list_head	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);
}extern void down(struct semaphore *sem);
extern int __must_check down_interruptible(struct semaphore *sem);
extern int __must_check down_killable(struct semaphore *sem);
extern int __must_check down_trylock(struct semaphore *sem);
extern int __must_check down_timeout(struct semaphore *sem, long jiffies);
extern void up(struct semaphore *sem);

三、源码分析

①初始化信号量:sema_init函数,第一个参数为信号量结构体指针,第二个参数为初始信号量的值,主要执行函数为__SEMAPHORE_INITIALIZER宏,对struct semaphore结构体进行初始化。

#define __SEMAPHORE_INITIALIZER(name, n)				\\
{									\\.lock		= __RAW_SPIN_LOCK_UNLOCKED((name).lock),	\\.count		= n,						\\.wait_list	= LIST_HEAD_INIT((name).wait_list),		\\
}#define DEFINE_SEMAPHORE(name)	\\struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)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);
}

②请求信号量相关的函数:down、down_interruptible和down_killable等等,down函数已经过时,推荐使用down_killable函数代替,后者可以被致命信号打断,所有信号量的核心实现函数均为__down_common,以上函数均可能导致睡眠,down_trylock接口不会导致睡眠,若获取不到将直接返回。

/* 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)
{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);
}
EXPORT_SYMBOL(down);/* down_interruptible - acquire the semaphore unless interrupted* @sem: the semaphore to be acquired Attempts to acquire the semaphore.  If no more tasks are allowed to* acquire the semaphore, calling this function will put the task to sleep.* If the sleep is interrupted by a signal, this function will return -EINTR.* If the semaphore is successfully acquired, this function returns 0.*/
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--;elseresult = __down_interruptible(sem);raw_spin_unlock_irqrestore(&sem->lock, flags);return result;
}
EXPORT_SYMBOL(down_interruptible);/* down_killable - acquire the semaphore unless killed* @sem: the semaphore to be acquired Attempts to acquire the semaphore.  If no more tasks are allowed to* acquire the semaphore, calling this function will put the task to sleep.* If the sleep is interrupted by a fatal signal, this function will return* -EINTR.  If the semaphore is successfully acquired, this function returns* 0.*/
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--;elseresult = __down_killable(sem);raw_spin_unlock_irqrestore(&sem->lock, flags);return result;
}
EXPORT_SYMBOL(down_killable);

③信号量核心函数:__down_common

  1. 首先将信号量的wait_list加入到等待队列末尾
  2. 调用signal_pending_state函数,主要是处理TASK_INTERRUPTIBLE | TASK_WAKEKILL两个信号值,判断当前进程是否信号挂起或者收到kill信号,进而是否需要立即返回到RUNNING状态。此操作应该是避免刚收到信号但是又获取不到信号量出现阻塞卡死的问题。
  3. 如果不需要立即返回,则调用__set_current_state将当前进程置为state状态,state为调用者传入的TASK_KILLABLE、TASK_UNINTERRUPTIBLE等。并且调用schedule_timeout函数主动执行CPU调度,使自身进入睡眠,让出CPU执行别的进程。

需要注意的是,在调用schedule_timeout函数之前,先执行了raw_spin_unlock_irq解自旋锁,因为在调用down_killable等函数时,执行过raw_spin_lock_irqsave函数加锁,所以必须先解一次锁,才能执行CPU调度,否则自旋锁加锁期间无法执行CPU调度,并会打印一些ERROR崩溃问题,此问题详情可见另一篇文章链接: 【Linux驱动篇】同步机制(1)— 自旋锁

/ Because this function is inlined, the 'state' parameter will be* constant, and thus optimised away by the compiler.  Likewise the* 'timeout' parameter for the cases without timeouts.*/
static inline int __sched __down_common(struct semaphore *sem, long state,long timeout)
{struct semaphore_waiter waiter;list_add_tail(&waiter.list, &sem->wait_list);waiter.task = current;waiter.up = false;for (;;) {if (signal_pending_state(state, current))goto interrupted;if (unlikely(timeout <= 0))goto timed_out;__set_current_state(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;
}

④释放信号量:up函数,类似于mutex_unlock解锁,将信号量计数加1,核心函数为__up(sem);
__up(sem)中移除信号量等待队列中的第一个元素,然后调用wake_up_process唤醒该元素保存的进程

/* 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;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);
}
EXPORT_SYMBOL(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 = true;wake_up_process(waiter->task);
}

四、使用信号量的注意事项:

  • 由于信号量可能导致睡眠,进而引起CPU调度,所以不能在中断、软中断、tasklet和内核定时器等不允许睡眠的场景下使用(定时器处理函数是作为软中断在底半部执行的)
  • 信号量无法递归获取,无法重复释放,这一点跟自旋锁、互斥锁也是一样

No pains, no gains.