每天一个小知识Linux信号量

简介

Linux里的信号量是一种睡眠锁,调用者试图获得一个已被占用的信号量时,信号量会将其推入一个等待队列,让其睡眠。当该信号量被释放后,等待队列中的任务会被唤醒,获得该信号量。

信号量与自旋锁在使用上的差异

  1. 信号量适用于锁会被长时间占用的情况;
  2. 锁被短时间占用时不适合使用信号量,因为睡眠、维护等待队列以及唤醒所花费的开销可能比锁占用的时间还长;
  3. 因为执行线程在锁被争用时会睡眠,所以只能在进程上下文中才能获取信号量(因为中断上下文不能进行调度)。
  4. 持有信号量时可以进入睡眠,不会造成死锁,因为其他进程试图获得失败时只是会进入睡眠,最终还是会执行;
  5. 占用信号量时不能同时占用自旋锁,因为可能会进入睡眠,如果占用自旋锁,可能会导致死锁,持有自旋锁是不允许睡眠的;

实例

以内核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驱动开发工程师,深入操作系统的世界,贯彻终身学习、终身成长的理念。平时喜欢折腾,寒冬之下,抱团取暖,期待你来一起探讨技术、搞自媒体副业,程序员接单和投资理财。【对了,不定期送闲置开发板、书籍、键盘等等】。

不断探索自我、走出迷茫、找到热爱,希望和你成为朋友,一起成长~

展开阅读全文

页面更新:2024-03-03

标签:信号量   死锁   可能会   持有者   队列   初始化   线程   函数   睡眠   进程

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top