1. 临界区保护

临界区是仅允许一个线程访问的共享资源。它可以是一个具体的硬件设备,也可以是一个变量、一个缓冲区。多个线程必须互斥的对他们进行访问

1.1 方法一:关闭系统调度保护临界区

禁止调度

/* 调度器商锁,上锁后不再切换到其他线程,仅响应中断 */
rt_enter_critical();
/* 临界操作 */
rt_exit_critical();

关闭中断:因为所有的线程的调度都是建立在中断的基础上的,所以当关闭中断后系统将不能再进行调度,线程自身也自然不会被其他线程抢占了

rt_base_t level;
/* 关闭中断 */
level = rt_hw_interrupt_disable();
/* 临界操作 */
rt_hw_interrupt_enable(level);

1.2 方法二:互斥特性保护临界区

信号量、互斥量

2. 信号量

嵌入式系统运行的代码主要包括线程和ISR,在它们的运行过程中,它们的运行步骤有时需要同步(按照预定的先后次序运行),有时访问的资源需要互斥(一个时刻只允许一个线程访问资源),有时也需要比本次交换数据。这些机制成为进程间通信IPC。RT-Thread中的IPC机制包括信号量、互斥量、事件、邮箱、消息队列。通过IPC,可以协调多个线程(包括ISR)默契的工作。信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。每个信号量对象都有一个信号量值和一个线程等待队列。信号量的值对应信号量对象的实例数目(资源数目),如果信号量值N,则表示有N个信号量实例(资源)可被使用。当值为0时,再请求该信号量的的线程,就会被挂起在该信号量的等待队列上。

2.1 信号量的定义

struct rt_semaphore
{
struct rt_ipc_object parent; /* IPC对象继承而来 */
rt_uint16_t value; /* 信号量的值 */
} 静态信号量:struct rt_semaphore static_sem
动态信号量:rt_sem_t dynamic_sem typedef struct rt_semaphore *rt_sem_t;

2.2 信号量的操作

  1. 初始化与脱离
静态信号量:
rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag)
rt_err_t rt_sem_detach(rt_sem_t sem) //将不用的静态信号量从系统中脱离
  1. 创建与删除
判断一下返回值是不是RT_NULL
动态信号量:
rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag) //等待队列中的线程,当有资源的时候: RT_IPC_FLAG_FIFO先来先服务,先后顺序排列 RT_IPC_FLAG_PRIO按照线程优先级排列
rt_err_t rt_sem_delete(rt_sem_t sem)//释放系统资源
  1. 获取信号量
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time) //RT_WAITING_FOREVER = -1, 以系统滴答时钟为单位。即100HZ,等待10ms的倍数。如果超时则返回-RT_ETIMEOUT.切忌该函数不可在中断中调用,因为它会导致线程被挂起。只能在线程调用
rt_err_t rt_sem_trytake(rt_sem_t sem) //时间参数为0,一秒钟都不等待
  1. 释放信号量
rt_err_t rt_sem_release(rt_sem_t sem) // 既可以在线程,也可以在中断中调用。因为它不会导致线程被挂起

3. 生产者、消费者问题

两个线程,一个生产者线程和一个消费者线程,两个线程共享一个初始为空、固定大小为n的缓存区。生产者的工作是生产一段数据,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待,如此反复。只有缓冲区非空时,消费者才能从中取出数据,一次消费一段数据,否则必须等待。问题的核心是:

  1. 保证不让生产者在缓存还是满的时候仍然要向内写数据
  2. 不让消费者试图从空的缓存中取出数据

解决生产者消费者问题实际上是要解决线程间互斥关系和同步关系问题。由于缓冲区是临界资源,一个时刻只允许一个生产者放入消息,或者一个消费者从中取出消息。这里需要解决一个互斥访问的问题。同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,所以还需要解决一个同步的问题

/* 生成者线程入口 */
void producer_thread_entry(void* parameter)
{
int cnt = 0; /* 运行100次 */
while( cnt < 100)
{
/* 获取一个空位 */
rt_sem_take(&sem_empty, RT_WAITING_FOREVER); /* 修改array内容,上锁 */
rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
array[set%MAXSEM] = cnt + 1;
rt_kprintf("the producer generates a number: %d\n", array[set%MAXSEM]);
set++;
rt_sem_release(&sem_lock); /* 发布一个满位 */
rt_sem_release(&sem_full);
cnt++; /* 暂停一段时间 */
rt_thread_delay(50);
} rt_kprintf("the producer exit!\n");
} /* 消费者线程入口 */
void consumer_thread_entry(void* parameter)
{
rt_uint32_t no;
rt_uint32_t sum; /* 第n个线程,由入口参数传进来 */
no = (rt_uint32_t)parameter; sum = 0;
while(1)
{
/* 获取一个满位 */
rt_sem_take(&sem_full, RT_WAITING_FOREVER); /* 临界区,上锁进行操作 */
rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
sum += array[get%MAXSEM];
rt_kprintf("the consumer[%d] get a number: %d\n", no, array[get%MAXSEM] );
get++;
rt_sem_release(&sem_lock); /* 释放一个空位 */
rt_sem_release(&sem_empty); /* 生产者生产到100个数目,停止,消费者线程相应停止 */
if (get == 100) break; /* 暂停一小会时间 */
rt_thread_delay(10);
} rt_kprintf("the consumer[%d] sum is %d \n ", no, sum);
rt_kprintf("the consumer[%d] exit!\n");
} int semaphore_producer_consumer_init()
{
/* 初始化3个信号量 */
rt_sem_init(&sem_lock , "lock", 1, RT_IPC_FLAG_FIFO);
rt_sem_init(&sem_empty, "empty", MAXSEM, RT_IPC_FLAG_FIFO);
rt_sem_init(&sem_full , "full", 0, RT_IPC_FLAG_FIFO); /* 创建线程1 */
producer_tid = rt_thread_create("producer",
producer_thread_entry, RT_NULL, /* 线程入口是producer_thread_entry, 入口参数是RT_NULL */
THREAD_STACK_SIZE, THREAD_PRIORITY - 1, THREAD_TIMESLICE);
if (producer_tid != RT_NULL)
rt_thread_startup(producer_tid);
else
tc_stat(TC_STAT_END | TC_STAT_FAILED); /* 创建线程2 */
consumer_tid = rt_thread_create("consumer",
consumer_thread_entry, RT_NULL, /* 线程入口是consumer_thread_entry, 入口参数是RT_NULL */
THREAD_STACK_SIZE, THREAD_PRIORITY + 1, THREAD_TIMESLICE);
if (consumer_tid != RT_NULL)
rt_thread_startup(consumer_tid);
else
tc_stat(TC_STAT_END | TC_STAT_FAILED); return 0;
}

4. 互斥量

互斥量控制块是操作系统用于管理互斥量的一个数据结构。

4.1 互斥量控制块

struct rt_mutex
{
struct rt_ipc_object parent; /* IPC对象继承而来 */
rt_uint16_t value; /* 只有LOCK和UNLOCK两种值 */
rt_uint8_t original_priority; /* 上一次获得该锁的线程的优先级 */
rt_uint8_t hold; /* 该线程获取了多少次该互斥锁 */
struct rt_thread *owner; /* 当前拥有该锁的线程句柄 */
} 静态互斥量:struct rt_mutex static_mutex;
动态互斥量:rt_mutex_t dynamic_mutex;

4.2 互斥量的操作

  1. 初始化与脱离
静态互斥量
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag); //RT_IPC_FIFO RT_IPC_FLAG_PRIO
rt_err_t rt_mutex_detach(rt_mutex_t mutex);
  1. 创建与删除
动态互斥量
rt_mutex rt_mutex_create(const char *name, rt_uint8_t flag);
rt_err_t rt_mutex_delete(rt_mutex_t mutex);
  1. 获取互斥量
只能在线程中调用,且同一个线程能够take多次同一个互斥量,其成员hold+1
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time) // RT_WAITING_FOREVER = -1
  1. 释放互斥量
只能在线程中调用,不能在中断中调用。必须同一个线程获取的同一个互斥量,才能在该线程释放该互斥量
rt_err_t rt_mutex_release(rt_mutex_t mutex)

4.3 互斥量和信号量的差别

  1. 信号量可以由任何线程(以及中断)释放,它用于同步的时候就像交通灯,线程只有在获得许可的时候,才能运行,强调的是运行步骤;互斥量只能由持有它的线程释放,即只有锁上它的哪个线程,才有钥匙打开它,强调的是许可和权限
  2. 使用信号量可能导致优先级反转,互斥量可通过优先级集成的方法解决优先级反转问题

5. 线程优先级翻转

当一个高优先级线程试图通过某种互斥IPC对象机制访问共享资源时,如果该IPC对象已经被一个低优先级的线程所持有,而且这个低优先级线程运行过程中可能又被其他一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞的情况。导致高优先级的实时性得不到保证

5.1 优先级继承

在RT-Thread中,通过互斥量的优先级继承算法,可有有效解决优先级翻转问题。优先级继承是指提高某个占有某种共享资源的低优先级线程优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,从而得到更快地执行然后释放共享资源,当这个低优先级线程释放该资源时,优先级重新回到初始设定值。继承优先级的线程,避免了系统共享资源被任何中间优先级的线程抢占

优先级翻转线向提醒编程人员对共享资源进行互斥访问的代码段应尽量短。让低优先级线程尽快完成工作,释放共享资源

参考文献

  1. RT-Thread视频中心内核入门
  2. RT-Thread文档中心

本文作者: CrazyCatJack

本文链接: https://www.cnblogs.com/CrazyCatJack/p/14408842.html

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

关注博主:如果您觉得该文章对您有帮助,可以点击文章右下角推荐一下,您的支持将成为我最大的动力!


RT-Thread学习笔记2-互斥量与信号量的更多相关文章

  1. 1.linux系统基础笔记(互斥量、信号量)

    操作系统是很多人每天必须打交道的东西,因为在你打开电脑的一刹那,随着bios自检结束,你的windows系统已经开始运行了.如果问大家操作系统是什么?可能有的人会说操作系统就是windows,就是那些 ...

  2. 【C#编程基础学习笔记】6---变量的命名

    2013/7/24 技术qq交流群:JavaDream:251572072  教程下载,在线交流:创梦IT社区:www.credream.com [C#编程基础学习笔记]6---变量的命名 ----- ...

  3. linux 线程的同步 一 (互斥量和信号量)

    互斥量(Mutex) 互斥量表现互斥现象的数据结构,也被当作二元信号灯.一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源. ...

  4. C#线程学习笔记六:线程同步--信号量和互斥体

    本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/23/Mutex_And_Semaphore.html,记录一下学习过程以备后续查用.     ...

  5. 【转】【Linux】 临界区,互斥量,信号量,事件的区别

    原文地址:http://blog.itpub.net/10697500/viewspace-612045/ Linux中 四种进程或线程同步互斥的控制方法1.临界区:通过对多线程的串行化来访问公共资源 ...

  6. CPP-基础:windows api 多线程---互斥量、信号量、临界值、事件区别

    http://blog.csdn.net/wangsifu2009/article/details/6728155 四种进程或线程同步互斥的控制方法:1.临界区:通过对多线程的串行化来访问公共资源或一 ...

  7. thread学习笔记--BackgroundWorker 类

    背景: 在 WinForms 中,有时要执行耗时的操作,比如统计某个磁盘分区的文件夹或者文件数目,如果分区很大或者文件过多的话,处理不好就会造成“假死”的情况,或者报“线程间操作无效”的异常,或者在该 ...

  8. Boost Thread学习笔记三

    下面先对condition_impl进行简要分析.condition_impl在其构造函数中会创建两个Semaphore(信号量):m_gate.m_queue,及一个Mutex(互斥体,跟boost ...

  9. Boost Thread学习笔记二

    除了thread,boost种:boost::mutexboost::try_mutexboost::timed_mutexboost::recursive_mutexboost::recursive ...

随机推荐

  1. JS实现鼠标移入DIV随机变换颜色

    今天分享一个在 JavaScript中,实现一个鼠标移入可以随机变换颜色,本质就是js的随机数运用. 代码如下: <!DOCTYPE html> <html> <head ...

  2. (013)每日SQL学习:日期的各种计算

    1.确定两个日期之间的工作日天数 --确定两个日期之间的工作日天数with x0 as (select to_date('2018-01-01','yyyy-mm-dd') as 日期 from du ...

  3. oracle创建表并加索引

    一个语句创建Oracle所有表的序列 -- 动态创建序列 2 declare 3 cursor c_job is 4 select TABLE_NAME from user_tables; 5 6 c ...

  4. 前序遍历 排序 二叉搜索树 递归函数的数学定义 return 递归函数不能定义为内联函数 f(x0)由f(f(x0))决定

    遍历二叉树   traversing binary tree 线索二叉树 threaded binary tree 线索链表 线索化 1. 二叉树3个基本单元组成:根节点.左子树.右子树 以L.D.R ...

  5. 大数据之Hadoop技术入门汇总

    今天,小编对Hadoop入门学习知识进行了汇总,帮助大家更好地入手大数据.小编关于Hadoop入门总共发写了12篇原创文章,文章是参照尚硅谷大数据视频教程来进行撰写的. 今天,小编带你解锁正确的阅读顺 ...

  6. qbxt 学习笔记 10.2

    写在前面 昨晚网络咕了,而且比较晚,没交作业.解题报告写成书面的了,代码另发 + 博客. 目录 写在前面 1. 爬山算法 概述 示例 2. 模拟退火 概述 3. Meet in the Middle ...

  7. Opencart 后台getshell

    朋友实战中遇到的,帮忙看后台getshell. 修改日志文件,但是奈何找不到warning这类等级的错误,没办法控制写入的内容,通过sql报错能写入了,但是尖括号却会被实体,使用16进制一样会实体.. ...

  8. 洛谷 P6362 平面欧几里得最小生成树

    题目描述 平面上有 \(n\) 个点,第 \(i\) 个点坐标为 \((x_i, y_i)\).连接 \(i, j\) 两点的边权为 \(\sqrt{(x_i - x_j) ^ 2 + (y_i - ...

  9. 最简单直接地理解Java软件设计原则之接口隔离原则

    理论性知识 定义 接口隔离原则, Interface Segregation Principle,(ISP). 一个类对应一个类的依赖应该建立在最小的接口上: 建立单一接口,不要建立庞大臃肿的接口: ...

  10. SSH密码暴力破解及防御实战

    SSH密码暴力破解及防御实战 一.Hydra(海德拉) 1.1 指定用户破解 二.Medusa(美杜莎) 2.1 语法参数 2.2 破解SSH密码 三.Patator 3.1 破解SSH密码 四.Br ...