zoukankan      html  css  js  c++  java
  • RT-Thread内核学习

    一、概述

    二、线程的组成

    2.1、线程代码(入口函数)

    2.2、线程控制块

    2.3、线程栈

    三、线程相关的API

    3.1、线程的创建

    3.2、状态的切换

    四、注意事项与补充

    4.1、动态创建与静态创建的优缺点比较?

    4.2、系统滴答时钟频率的选取

    4.3、线程栈大小分配的小策略

    4.4、线程栈的数据具体是如何保存的?


    一、概述

    线程是RT-Thread的核心部分,也是最基础的功能,系统都是围绕线程来构建的。

    二、线程的组成

    RT-Thread中,线程由三部分组成:

    1、线程代码(入口函数)

    2、线程控制块

    3、线程堆栈

    2.1、线程代码(入口函数)

    线程代码是我们实现某个功能的代码实现,一般的入口函数都是无限循环结构,类似如下的代码:

    1.  
      /* 线程1的入口函数 */
    2.  
      static void thread1_entry(void *parameter)
    3.  
      {
    4.  
      rt_uint32_t count = 0;
    5.  
       
    6.  
      while (1)
    7.  
      {
    8.  
      /* 线程1采用低优先级运行,一直打印计数值 */
    9.  
      rt_kprintf("thread1 count: %d ", count ++);
    10.  
      rt_thread_mdelay(500);
    11.  
      }
    12.  
      }

    当然也有顺序执行结构,一些只需要执行一次,就不使用循环,执行完一次后就会被回收,不再有效。

    2.2、线程控制块

    线程控制块是操作系统用于管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包括线程与线程之间连接用的链表结构、线程等待事件集合等。

    1.  
      /**
    2.  
      * Thread structure
    3.  
      */
    4.  
      struct rt_thread //线程结构体
    5.  
      {
    6.  
      /* rt object */
    7.  
      char name[RT_NAME_MAX]; /**< the name of thread */
    8.  
      rt_uint8_t type; /**< type of object */
    9.  
      rt_uint8_t flags; /**< thread's flags */
    10.  
       
    11.  
      /*
    12.  
      此处省略很多代码
    13.  
      */
    14.  
      };
    15.  
      typedef struct rt_thread *rt_thread_t; //线程结构体指针

    2.3、线程栈

    RT-Thread 每个线程都具有独立的栈空间,当线程切换时,系统会将当前线程的上下文保存到线程栈中,当线程要恢复时,再从对应的线程栈中读取之前保存的上下文信息,从而恢复到被切换时的状态,完整的恢复线程的运行。

    线程上下文是指线程执行时的环境,具体来说,就是各个变量和数据包的所有寄存器变量、堆栈信息、内存信息等。(注:这里要好好研究一下,比如具体保存哪些,如何实现等,深究一下)

    线程栈在形式上是一段连续的内存空间,可以通过定义一个数组或者申请一段动态内存来作为线程的栈。

    三、线程相关的API

    3.1、线程的创建

    3.1.1、创建静态线程,初始化函数如下:

    1.  
      rt_err_t rt_thread_init(struct rt_thread *thread, //线程控制块指针
    2.  
      const char *name, //线程名
    3.  
      void (*entry)(void *parameter), //入口函数
    4.  
      void *parameter, //入口函数参数
    5.  
      void *stack_start, //栈地址
    6.  
      rt_uint32_t stack_size, //栈大小(单位:字节)
    7.  
      rt_uint8_t priority, //优先级
    8.  
      rt_uint32_t tick) //时间片数(单位:系统滴答)

    初始化前提,需要定义线程控制块、栈(这里一般是数组)、入口函数,举例如下:

    1.  
      /* 定义线程控制块 */
    2.  
      static struct rt_thread led1_thread;
    3.  
      /* 定义线程控栈时要求RT_ALIGN_SIZE个字节对齐 */
    4.  
      ALIGN(RT_ALIGN_SIZE)
    5.  
      /* 定义线程栈 */
    6.  
      static rt_uint8_t rt_led1_thread_stack[1024];
    7.  
      /* 函数声明 */
    8.  
      static void led1_thread_entry(void* parameter);

    3.1.2、创建动态线程,初始化函数如下:

    1.  
      //返回线程控制块指针
    2.  
      rt_thread_t rt_thread_create(const char *name, //线程名字
    3.  
      void (*entry)(void *parameter), //线程入口函数
    4.  
      void *parameter, //入口函数参数
    5.  
      rt_uint32_t stack_size, //栈大小
    6.  
      rt_uint8_t priority, //优先级
    7.  
      rt_uint32_t tick) //时间片

    初始化前提,需要定义线程控制块指针、入口函数,举例如下:

    1.  
      /* 定义线程控制块 */
    2.  
      static rt_thread_t led1_thread = RT_NULL;
    3.  
      /* 函数声明 */
    4.  
      static void led1_thread_entry(void* parameter);

    3.2、状态的切换

    3.2.1、比较常用的是启动函数,传入的参数是线程控制块指针。

    1.  
      /* 启动线程,开启调度 */
    2.  
      if (led1_thread != RT_NULL)
    3.  
      rt_thread_startup(led1_thread);

    3.2.2、别的导致线程状态切换的API如下:

    3.3、钩子函数

    3.3.1、空闲线程的钩子函数

    空闲钩子函数是空闲线程的钩子函数,如果设置了空闲钩子函数,就可以在系统执行空闲线程时,自动执行空闲钩子函数来做一些其他事情,比如系统指示灯等。API如下:

    1.  
      rt_err_t rt_thread_idle_sethook(void (*hook)(void)); //hook为自己定义的函数
    2.  
      rt_err_t rt_thread_idle_delhook(void (*hook)(void));

    空闲线程的优先级是最低的。最多可以设计四个空闲线程的钩子函数(why?) 。

    3.3.2、调度器的钩子函数

    在整个系统的运行时,系统都处于线程运行、中断触发 - 响应中断、切换到其他线程,甚至是线程间的切换过程中,或者说系统的上下文切换是系统中最普遍的事件。有时用户可能会想知道在一个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个相应的钩子函数。在系统线程切换时,这个钩子函数将被调用:

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

    钩子函数 hook() 的声明如下:

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

    注意: 请仔细编写你的钩子函数,稍有不慎将很可能导致整个系统运行不正常(在这个钩子函数中,基本上不允许调用系统 API,更不应该导致当前运行的上下文挂起)。

    调度器的钩子函数只能设置一个。

    四、注意事项与补充

    4.1、动态创建与静态创建的优缺点比较?

    待续

    4.2、系统滴答时钟频率的选取

    操作系统都存在一个叫做“系统心跳”的时钟,它是操作系统的最小时钟单位,负责系统和时间相关的一些操作,这个心跳时钟一般是由硬件定时器的中断来产生的。

    时钟节拍使得内核可以将线程延时若干个整数时钟节拍,以及线程等待事件时提供等待超时的依据。

    系统的心跳时钟也成为系统滴答或时钟节拍,它的频率,我们要根据cpu的处理能力来决定(比如72MHz的STM32F1,我们常设置每个滴答时间为10ms)。

    频率越快,内核函数介入系统运行的几率越大,内核占用处理器的时间就越长,系统的负荷越高,也就是说本该处理应用的计算资源更多的被内核占用。

    但频率越低,时间处理的精度又不够,也可能会造成系统响应迟钝。

    4.3、线程栈大小分配的小策略

    先将线程栈大小设置成一个比较大的值(比如2048),在线程运行时查看线程栈的使用情况(如下图),根据情况设置合理的栈大小,一般使线程栈使用最大量设置为70%左右比较合适。

    4.4、线程栈的数据具体是如何保存的?

  • 相关阅读:
    svn ------ 更改提交地址,显示relocate
    .NET ------ 前端连接到新的界面与后台链接到新的界面
    .NET ------ 修改时间改变排序
    .NET ------ 通过flag 实现一表多用
    Java面向对象——抽象类与抽象方法
    Java——final关键字
    Java——类的成员之4:代码块(初始化块)
    Java——main方法的使用
    单例(Singleton)设计模式
    Java面向对象——static关键字
  • 原文地址:https://www.cnblogs.com/wt88/p/13936220.html
Copyright © 2011-2022 走看看