Zephyr mutex

这篇具有很好参考价值的文章主要介绍了Zephyr mutex。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

简介

  • Mutex 实现了一个优先级继承算法,该算法可以将 Mutex 持有者的的任务优先级提高至等待队列中优先级最高的线程同等优先级。
  • Zephyr 中支持互斥操作,由于其本身是支持SMP调度机制的,关中断不能达到独占访问的目的,需要使用一个全局自旋锁来保护持有者线程优先级之类的东西,这不是 k_mutex 中的一部分。

优先级反转与优先级继承

优先级反转

  • 高优先级任务访问的临界资源被低优先级任务持有,因此高优先级任务让出CPU进入等待,而低优先级任务由于优先级较低,被中等优先级任务抢占,其他中等优先级的任务却能正常运行。
  • 从运行现象上来看,中等优先级的任务一直在执行,而高优先级任务被阻塞无法执行,就像是中等优先级任务的优先级更高,这种现象称为优先级反转。

解决方案

  • 禁止抢占
    • 在一个任务运行时,除非主动让出CPU,否则即使是比他优先级更高的线程,也不能抢占CPU。
  • 优先级继承
    • 当高优先级进程请求一个已经被被低优先级占有的临界资源时,将低优先级进程的优先级临时提升到与高优先级进程一样的级别,使得低优先级进程能更快地运行,从而更快地释放临界资源。低优先级进程离开临界区后,其优先级恢复至原本的值。
  • 优先级天花板
    • 将访问临界资源的任务的优先级提升(将该值称为优先级的天花板),所有访问该临界资源的任务,其优先级都应小于等于天花板。

Zephyr 中的解决方案

  • 在 Zephyr 中,默认采用的是基于优先级继承算法来实现加锁和解锁。

数据结构

struct k_mutex {
	/** Mutex wait queue */
	_wait_q_t wait_q;
	/** Mutex owner */
	struct k_thread *owner;

	/** Current lock count */
	uint32_t lock_count;

	/** Original thread priority */
	int owner_orig_prio;

	SYS_PORT_TRACING_TRACKING_FIELD(k_mutex)
};
  • wait_q 获取锁失败且等待时间不为0的线程会被放入等待队列中。
  • owner 所的持有线程。
  • lock_count 加锁次数,Zephyr中的 mutex 支持递归操作,同一个线程可对互斥量多次加锁,每加锁一次 lock_count 加一,每解锁一次 lock_count 减1。
  • owner_orig_prio 对互斥量进行第一次加锁时,线程的初始优先级,用于解锁时将线程的优先级进行恢复。

Mutex 初始化

  • Zephyr 提供两种初始化方式,一种是使用 Z_MUTEX_INITIALIZER 初始化,另一种是使用 k_mutex_init 函数初始化。
#define Z_MUTEX_INITIALIZER(obj) \
	{ \
	.wait_q = Z_WAIT_Q_INIT(&obj.wait_q), \
	.owner = NULL, \
	.lock_count = 0, \
	.owner_orig_prio = K_LOWEST_APPLICATION_THREAD_PRIO, \
	}
int z_impl_k_mutex_init(struct k_mutex *mutex)
{
	mutex->owner = NULL;
	mutex->lock_count = 0U;

	z_waitq_init(&mutex->wait_q);

	z_object_init(mutex);

	SYS_PORT_TRACING_OBJ_INIT(k_mutex, mutex, 0);

	return 0;
}

Mutex 加锁

  • 对 Mutex 进行加锁时分为三种情况
    • 加锁成功,返回0
    • 加锁失败,超时为0,返回错误
    • 加锁失败,超时不为0,将当前线程添加到等待队列,同时提升互斥量持有者的优先级。
  • k_mutex_lock 可以在一个线程中多次调用,实现递归互斥锁的功能,解锁次数需与加锁次数相同。
  • 下面是 k_mutex_lock 的实现:
int z_impl_k_mutex_lock(struct k_mutex *mutex, k_timeout_t timeout)
{
	int new_prio;
	k_spinlock_key_t key;
	bool resched = false;

	__ASSERT(!arch_is_in_isr(), "mutexes cannot be used inside ISRs");

	SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_mutex, lock, mutex, timeout);

	key = k_spin_lock(&lock);

	/* mutex 未上锁或持有者递归调用 k_mutex_lock */
	if (likely((mutex->lock_count == 0U) || (mutex->owner == _current))) {
		
		/* 递归调用 k_mutex_lock时优先级不变,首次上锁将 mutex->owner_orig_prio 设置为线程优先级 */
		mutex->owner_orig_prio = (mutex->lock_count == 0U) ?
					_current->base.prio :
					mutex->owner_orig_prio;

		/* 记录上锁次数,标记持有线程 */
		mutex->lock_count++;
		mutex->owner = _current;

		LOG_DBG("%p took mutex %p, count: %d, orig prio: %d",
			_current, mutex, mutex->lock_count,
			mutex->owner_orig_prio);

		k_spin_unlock(&lock, key);

		SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_mutex, lock, mutex, timeout, 0);

		return 0;
	}

	/* timeout 等于 K_NO_WAIT,返回-EBUSY */
	if (unlikely(K_TIMEOUT_EQ(timeout, K_NO_WAIT))) {
		k_spin_unlock(&lock, key);

		SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_mutex, lock, mutex, timeout, -EBUSY);

		return -EBUSY;
	}

	SYS_PORT_TRACING_OBJ_FUNC_BLOCKING(k_mutex, lock, mutex, timeout);

	/* 通过优先级继承算法获取最高优先级,Zephyr中数字越小优先级越高 */
	new_prio = new_prio_for_inheritance(_current->base.prio,
					    mutex->owner->base.prio);

	LOG_DBG("adjusting prio up on mutex %p", mutex);

	/* 如果 new_prio 比 Mutex持有者优先级更高,将调整持有线程优先级 */
	if (z_is_prio_higher(new_prio, mutex->owner->base.prio)) {
		resched = adjust_owner_prio(mutex, new_prio);
	}

	/* 将当前线程挂起等待唤醒 */
	int got_mutex = z_pend_curr(&lock, key, &mutex->wait_q, timeout);

	LOG_DBG("on mutex %p got_mutex value: %d", mutex, got_mutex);

	LOG_DBG("%p got mutex %p (y/n): %c", _current, mutex,
		got_mutex ? 'y' : 'n');

	/* 获取Mutex 成功,返回0 */
	if (got_mutex == 0) {
		SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_mutex, lock, mutex, timeout, 0);
		return 0;
	}

	/* 获取超时 */
	LOG_DBG("%p timeout on mutex %p", _current, mutex);

	key = k_spin_lock(&lock);

	/* 检查在线程超时解除挂起后,Mutex是否被释放,如果已经被释放,则不用将持有者的优先级调低
	 */
	if (likely(mutex->owner != NULL)) {
		/* 等待队列中的线程在插入时按照优先级进行排序,
		 * 头部的线程优先级最高,尾部线程优先级最低,
		 * 等待队列中头节点线程的优先级即为持有线程需要调整的优先级
		 */
		struct k_thread *waiter = z_waitq_head(&mutex->wait_q);

		/* 等待队列中没有挂起线程则直接设置为当前值,有则将其设置为 waiter 的优先级 */
		new_prio = (waiter != NULL) ?
			new_prio_for_inheritance(waiter->base.prio, mutex->owner_orig_prio) :
			mutex->owner_orig_prio;

		LOG_DBG("adjusting prio down on mutex %p", mutex);

		/* 如果调整后的优先级 new_prio 和Mutex持有者优先级不一致,说明继承的优先级更高,
		 * 或者前面提高了持有者的优先级,现在上锁超时,需要将优先级恢复至原有值,
		 * 这两种情况会改变持有者优先级,在此必须进行一次上下文切换,使新的优先级立即生效
	     */
		resched = adjust_owner_prio(mutex, new_prio) || resched;
	}

	if (resched) {
		z_reschedule(&lock, key);
	} else {
		k_spin_unlock(&lock, key);
	}

	SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_mutex, lock, mutex, timeout, -EAGAIN);

	return -EAGAIN;
}

Mutex 解锁

  • 互斥量的解锁线程必须与加锁线程相同,否则返回错误。
  • 当线程解锁时会调用 k_mutex_unlock 函数,该函数首先会将 lock_count 减1,同时通过 owner_orig_prio 恢复当前线程的优先级。
  • 此后资源便可以被其他线程抢占,会将优先级最高的线程从等待队列中取出并唤醒,将该线程设置为互斥量的持有者。
int z_impl_k_mutex_unlock(struct k_mutex *mutex)
{
	struct k_thread *new_owner;

	__ASSERT(!arch_is_in_isr(), "mutexes cannot be used inside ISRs");

	SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_mutex, unlock, mutex);

	CHECKIF(mutex->owner == NULL) {
		SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_mutex, unlock, mutex, -EINVAL);

		return -EINVAL;
	}

	/* 当前线程不是互斥量的持有者,返回错误 */
	CHECKIF(mutex->owner != _current) {
		SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_mutex, unlock, mutex, -EPERM);

		return -EPERM;
	}

	/* 尝试解锁一个未锁定的互斥量。如果当前线程等于mutex->owner,
	 * 则mutex->lock_count不能为零,因此不需要下溢检查。使用assert捕获未定义的行为。
	 */
	__ASSERT_NO_MSG(mutex->lock_count > 0U);

	LOG_DBG("mutex %p lock_count: %d", mutex, mutex->lock_count);

	/* 如果当前线程是互斥量的所有者并且计数大于1,则减少计数并返回并保持当前线程作为所有者 */
	if (mutex->lock_count > 1U) {
		mutex->lock_count--;
		goto k_mutex_unlock_return;
	}

	k_spinlock_key_t key = k_spin_lock(&lock);

	/* 如果在解锁时 mutex->owner_orig_prio 与 mutex->owner->base.prio 不相等,
	 * 说明在解锁前这段时间内,互斥量的持有者的优先级被提升过,这时需要将互斥量持有者的优先级恢复为原来的优先级
	 */
	adjust_owner_prio(mutex, mutex->owner_orig_prio);

	/* 从等待队列中将优先级最高的线程唤醒 */
	new_owner = z_unpend_first_thread(&mutex->wait_q);
	/* 将线程持有者设置为该线程 */
	mutex->owner = new_owner;

	LOG_DBG("new owner of mutex %p: %p (prio: %d)",
		mutex, new_owner, new_owner ? new_owner->base.prio : -1000);

	if (new_owner != NULL) {
		/* 由于等待队列是根据优先级进行排序的,因此唤醒的线程的优先级一定是最高的,不需要再次调整互斥量的持有者的优先级 */
		
		mutex->owner_orig_prio = new_owner->base.prio;
		arch_thread_return_value_set(new_owner, 0);
		z_ready_thread(new_owner);
		z_reschedule(&lock, key);
	} else {
		mutex->lock_count = 0U;
		k_spin_unlock(&lock, key);
	}


k_mutex_unlock_return:
	SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_mutex, unlock, mutex, 0);

	return 0;
}

文章来源地址https://www.toymoban.com/news/detail-417055.html

到了这里,关于Zephyr mutex的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++11互斥量mutex使用详解

    mutex又称互斥量,C++ 11中与 mutex相关的类(包括锁类型)和函数都声明在#include头文件中,所以如果你需要使用 std::mutex,就必须包含#include头文件。 C++11提供如下4种语义的互斥量(mutex) : std::mutex,独占的互斥量,不能递归使用。 std::time_mutex,带超时的独占互斥量,不能递

    2024年02月16日
    浏览(38)
  • 多进程共享的pthread_mutex_t

    要有一片多进程能一起访问的共享内存。共享内存如何获得本文不做介绍,请自行google。 共享内存划一段大小为sizeof(pthread_mutex_t)的内存备用,记这片内存为mutex_reserve。把这片内存初始化为全0。 用pthread_mutex_t的指针mutex_p指向mutex_reserve。 构造phtread_mutex_t的初始化属性结构体

    2024年01月21日
    浏览(36)
  • 【Rust 基础篇】Rust 互斥器(Mutex)

    在 Rust 中,互斥器(Mutex)是一种用于在多个线程之间共享数据的并发原语。互斥器提供了一种安全的方式,允许多个线程访问共享数据,但每次只允许一个线程进行写操作。本篇博客将详细介绍 Rust 中互斥器的使用方法,包含代码示例和对定义的详细解释。 在 Rust 中,我们

    2024年02月15日
    浏览(82)
  • Modern C++ std::mutex底层原理

    我时常有这样的疑问: std::mutex怎么就能保证后面的语句100%安全哪? CPU reordering就不会把这些语句重排到mutex前面执行? 而且各个CPU都是有L1、L2缓存的,如果mutex后面要访问的的变量在这些缓存中怎么办? 带着这些疑问我们先看看std::mutex是怎么实现的。 先给个图,再细说。

    2024年01月17日
    浏览(38)
  • C++ 各类mutex和读写锁性能比较

    pthread_mutex_t: 互斥锁,同一瞬间只能有一个线程能够获取锁,其他线程在等待获取锁的时候会进入休眠状态。因此pthread_mutex_t消耗的CPU资源很小,但是性能不高,因为会引起线程切换。 pthread_spinlock_t: 自旋锁,同一瞬间也只能有一个线程能够获取锁,不同的是,其他线程在

    2024年02月02日
    浏览(42)
  • C++11之超时锁timed_mutex

    超时锁:用来记录线程加锁 解锁 等竞争锁的过程,多用于调试多线程时使用。 启动多个线程,然后,竞争一把超时锁,分别打印记录加锁成功的线程和失败的线程id: 打印结果:

    2024年02月13日
    浏览(44)
  • 线程间互斥-mutex互斥锁和lock_guard

    锁+双重判断的技法 竟态条件:多线程程序执行的结果一致,不会随着CPU对线程不同的调用顺序 线程不安全的代码如下 输出的部分结果里有很多重复的数字,相当于同一张票被卖出多次,原因在于 ticketCount–; 是线程不安全的,理由如下 某一时刻ticketCount = 99,thread1此时调用

    2024年02月03日
    浏览(39)
  • Go并发编程 Goroutine、Channel、Select、Mutex锁、sync、Atomic等

    本文所有实例代码运行go版本: go version go1.18.10 windows/amd64 串行:所有任务一件一件做,按照事先的顺序依次执行,没有被执行到的任务只能等待。最终执行完的时间等于各个子任务之和。 并发:是以交替的方式利用 等待 某个任务的时间来处理其他任务计算逻辑,在计算机

    2024年02月07日
    浏览(52)
  • 从汇编角度解释线程间互斥-mutex互斥锁与lock_guard的使用

    我们创建三个线程同时进行购票,代码如下  我们再看这段代码的汇编过程  汇编代码如下: 上述汇编过程的解读为: 将ticketCount的值从内存放到寄存器eax 通过寄存器完成减法操作 将运算结果再从eax寄存器中放到内存中 可以看到,三个线程在执行代码时,每个线程在执行到

    2024年02月19日
    浏览(37)
  • OpenCV + CLion在windows环境下使用CMake编译, 出现Mutex相关的错误的解决办法

    最近在windows下面用cmake编译OpenCV的项目代码,但是一直碰到找不到mutex的问题,百思不得其解, 查看stackoverfow里面有提到,mingw64有个POSIX的东西,觉得可以一试,就到github上重新下载mingw64 Releases · niXman/mingw-builds-binaries · GitHub  选择x86_64-12.2.0-release-posix-seh-ucrt-rt_v10-rev2.7z版本,重

    2024年02月11日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包