IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> RTOS学习笔记 -> 正文阅读

[嵌入式]RTOS学习笔记

目录

一、什么是RTOS

二、UCOSII

三、RT-Thread

3.1、自动初始化机制

?3.2、线程管理

3.3、线程间同步

3.4、线程间通信

3.5、内存管理

四、参考资料

4.1、RT-Thread


一、什么是RTOS

在裸机上写程序,例如51,通常分为两部分:前台系统(中断,中断嵌套)和后台系统(while)

RTOS,实时操作系统,实时性,核心内容在于:实时内核

RTOS操作系统:FreeRTOS,UCOS,RTX,RT-Thread,DJYOS等

二、UCOSII

1.UCOS2中,使用的是抢占实时调度算法,调度原则是在每一个systick系统时间,每隔一段时间进去一次中断切换任务hanlder中,都要去判断下64个任务中所有处于就绪态的任务里,谁的优先级高,就执行谁。

2.ucosii就绪表设计64个任务,8个组每个组8个任务,用一个字节的每个位表示这8个组

rdyGrp这个变量拆成8个位来使用,这个变量的值反映哪一个组有就绪任务,然后去查对应的rdytbl

3.任务切换核心:保存上下文(私有栈),恢复要去执行的上下文,然后跳转任务中执行。

4.任务状态:运行态,就绪态,休眠态,挂起态

5.解决临界区有3种方法,中断关闭和使能,中断状态保存到栈,中断状态保存在本地的局部变量

6.函数运行需要栈(存放局部变量)支持,在单片机中整个程序一个栈,OS中因为有任务(进程线程概念)所以需要很多个独立的栈(私有栈)。

7.运行起来后最少有2个任务(也就是进程的概念),状态任务(30)统计各种状态参数(CPU使用率,内存使用率),空任务(31)什么都不干。

互斥锁1个资源互斥访问,资源保护,任务同步,多个任务等待和唤醒

信号量:至少2个多资源的互斥访问

优先级翻转ABC任务(a>b>c)c拿到资源,这时候a要资源运行(拿不到所以不能运行),这个时候b事件抢占了资源运行了(a<b>c)a优先级是最高的但是b先执行了。

如何解决优先级翻转:优先级天花板(拿到资源后优先级提升到最高),优先级继承(谁拿资源就设置为谁的优先级)

旗标flag实质是16u变量,每一bit代表一个含义,任务通过判断flag状态,从而决定任务运行轨迹(主要用途是任务之间同步)

队列:缓冲,速率匹配,循环使用(接收数据太多,处理跟不上)(可以用数组也可以链表实现,栈因为简单数组来实现就是)

三、RT-Thread

1.在进入main函数之前会先去$Sub$$前缀的main,在原有函数的名字前加上$Sub$$前缀可以将原有函数劫持下来,并通过加上$Super$$前缀再调用原始函数。

3.1、自动初始化机制

  • 通过2个函数实现初始化rt_components_board_init() 与 rt_components_init()

都是使用的else后面的实现,通过把函数指针挨个放入到区间段,然后for遍历挨个实现函数的初始化

用typedef 定义了一个函数指针类型init_fn_t

typedef int (*init_fn_t)(void);
void rt_components_board_init(void)
{
#if RT_DEBUG_INIT
    int result;
    const struct rt_init_desc *desc;
    for (desc = &__rt_init_desc_rti_board_start; desc < &__rt_init_desc_rti_board_end; desc ++)
    {
        rt_kprintf("initialize %s", desc->fn_name);
        result = desc->fn();
        rt_kprintf(":%d done\n", result);
    }
#else
    const init_fn_t *fn_ptr;

    for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
    {
        (*fn_ptr)();
    }
#endif
}

/**
 * RT-Thread Components Initialization
 */
void rt_components_init(void)
{
#if RT_DEBUG_INIT
    int result;
    const struct rt_init_desc *desc;

    rt_kprintf("do components intialization.\n");
    for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++)
    {
        rt_kprintf("initialize %s", desc->fn_name);
        result = desc->fn();
        rt_kprintf(":%d done\n", result);
    }
#else
    const init_fn_t *fn_ptr;

    for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
    {
        (*fn_ptr)();
    }
#endif
}
  • 如何把函数指针放入到指定位置的,通过宏实现

查看宏代码,fn是函数名,指针

#define INIT_EXPORT(fn, level)


/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn)           INIT_EXPORT(fn, "1")

/* pre/device/component/env/app init routines will be called in init_thread */
/* components pre-initialization (pure software initilization) */
#define INIT_PREV_EXPORT(fn)            INIT_EXPORT(fn, "2")
/* device initialization */
#define INIT_DEVICE_EXPORT(fn)          INIT_EXPORT(fn, "3")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn)       INIT_EXPORT(fn, "4")
/* environment initialization (mount disk, ...) */
#define INIT_ENV_EXPORT(fn)             INIT_EXPORT(fn, "5")
/* appliation initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn)             INIT_EXPORT(fn, "6")

继续展开INIT_EXPORT,init_fn_t函数指针,其##为连接符,整体就是把fn函数的地址赋值给__rt_init_fn这个函数指针。(例如fn函数名为rti_start,函数指针就是__rt_init_rti_start

?

#define INIT_EXPORT(fn, level) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? \
? ? ? ? ? ? RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn." level) = fn
#define SECTION(x)                  __attribute__((section(x)))

展开SECTION

_attribute_((section(x))) :将作用的函数或数据放入指定名为x(level)的输入段中。(在不同的编译器中实现的方式也有所不同。)
总结:作用就是将函数 fn 的地址赋给一个 __rt_init_fn 的指针,然后放入相应 level 的数据段中。所有函数使用自动初始化宏导出后,这些数据段中就会存储指向各个初始化函数的指针。

?3.2、线程管理

  • 线程功能特点

RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行。线程切换时,先将当前线程上下文保存起来,当再切回到这个线程时,线程调度器将该线程的上下文信息恢复。

系统线程有2个,主线程(main)和空闲线程

  • 线程状态

初始状态,就绪状态,运行状态,挂起状态,关闭状态

  • 优先级和时间片

优先级:0为最高优先级,最低优先级默认分配给空闲线程(回收被删除线程的资源,在空闲线程运行时会调用该钩子函数,适合钩入功耗管理、看门狗喂狗等工作)

时间片:时间片起到约束线程单次运行时长的作用

  • 动态线程与静态线程

动态线程与静态线程的区别是:动态线程是系统自动从动态内存堆上分配栈空间与线程句柄(初始化 heap 之后才能使用 create 创建动态线程),静态线程是由用户分配栈空间与线程句柄。

3.3、线程间同步

  • 线程的同步方式

线程的同步方式有很多种,信号量(semaphore)、互斥量(mutex)、和事件集(event)等。其核心思想都是:在访问临界区的时候只允许一个 (或一类) 线程运行。

  • 信号量

线程可以获取或释放它,从而达到同步或互斥的目的。每个信号量对象都有一个信号量值和一个线程等待队列。获取信号量,值就-1,释放就+1.

可以运用在多种场合中。形成锁、同步、资源计数等关系(生产者消费者)

,单一的锁常应用于多个线程间对同一共享资源(即临界区)的访问

中断与线程间的互斥不能采用信号量(锁)的方式,而应采用开关中断的方式(中断释放信号量,线程处理数据)。

资源计数适合于线程间工作处理速度不匹配的场合(如生产者消费者)

  • 互斥量

互斥量和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。

互斥量解决优先级翻转,实现的是优先级继承协议

互斥量的使用比较单一

(1)线程多次持有互斥量的情况下。这样可以避免同一线程多次递归持有而造成死锁的问题。

(2)可能会由于多线程同步而造成优先级翻转的情况。

  • 事件集

一个事件集可以包含多个事件,利用事件集可以完成一对多,多对多的线程间同步。

一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;

线程通过 “逻辑与” 或“逻辑或”将一个或多个事件关联起来,形成事件组合。

1)事件只与线程相关,事件间相互独立:每个线程可拥有 32 个事件标志,采用一个 32 bit 无符号整型数进行记录,每一个 bit 代表一个事件;

2)事件仅用于同步,不提供数据传输功能;

3)事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。

信号量不同的是,事件的发送操作在事件未清除前,是不可累计的,而信号量的释放动作是累计的。信号量只能识别单一的释放动作,而不能同时等待多种类型的释放。

3.4、线程间通信

  • 邮箱

邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)即邮箱也可以传递指针

  • 消息队列

消息队列是一种异步的通信方式。先进先出原则 (FIFO)。

消息队列可以应用于发送不定长消息的场合,包括线程与线程间的消息交换

  • 信号

信号本质是软中断,用来通知线程发生了异步事件,用做线程之间的异常通知、应急处理。

3.5、内存管理

实时系统苛刻:内存分配的时间必须确定,随着运行时间推移内存变的碎片化,内存大小

针对上层应用和系统资源不同分为两类:内存堆管理与内存池管理

而内存堆管理又根据具体内存设备划分为三种情况:

第一种是针对小内存块的分配管理(小内存管理算法);

第二种是针对大内存块的分配管理(slab 管理算法);

第三种是针对多内存堆的分配情况(memheap 管理算法)

  • 内存堆管理

内存堆管理器可以分配任意大小的内存块,非常灵活和方便。但其也存在明显的缺点:一是分配效率不高,在每次分配时,都要空闲内存块查找;二是容易产生内存碎片。

小内存管理算法,slab 管理算法,memheap 管理算法

  • 内存池

避免内存碎片的策略是使用?内存池 + 内存堆?混用的方法。

用于分配大量大小相同的小内存块,它可以极大地加快内存分配与释放的速度,且能尽量避免内存碎片化。支持线程挂起功能:当无空闲内存时申请线程会被挂起(内存资源进行同步的场景,如播放器)

四、参考资料

4.1、RT-Thread

1.RT-Thread 官网?

2.RT-Thread 文档中心

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-07-04 23:07:47  更:2022-07-04 23:10:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 0:55:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码