linux 内核 ---信号量(semaphore)
信号量使用说明
(1)定义信号量
struct semaphore sem;
(2)初始化信号量
void sema_init(struct semaphore *sem, int val);
该函数初始化信号量,并设置信号量sem的值为val。
(3)获得信号量
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);
void down(struct semaphore * sem);
该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文中使用。
int down_interruptible(struct semaphore * sem);
该函数功能与down类似,不同之处为,因为down()进入睡眠状态的进程不能被信号打断,但因为down_interruptible()进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这时候函数的返回值非0。在使用down_interruptible()获取信号量时,对返回值一般会进行检查,如果非0,通常立即返回- ERESTARTSYS
int down_trylock(struct semaphore * sem);
该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0值。它不会导致调用者睡眠,可以在中断上下文中使用。
(4)释放信号量
void up(struct semaphore * sem);
该函数释放信号量sem,唤醒等待者。
信号量原理
struct semaphore { raw_spinlock_t lock; // 对于信号量的操作,比如获取信号量时的减一操作,都需要做临界区保护(critial section),通过自旋锁实现临界区 unsigned int count; struct list_head wait_list; // 当 count 为0时表示无法获取到信号量,需要把获取信号量的当前线程记录到这个链表,等 up 操作时唤醒等待信号量的线程 };
释放信号量函数时,发现链表内记录着等待此信号量的线程,调用 wake_up_process() 把进程放入调度器的运行队列,如果符合抢占要求会触发抢占,设置标志位,但是执行抢占是在固定几个抢占点,wake_up_process() 不执行抢占。
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); }
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);
}
获取信号量时,发现count为0,把当前线程记录到信号量的链表里,释放自旋锁,启调度
void 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);
}
static noinline void __sched __down(struct semaphore *sem) { __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); } 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; }
无论是释放信号量,还是获取信号量,临界区的实现都是通过自旋锁。所以在多核场景下,一个核如果获取了自旋锁操作信号量参数,其他核尝试获取锁会自旋在那。在临界区内如果需要睡眠,会先释放自旋锁,从而允许其他线程能够执行。