[010] [RT-Thread学习笔记] 线程内置定时器的作用

RT-Thread
学习笔记
线程内置定时器
的初始化流程
线程内置定
时器的应用

RT-Thread版本:4.0.5
MCU型号:STM32F103RCT6(ARM Cortex-M3 内核)

关于RT-Thread定时器的使用可以参考定时器源码浅析及其应用

1 线程内置定时器的初始化流程

  • 线程控制块
/* 线程控制块 */
struct rt_thread
{
    /* 内核对象基本信息 */
    char        name[RT_NAME_MAX];                      /**< 线程名称 */
    rt_uint8_t  type;                                   /**< 对象类型 */
    rt_uint8_t  flags;                                  /**< 线程标志位 */
    ...
    struct rt_timer thread_timer;                       /**< 内置线程定时器 */
    ...
};

可以看到每个线程对象都有一个内置定时器对象thread->thread_timer

  • 内置定时器的初始化

使用rt_thread_creatert_thread_init函数初始化线程时会调用_thread_init函数,然后该函数内部会对线程内置定时器进行初始化:

static rt_err_t _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_timer_init(&(thread->thread_timer),
                  thread->name,
                  rt_thread_timeout,
                  thread,
                  0,
                  RT_TIMER_FLAG_ONE_SHOT);
    ...
}                             

将线程内置定时器初始化为单次触发硬件定时器timer->parent.flag没有或|上RT_TIMER_FLAG_SOFT_TIMER的话,就为硬件定时器,因为初值为0),其超时回调函数为rt_thread_timeout
最终会在滴答定时器Systick中断里,调用硬件定时器扫描函数rt_timer_check扫描硬件定时器列表,当有超时事件发生时就会调用对应的超时函数,因为是单次触发,所以只会触发一次定时器事件,触发后会将该定时器设为非激活态且不会再次启动。

:动态创建的线程,其内置定时器初始化为什么可以使用静态方法?

:线程对象内存虽然是动态申请的,但是定时器对象是一个静态的对象struct rt_timer,是一个定时器结构体变量,而不是一个指向该结构体的指针,所以必须使用静态方法初始化。

  • 内置定时器超时回调函数
void rt_thread_timeout(void *parameter)
{
    struct rt_thread *thread;
    register rt_base_t temp;

    thread = (struct rt_thread *)parameter;

    /* 参数检查 */
    RT_ASSERT(thread != RT_NULL);
    RT_ASSERT((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND);
    RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);

    /* 关中断 */
    temp = rt_hw_interrupt_disable();

    /* 设置超时错误标志 */
    thread->error = -RT_ETIMEOUT;

    /* 将线程从挂起列表中移除 */
    rt_list_remove(&(thread->tlist));

    /* 插入到就绪列表以便于调度 */
    rt_schedule_insert_thread(thread);

    /* 开中断 */
    rt_hw_interrupt_enable(temp);

    /* 发起线程调度 */
    rt_schedule();
}

所有线程的内置定时器超时回调函数均为rt_thread_timeout,当某线程内置定时器启动且到达设定的超时时间后,就会调用该函数,将线程的错误码设为超时错误-RT_ETIMEOUT,然后将线程从挂起列表中移除,插入到就绪列表中,最后发起一次线程调度。

  • 线程内置定时器的脱离

当线程被删除或脱离时,函数内部会调用rt_timer_detach(&(thread->thread_timer)),将线程内置定时器从系统定时器列表中脱离(因为是静态对象)。

2 线程内置定时器的应用

  • 线程主动调用延时(睡眠)函数,将自身延时阻塞挂起。

线程主动调用以下函数时,会让线程从运行状态切换到挂起状态,并设置相应的阻塞延时时间,该延时时间即为先内置定时器的超时时间。

rt_err_t rt_thread_sleep(rt_tick_t tick);
rt_err_t rt_thread_delay(rt_tick_t tick);
rt_err_t rt_thread_mdelay(rt_int32_t ms);

这三个延时函数其实最终都调用的是rt_thread_sleep

rt_err_t rt_thread_sleep(rt_tick_t tick)
{
    register rt_base_t temp;
    struct rt_thread *thread;

    /* 获取当前线程句柄 */
    thread = rt_thread_self();
    RT_ASSERT(thread != RT_NULL);
    RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);

    /* 关中断 */
    temp = rt_hw_interrupt_disable();

    /* 挂起当前线程 */
    rt_thread_suspend(thread);

    /* 重置线程内置计时器的超时时间并启动它 */
    rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, &tick);
    rt_timer_start(&(thread->thread_timer));

    /* 开中断 */
    rt_hw_interrupt_enable(temp);

    rt_schedule();      //> (1)

    /* 清除该线程的错误码为RT EOK */
    if (thread->error == -RT_ETIMEOUT) 
        thread->error = RT_EOK;

    return RT_EOK;
}

利用中断锁保护临界段,然后将当前线程挂起,再用rt_timer_control函数修改定时器的超时时间为当前设定的延时时间,然后启动定时器,最后在(1)处发起任务调度。当到达定时器超时时间时,会调用其超时回调函数,将当前线程插入到就绪队列中,并发起一次线程调度。如果当前线程优先级最高,程序会回到(1)处继续执行,因为在超时回调函数中将线程错误码设为了-RT_ETIMEOUT,但这属于正确调用,所以最后将错误码改为RT_EOK

  • IPC通信的超时等待机制

使用RT-Thead的IPC通信机制实现多线程间的同步、通信时,当线程利用某通信机制获取某项资源失败而被阻塞时,就会根据设定的等待时间挂起等待该资源,如信号量的获取:

rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time)
{
    register rt_base_t temp;
    struct rt_thread *thread;

    /* 关中断 */
    temp = rt_hw_interrupt_disable();

    if (sem->value > 0)
    {
        /* 有可用信号量 */
        sem->value --;

        /* 开中断 */
        rt_hw_interrupt_enable(temp);
    }
    else
    {
        /* 不等待,返回超时错误 */
        if (time == 0)
        {
            rt_hw_interrupt_enable(temp);

            return -RT_ETIMEOUT;
        }
        else
        {
            /* 当前上下文检查 */
            RT_DEBUG_IN_THREAD_CONTEXT;

            /* 信号量不可用,挂起当前线程 */
            /* 获取当前线程 */
            thread = rt_thread_self();

            /* 设置线程错误码 */
            thread->error = RT_EOK;

            /* 挂起线程 */
            _ipc_list_suspend(&(sem->parent.suspend_thread),
                                thread,
                                sem->parent.parent.flag);

            /* 有等待时间则开始计时 */
            if (time > 0)
            {
                /* 设置线程超时时间,并启动定时器 */
                rt_timer_control(&(thread->thread_timer),
                                 RT_TIMER_CTRL_SET_TIME,
                                 &time);
                rt_timer_start(&(thread->thread_timer));
            }

            /* 开中断 */
            rt_hw_interrupt_enable(temp);

            /* 发起线程调度 */
            rt_schedule();  //> (1)

            if (thread->error != RT_EOK)
            {
                return thread->error;
            }
        }
    }

    return RT_EOK;
}

当线程获取信号量失败后,如果设置了等待时间,那么会将该线程挂起阻塞,然后将等待时间重新赋值为定时器的超时时间并启动它,最后发起一次线程调度。以下两种情况会唤醒恢复线程,回到(1)处继续执行调度语句后的代码:

  1. 未到达超时时间,但当前资源变为可用:此时应该有其他执行单元进行了释放该信号量的操作,若此线程是信号量等待队列第一位,就会唤醒它(将线程从挂起列表移除并插入到就绪列表中),然后执行线程调度,若该线程是就绪队列中优先级最高的话,就会回到(1)处继续执行。此时线程错误码thread->error值就是调度前设置的RT_EOK,所以最后会返回RT_EOK
  2. 到达超时时间,资源依然不可用:此时就会执行线程内置定时器超时回调函数了,将线程错误码thread->error设为-RT_ETIMEOUT,然后将线程唤醒,执行线程调度,同样如果该线程是当前就绪队列中优先级最高的话,会回到(1)处继续执行。但此时错误码就是-RT_ETIMEOUT了,所以函数最后会返回-RT_ETIMEOUT

注意:第一种情况,线程内置定时器并未停止,还是需要等超时时间到了,由硬件定时器扫描函数将其设为非激活态。


以上为本人目前所了解的应用,如还有其他应用,欢迎大家私信或在评论区给出。

END

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柯西的彷徨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值