Fork me on GitHub

RT-Thread学习笔记1-启动顺序与线程创建


1. 启动顺序

  1. SystemInit()
  2. $Sub$$main()
  3. rtthread_startup()
  4. rt_application_init()
  5. main_thread_entry
  6. $Super$$main用户主函数

2. 堆范围

自由分配的内存(堆)起始地址为RAM的起始地址加上RW+ZI段后的地址区域。

编译出的program size分为:

  1. Code: 代码段,存放程序的代码部分
  2. RO-data: 只读数据段,存放程序中定义的常量
  3. RW-data: 读写数据段,存放初始化为非0值的全局变量
  4. ZI-data: 0数据段,存放未初始化得全局变量及初始化为0的变量

实际占用空间情况为:

  1. RO Size包含了Code及RO-data,表示程序占用flash空间的大小
  2. RW Size包含了RW-data及ZI-data,表示运行时占用RAM的大小
  3. ROM Size包含了Code, RO Data和RW data,表示烧写程序占用flash空间的大小

板子上电后默认从flash启动,启动之后会将RW段中的RW-data(初始化的全局变量)搬运到RAM中,但不会搬运RO段,即CPU的执行代码从flash中读取,另外根据编译器给出的ZI地址和大小,分配出ZI段,并将这块RAM区域清零。动态内存堆为未使用的RAM空间,应用程序申请和释放的内存都来自该空间
BsntdH.png

char *ptr;
ptr = rt_malloc(10);
if (ptr != RT_NULL)
{
    rt_memset(ptr, 0, 10);
    rt_kprintf("malloc success\n");
    rt_free(ptr);
    ptr = RT_NULL;
}

3. 线程创建

RT-Thread中,线程由三部分组成:线程代码(入口函数)、线程控制块、线程堆栈

3.1 线程代码(入口函数)

无限循环结构
void thread_entry(void *parameter)
{
    while(1)
    {
        /* 等待事件发生 */
    
        /* 处理事件 */
    }
}

顺序执行结构
void thread_entry(void *parameter)
{
    /* 事务1处理 */
    /* 事务2处理 */
    /* 事务3处理 */
}

3.2 线程控制块

操作系统管理线程的一个数据结构。存放线程的一些信息,比如优先级、线程名称、线程状态等等,也包括线程与线程之间连接用的链表结构,线程等待时间集合等

struct rt_thread;
struct rt_thread *rt_thread_t;

3.3 线程栈

每个线程都有独立的栈空间,线程切换时,系统会将当前线程的上下文保存在线程栈中,当线程要恢复运行时,再从线程栈中读取上下文信息,恢复线程的运行。线程上下文是指线程执行时的环境,各个变量和数据包括所有的寄存器变量,堆栈信息,内存信息等。线程栈在形式上是一段连续的内存空间,可以通过定义一个数组或者申请一段动态内存来作为线程的栈
创建线程:

创建静态线程
rt_err_t rt_thread_init(struct rt_thread *thread,
const char *name,
void (*entry)(void *parameter),
void *parameter,
void *stack_start,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick)

创建动态线程
rt_thread_t rt_thread_create(const char *name,
void (*entry(void *parameter),
void *parameter,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick))

启动线程
rt_err_t rt_thread_startup(rt_thread_t thread)
调用此函数后创建的线程会被加入到线程的就绪队列,执行调度

rt_err_t thread_static_init()
{
    rt_err_t result;

    result = rt_thread_init(&thread,
        "test",
        thread_entry, RT_NULL,
        &thread_stack[0], sizeof(thread_stack),
        THREAD_PRIORITY, 10);

    if (result == RT_EOK)
        rt_thread_startup(&thread);
    else
        tc_stat(TC_STAT_END | TC_STAT_FAILED);

    return result;
}

int thread_dynamic_init()
{
    rt_thread_t tid;

    tid = rt_thread_create("test",
        thread_entry, RT_NULL,
        THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid != RT_NULL)
        rt_thread_startup(tid);
    else
        tc_stat(TC_STAT_END | TC_STAT_FAILED);

    return 0;
}

rt_thread_delay(15); // 根据时钟频率决定。时钟频率100HZ,那么一次delay 10ms.此处就未150ms
rt_thread_sleep(15);
rt_thread_mdelay(15); // delay 15ms

区别:

  1. 资源分配形式不同:静态线程的线程控制块和线程栈是静态分配的,而动态线程的这两部分是运行时动态分配的
  2. 执行效率:如果堆空间是片外RAM,那么动态线程的运行效率低于静态线程。反之,如果都是片内RAM,则没有差别

4. 系统滴答时钟

心跳时钟由硬件定时器的定时中断产生。称之为系统滴答或者时钟节拍。其频率需要根据CPU的处理能力来决定。始终街拍使得内核可以将线程延时若干个时钟节拍,以及线程等待时间发生时,超时的依据。频率越快,内核函数介入系统运行的概率越大,内核占用的处理器时间就越长,系统的负荷就越大。频率越小,时间处理精度又不够。在stm32平台上一般设置系统滴答频率为100HZ,即每个滴答的时间是10ms。在rtconfig.h中的RT_TICK_PER_SECOND宏,就是代表的HZ数

5. GPIO驱动架构操作IO

#include <rt_device.h>
IO初始化
void rt_pin_mode(rt_base_t pin, rt_base_t mode)
PIN_MODE_OUTPUT
PIN_MODE_INPUT
PIN_MODE_INPUT_PULLUP
PIN_MODE_INPUT_PULLDOWN
PIN_MODE_OUTPUT_OD

IO写入
void rt_pin_write(rt_base_t pin, rt_base_t value)
PIN_HIGH
PIN_LOW

IO读出
int rt_pin_read(rt_base_t pin)

首先通过看drv_gpio.c中的宏,得知我们设置的芯片有多少个脚。再看__STM32_PIN(2, E, 4).那么这里传入2,就表示要操作PE4引脚

使用msh中的命令:list_thread。列出当前所有线程的栈使用情况
BghJMD.png
可以先将线程栈大小设置一个固定值(比如2048),在线程运行时通过该命令查看线程栈的使用情况,了解线程栈使用的实际情况,根据情况设置合理的大小。一般将线程栈最大使用量设置为70%

6. 线程优先级 & 时间片

优先级

分别描述了线程竞争处理器资源的能力和持有处理器时间长短的能力。RT-Thread最大支持256个优先级,数值越小优先级越高,0为最高优先级,最低优先级保留给空闲线程idle。可以通过rt_config.h中的RT_THREAD_PRIORITY_MAX宏,修改最大支持的优先级。针对STM32默认设置最大支持32个优先级。具体应用中,线程总数不受限制,能创建的线程总数之和具体硬件平台的内存有关

时间片

只有在相同优先级的就绪态线程中起作用,时间片起到约束线程单次运行时长的作用,其单位是一个系统街拍(OS Tick)

优先级抢占调度

当有高优先级线程处于就绪态后,就会发生任务调度

时间片轮询调度

相同优先级的线程,操作系统按照时间片大小轮流调度线程,时间片起到约束线程单次运行时长的作用。保证同优先级任务轮流占有处理器

7. 钩子函数

空闲线程

特殊的系统线程,具有最低的优先级。系统中无其他就绪线程可运行时,调度器将调度到空闲线程。空闲线程负责一些系统资源回收以及将一些处于关闭态的线程从线程调度列表中移除的动作。空闲线程在形式上是一个无限循环结构,且永远不被挂起。在RT-Thread实时操作系统中空闲线程向用户提供了钩子函数,空闲线程钩子函数可以在系统空闲的时候,执行一些非紧急事务,例如系统运行指示灯闪烁,CPU使用率统计等等

rt_err_t rt_thread_idle_sethook(void(*hook)(void))
rt_err rt_thread_idle_delhook(void(*hook)(void))

注意:

  1. 空闲线程是一个线程状态永远为就绪态的线程,所以钩子函数中执行的相关代码必须保证空闲线程在任何时刻都不会被挂起,例如rt_thread_delay(), rt_sem_take()等可能会导致线程挂起的阻塞类函数,都不能再钩子函数中调用。
  2. 空闲线程可以设置多个钩子函数(有最大限制)

系统调度钩子函数

系统上下文切换是最普遍的时间,如果用户想知道在某一个时刻发生了什么样的线程切换,RT-Thread提供了一个系统调度钩子函数,这个钩子函数在系统进行任务切换时运行,通过这个钩子函数,可以了解到系统任务调度时的信息

rt_scheduler_sethook(void(*hook)(struct rt_thread *from, struct rt_thread *to))

参考文献

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

本文作者: CrazyCatJack

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

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

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


posted @ 2021-02-17 17:32  CrazyCatJack  阅读(1017)  评论(0编辑  收藏  举报