Linux里的信号量是一种睡眠锁,调用者试图获得一个已被占用的信号量时,信号量会将其推入一个等待队列,让其睡眠。当该信号量被释放后,等待队列中的任务会被唤醒,获得该信号量。
以内核mmc驱动为例看看信号量如何使用,源文件driver/mmc/card/queue.c
int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card,
spinlock_t *lock, const char *subname)
{
/* 省略··· */
sema_init(&mq->thread_sem, 1);
/* 省略··· */
}
// sema_init(struct semaphore, int); 以指定计数值都初始化动态创建信号量
mmc队列初始化时初始化了一个信号量,该信号量值初始化为1,也叫互斥信号量。
void mmc_queue_suspend(struct mmc_queue *mq)
{
struct request_queue *q = mq->queue;
unsigned long flags;
if (!(mq->flags & MMC_QUEUE_SUSPENDED)) {
mq->flags |= MMC_QUEUE_SUSPENDED;
spin_lock_irqsave(q->queue_lock, flags);
blk_stop_queue(q);
spin_unlock_irqrestore(q->queue_lock, flags);
down(&mq->thread_sem);
}
}
/**
* mmc_queue_resume - resume a previously suspended MMC request queue
* @mq: MMC queue to resume
*/
void mmc_queue_resume(struct mmc_queue *mq)
{
struct request_queue *q = mq->queue;
unsigned long flags;
if (mq->flags & MMC_QUEUE_SUSPENDED) {
mq->flags &= ~MMC_QUEUE_SUSPENDED;
up(&mq->thread_sem);
spin_lock_irqsave(q->queue_lock, flags);
blk_start_queue(q);
spin_unlock_irqrestore(q->queue_lock, flags);
}
}
在mmc queue休眠唤醒函数会调用到down和up函数,其中:
// 尝试获取信号量,如果信号量已被占用,则进入不可中断睡眠状态
down(struct semaphore *)
// 释放信号量,如果睡眠队列不为空,则唤醒其中一个任务
up(struct semaphore *)
static int mmc_queue_thread(void *d)
{
struct mmc_queue *mq = d;
struct request_queue *q = mq->queue;
current->flags |= PF_MEMALLOC;
down(&mq->thread_sem);
do {
struct request *req = NULL;
unsigned int cmd_flags = 0;
spin_lock_irq(q->queue_lock);
set_current_state(TASK_INTERRUPTIBLE);
req = blk_fetch_request(q);
mq->mqrq_cur->req = req;
spin_unlock_irq(q->queue_lock);
if (req || mq->mqrq_prev->req) {
set_current_state(TASK_RUNNING);
cmd_flags = req ? req->cmd_flags : 0;
mq->issue_fn(mq, req);
cond_resched();
if (mq->flags & MMC_QUEUE_NEW_REQUEST) {
mq->flags &= ~MMC_QUEUE_NEW_REQUEST;
continue; /* fetch again */
}
/*
* Current request becomes previous request
* and vice versa.
* In case of special requests, current request
* has been finished. Do not assign it to previous
* request.
*/
if (cmd_flags & MMC_REQ_SPECIAL_MASK)
mq->mqrq_cur->req = NULL;
mq->mqrq_prev->brq.mrq.data = NULL;
mq->mqrq_prev->req = NULL;
swap(mq->mqrq_prev, mq->mqrq_cur);
} else {
if (kthread_should_stop()) {
set_current_state(TASK_RUNNING);
break;
}
up(&mq->thread_sem);
schedule();
down(&mq->thread_sem);
}
} while (1);
up(&mq->thread_sem);
return 0;
}
留意前面初始化信号量时,初值设为1,这个叫互斥信号量,即在一个时刻仅允许一个锁持有者,与自旋锁一样。也可以初始化为大于1的非0值,这种我们称之为计数信号量,它允许在一个时刻有多个锁持有者,允许多个执行线程访问临界区,但是这个使用情况较少。
PS:号主是一名芯片原厂的Linux驱动开发工程师,深入操作系统的世界,贯彻终身学习、终身成长的理念。平时喜欢折腾,寒冬之下,抱团取暖,期待你来一起探讨技术、搞自媒体副业,程序员接单和投资理财。【对了,不定期送闲置开发板、书籍、键盘等等】。
不断探索自我、走出迷茫、找到热爱,希望和你成为朋友,一起成长~