前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【每天一个小知识】Linux信号量

【每天一个小知识】Linux信号量

作者头像
哆哆jarvis
发布2023-02-26 13:55:30
7790
发布2023-02-26 13:55:30
举报

简介

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

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

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

实例

以内核mmc驱动为例看看信号量如何使用,源文件driver/mmc/card/queue.c

代码语言:javascript
复制
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,也叫互斥信号量。

代码语言:javascript
复制
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函数,其中:

代码语言:javascript
复制
// 尝试获取信号量,如果信号量已被占用,则进入不可中断睡眠状态
down(struct semaphore *) 
// 释放信号量,如果睡眠队列不为空,则唤醒其中一个任务
up(struct semaphore *)
代码语言:javascript
复制
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驱动开发工程师,深入操作系统的世界,贯彻终身学习、终身成长的理念。平时喜欢折腾,寒冬之下,抱团取暖,期待你来一起探讨技术、搞自媒体副业,程序员接单和投资理财。【对了,不定期送闲置开发板、书籍、键盘等等】。

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-02-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 哆哆jarvis 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 信号量与自旋锁在使用上的差异
  • 实例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档