RTOS学习笔记

简介: RTOS学习笔记

一、什么是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事件抢占了资源运行了(ac)。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 管理算法

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

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

目录
相关文章
|
Linux
Linux中裸机串口通信的基本方法
Linux中裸机串口通信的基本方法
111 0
|
Linux 索引 Perl
zynq操作系统: Linux驱动开发Bram篇
zynq操作系统: Linux驱动开发Bram篇
1145 0
|
26天前
|
传感器 安全 Linux
linux为什么不是实时操作系统
标准Linux内核并不是实时操作系统,因为它在任务调度、中断处理和内核抢占方面无法提供严格的时间确定性。然而,通过使用PREEMPT_RT补丁、Xenomai等实时扩展,可以增强Linux的实时性能,使其适用于某些实时应用场景。在选择操作系统时,需要根据具体应用的实时性要求,综合考虑系统的性能和可靠性。
23 1
|
7月前
|
算法 Ubuntu Linux
为什么Linux不是实时操作系统
本文探讨了Linux为何不是实时操作系统(RTOS)。实时性关乎系统对事件的确定性响应时间,而Linux虽能保证调度执行的实时任务,但无法确保中断响应时间、中断处理时间和任务调度时间的确定性。中断响应时间受中断屏蔽时间影响,Linux中无法确保;中断处理时间因不支持中断嵌套而不确定;任务调度时间虽快,但调度点的限制影响实时性。Linux的定位是通用操作系统,追求平均性能而非绝对实时性。为改善实时性,Linux提供了不同抢占模型,如可抢占内核(Low-Latency Desktop)和PREEMPT-RT补丁,后者接近硬实时但牺牲了吞吐量。PREEMPT-RT正逐渐成为Linux实时增强的标准。
228 1
为什么Linux不是实时操作系统
|
7月前
|
Linux
xenomai与VxWorks实时性对比(Jitter对比)
本文对比了VxWorks和xenomai的实时性,聚焦于Jitter这一关键指标。在特定的硬件环境下(双核Cortex-A15,2GB内存),VxWorks和xenomai的读取时间戳耗时分别为0.249和0.163微秒。在时钟中断Jitter方面,VxWorks各项测试平均值约为4.066微秒,而xenomai在空载和加载情况下的平均值分别为0.516和0.768微秒。任务Jitter上,VxWorks和xenomai内核态任务的平均Jitter接近,但xenomai用户态任务Jitter较高。
283 0
xenomai与VxWorks实时性对比(Jitter对比)
|
7月前
|
安全 Linux 调度
xenomai+linux双内核下的时钟管理机制
clock是操作系统正常运行的发动机,系统利用时钟中断维持系统时间、促使任务调度,以保证所有进程共享CPU资源;可以说,“时钟中断”是整个操作系统的脉搏。那你是否好奇xenomai cobalt内核和Linux内核双内核共存的情况下,时间子系统是如何工作的?一个硬件时钟如何为两个操作系统提供服务的?本文将揭开xenomai双核系统时间机制
154 0
xenomai+linux双内核下的时钟管理机制
|
7月前
|
Linux
Linux 驱动开发基础知识——总线设备驱动模型(八)
Linux 驱动开发基础知识——总线设备驱动模型(八)
114 0
Linux 驱动开发基础知识——总线设备驱动模型(八)
|
7月前
|
Linux
Linux 驱动开发基础知识—— LED 驱动程序框架(四)
Linux 驱动开发基础知识—— LED 驱动程序框架(四)
138 0
Linux 驱动开发基础知识—— LED 驱动程序框架(四)
|
7月前
|
Linux 芯片 开发者
Linux 驱动开发基础知识——内核对设备树的处理与使用(十)
Linux 驱动开发基础知识——内核对设备树的处理与使用(十)
946 0
Linux 驱动开发基础知识——内核对设备树的处理与使用(十)
|
Linux 芯片
嵌入式Linux系列第10篇:使用CAN
嵌入式Linux系列第10篇:使用CAN