rtt学习

内核

线程调度:除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的。有256个优先级,0最高,STM32默认36个

时钟管理:分为单次和周期,需要设置定时器回调函数

线程同步:使用信号量、互斥量、事件集实现,简单来说是获取与释放,学长说一般信号量能够实现互斥量的工作

线程通信:邮箱和消息队列。邮箱中一封邮件的长度固定为 4 字节大小;消息队列能够接收不固定长度的消息,并把消息缓存在自己的内存空间中。

内存管理:静态内存池管理及动态内存堆管理。静态内存池系统对内存块分配的时间是恒定的。如果为空则会挂起或阻塞线程等待内存释放

线程管理

线程运行时,会认为自己独占CPU

功能特点

分为系统线程(由RTT内核创建)和用户线程(由应用程序创建)

线程属性:线程控制块、线程栈、入口函数

特点:抢占式,优先查找优先级最高的线程

如果是中断让高优先级的线程满足条件,会在中断函数完成之后挂起中断线程,运行优先级高的线程

切换线程时会保留当前线程的上下文

工作机制

线程控制块

/* 线程控制块 */
struct rt_thread
{
    /* rt 对象 */
    char        name[RT_NAME_MAX];     /* 线程名称 */
    rt_uint8_t  type;                   /* 对象类型 */
    rt_uint8_t  flags;                  /* 标志位 */

    rt_list_t   list;                   /* 对象列表 */
    rt_list_t   tlist;                  /* 线程列表 */

    /* 栈指针与入口指针 */
    void       *sp;                      /* 栈指针 */
    void       *entry;                   /* 入口函数指针 */
    void       *parameter;              /* 参数 */
    void       *stack_addr;             /* 栈地址指针 */
    rt_uint32_t stack_size;            /* 栈大小 */

    /* 错误代码 */
    rt_err_t    error;                  /* 线程错误代码 */
    rt_uint8_t  stat;                   /* 线程状态 */

    /* 优先级 */
    rt_uint8_t  current_priority;    /* 当前优先级 */
    rt_uint8_t  init_priority;        /* 初始优先级 */
    rt_uint32_t number_mask;

    ......

    rt_ubase_t  init_tick;               /* 线程初始化计数值 */
    rt_ubase_t  remaining_tick;         /* 线程剩余计数值 */

    struct rt_timer thread_timer;      /* 内置线程定时器 */

    void (*cleanup)(struct rt_thread *tid);  /* 线程退出清除函数 */
    rt_uint32_t user_data;                      /* 用户数据 */
};

线程属性

线程栈

线程状态

线程优先级

RTT最大支持0~255,0为最高,最低优先级一般分配给空闲进程

时间片

对优先级相同的线程采用时间片的约束方式,用来约束线程单次运行时长

果就绪列表中有相同优先级的线程,则这两个线程会按照时间片长短被轮番调度

比如OS tick精度为1ms,那么五个时间片就是运行五个OS Tick。

如下图,设计了 thread1、thread2 两个相同优先级的线程,thread1 时间片为 10, thread2 时间片为 5

在两个线程都创建好之后,先运行时间片长的thread1,经过10个tick后运行时间片短的thread2,经过五个时间片后再运行thread1,如此循环。

入口函数

  • 无限循环模式

比如hero线程的例程里

这样的线程必须要有让出CPU使用权的动作,比如调用延时函数或主动挂起。

用无限循环可以保证线程一直被循环调度永不删除

  • 有限次模式

例如上面的代码,一旦执行完毕线程就会被删除

线程错误码

线程状态切换

1.线程通过函数rt_thread_create/init进入初始状态RT_THREAD_INIT

2.初始状态的线程调用rt_thread_startup进入就绪状态RT_THREAD_READY

3.就绪状态的线程被调度后进入运行状态RT_THREAD_RUNNING

4.当处于运行状态的线程调用 rt_thread_delay(),rt_sem_take(),rt_mutex_take(),rt_mb_recv() 等函数或者获取不到资源时,进入到挂起状态RT_THREAD_SUSPEND

5.挂起状态的线程,如果超时仍然没有获得资源或者被释放了资源,就会进入就绪状态RT_THREAD_READY

如果调用rt_thread_delete/detach()函数,将进入到关闭状态RT_THREAD_CLOSE

6.运行状态的线程,如果运行完毕,会在线程最后执行rt_thread_exit()进入关闭状态

系统线程

空闲线程

最低优先级,永远为就绪态,永远为死循环不能被挂起

也负责线程删除和回收

主线程

系统启动时创建,用于进入主函数

入口函数为main_thread_entry

调试的时候会发现代码一开始是在$$Sub$$main()这里的

线程管理方式

创建

例子:

删除

这个函数会先把要删除的线程改成close状态,放入僵尸队列,等待下一次空闲线程运行再删除

初始化

和create比多了一个线程句柄

脱离

注意创建和删除,初始化和脱离是对应的

创建和初始化的区别:create是创建动态线程,而init是初始化静态线程

启动

一般放在初始化或者创建线程之后,把线程的状态改为就绪状态。如果新启动的线程优先级更高会立刻切换到他。

我之前就是因为没加这个函数导致线程进不去,一定要加

获得当前线程

让出处理器资源

当前线程时间片用完或者主动让出。

线程睡眠

·

用处都是让线程挂起一段时间

sleep和delay都是以OS TICK为单位

但是mdelay是以ms为单位

挂起

该函数只能用来挂起自己,并且需要立刻使用rt_schedule()切换上下文

恢复

控制

钩子函数

可以看一下这篇文章

我大体的理解就是把写好的函数看成一个个钩子,通过调用函数指针的方式让指针指向这个函数,把这个过程形象的比作挂钩子的过程

设置和删除空闲钩子

空闲钩子就是在空闲进程中进行的钩子函数,比如可以设置系统指示灯,直观地表示空闲进程正在进行

设置调度器钩子

调度器钩子会在每次线程切换的时候运行

hook的声明如下:

不允许调用系统API,不允许挂起上下文

一些具体使用可以看这篇语雀

线程间同步的信号量

我对同步的理解:控制各个线程按照预定的先后次序进行。

比如我要对一个地址进行修改和读取,如果没有同步可能会出现修改和读取同时进行的情况。

临界区:多个线程同时操作或者访问的同一块区域(代码)。

线程间同步的核心思想是:访问临界区的时候只允许一个(或者一类)线程运行

信号量

每个信号量都包含信号量值线程等待队列,信号量值对应可使用的实例(资源)数目。当信号量实例为0时,再申请该信号量的线程就会被挂起在等待队列上。

信号量控制块

struct rt_semaphore
{
   struct rt_ipc_object parent;  /* 继承自 ipc_object 类 */
   rt_uint16_t value;            /* 信号量的值 */
};
/* rt_sem_t 是指向 semaphore 结构体的指针类型 */
typedef struct rt_semaphore* rt_sem_t;

rt_sem_t表示函数句柄,是一个指向信号量控制块的指针

管理方式

创建

rt_sem_t rt_sem_create(const char *name,
                        rt_uint32_t value,
                        rt_uint8_t flag);

关于FIFO和PRIO的区别:

FIFO:先入先出,等待队列中先进入的线程优先获得等待的信号量

PRIO:优先级等待,等待队列按照优先级排序,优先级高的先获得等待的信号量

删除

rt_err_t rt_sem_delete(rt_sem_t sem);

如果删除时有线程正在等待信号量,则会先唤醒线程在进行信号量的删除

初始化

对于静态信号量,使用init进行初始化

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);

和初始化对应,不要搞错了

获取

信号量值大于0时,线程将获得信号量,对应信号量值减1

rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time);

如果信号量值为0,则会根据参数time直接返回,挂起一段时间或者永久等待,直到该信号量被释放。

如果在time的范围内仍然没有用得到信号量,就会超时返回。

例如:

释放

线程间通信

邮箱

邮箱可以用作线程间通信,每一封邮件可以容纳4字节内容,各个线程则可以读取邮箱里的邮件来进行处理

线程发送邮件到邮箱时,如果邮箱已满,就可以设置挂起或者直接返回,接收同理。

posted @ 2025-04-07 09:31  小蒟蒻皮皮鱼  阅读(57)  评论(0)    收藏  举报