RT-Thread内核学习(认真系列) -- (1)线程

目录

 

一、概述

二、线程的组成

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的入口函数 */
static void thread1_entry(void *parameter)
{
    rt_uint32_t count = 0;

    while (1)
    {
        /* 线程1采用低优先级运行,一直打印计数值 */
        rt_kprintf("thread1 count: %d\n", count ++);
        rt_thread_mdelay(500);
    }
}

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

2.2、线程控制块

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

/**
 * Thread structure
 */
struct rt_thread   //线程结构体
{
    /* rt object */
    char        name[RT_NAME_MAX];                      /**< the name of thread */
    rt_uint8_t  type;                                   /**< type of object */
    rt_uint8_t  flags;                                  /**< thread's flags */

    /*
    此处省略很多代码
    */
};
typedef struct rt_thread *rt_thread_t; //线程结构体指针

2.3、线程栈

 

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

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

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

三、线程相关的API

3.1、线程的创建

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

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)            //时间片数(单位:系统滴答)

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

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

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

//返回线程控制块指针
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)                //时间片

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

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

 

3.2、状态的切换

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

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

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

3.3、钩子函数

3.3.1、空闲线程的钩子函数

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

rt_err_t rt_thread_idle_sethook(void (*hook)(void)); //hook为自己定义的函数
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、线程栈的数据具体是如何保存的?

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值