打印
[开发工具]

浅析RTThread(基本版)类所包含的成员

[复制链接]
686|14
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
stormwind123|  楼主 | 2023-4-28 16:13 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
先说点题外话,看了RTT的源代码,其实还挺感慨的,RTT源代码结构清晰,完整的面向对象编程方式,命名规范,实在是非常难得。希望能在之后的发展中继续把关代码风格审核,不要丢掉这个优秀的习惯。

首先还是先了解下rt_thread(基本版)类所包含的成员:

1.与object完全一样的部分,这个地方按道理是可以直接使用struct rt_object parent的,事实上除了rt_thread类以外,其他类也都是这样做的。

2.tlist用于rt_thread的一个双向链表,把每个rt_thread实例通过链表链接起来,方便通过name成员来查找对应的rt_thread.

3.sp一个数据指针,指向线程栈当前使用的地址,线程切换之后,用来保存MCU中寄存器sp中的数据。

4.entry一个函数指针,指向线程的入口函数。

5.parameter一个数据指针,指向传入线程的参数的数据地址。

6.stack_addr一个数据指针,指向为线程分配的栈所有数据的顶部,一旦分配完成在线程删除之前是一个固定值。

7.stack_size保存所分配的栈空间的大小。

8.error保存与线程相关的错误标志。

RTT申明了以下11种错误:
```#define RT_EOK 0 /*< There is no error /

define RT_ERROR 1 /*< A generic error happens /
define RT_EtiMEOUT 2 /*< Timed out /
define RT_EFULL 3 /*< The resource is full /
define RT_EEMPTY 4 /*< The resource is empty /
define RT_ENOMEM 5 /*< No memory /
define RT_ENOSYS 6 /*< No system /
define RT_EBUSY 7 /*< Busy /
define RT_EIO 8 /*< IO error /
define RT_EINTR 9 /*< Interrupted system call /
define RT_EINVAL 10 /*< Invalid argument /```
9.stat 保存线程现在处在的状态。

RTT的线程有以下7(6)种状态:
```#define RT_THREAD_INIT 0x00 /*< Initialized status /

define RT_THREAD_READY 0x01 /*< Ready status /
define RT_THREAD_SUSPEND 0x02 /*< Suspend status /
define RT_THREAD_RUNNING 0x03 /*< Running status /
define RT_THREAD_BLOCK RT_THREAD_SUSPEND /*< Blocked status /
define RT_THREAD_CLOSE 0x04 /*< Closed status /
define RT_THREAD_STAT_MASK 0x0f```
10.current_priority,init_priority这两个成员一个用来保存当前优先级,一个用来保存初始化的时候的优先级,当任务被启动的时候current_priority会被重新赋值为init_priority

11.number_mask与线程调度算法有关的一个参数,在调度器板块再来看这个部分。

12.init_tick,remaining_tick这两个成员在有相同优先级的线程同时运行的时候生效,init_tick记录当前线程在有相同优先级任务的时候的时间片长度,如init_tick=20则当运行20个tick后会进行线程切换。

13.thread_timer实例化了一个rt_timer类,该rt_timer主要用来定时唤醒线程,当线程获取s信号量或者互斥锁或邮箱、队列的时候会拥有一个超时时间,此时会配置这个软定时器,时间到后对线程进行唤醒。

14.cleanup一个函数指针,指向一个清理函数,当线程退出的时候,将会执行该函数。

15.user_data用户数据
```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 /

rt_list_t   list;                                   /**< the object list */
rt_list_t   tlist;                                  /**< the thread list */
/* stack point and entry */
void       *sp;                                     /**< stack point */
void       *entry;                                  /**< entry */
void       *parameter;                              /**< parameter */
void       *stack_addr;                             /**< stack address */
rt_uint32_t stack_size;                             /**< stack size */
/* error code */
rt_err_t    error;                                  /**< error code */
rt_uint8_t  stat;                                   /**< thread status */
/* priority */
rt_uint8_t  current_priority;                       /**< current priority */
rt_uint8_t  init_priority;                          /**< initialized priority */
if RT_THREAD_PRIORITY_MAX > 32
rt_uint8_t  number;
rt_uint8_t  high_mask;
endif
rt_uint32_t number_mask;
rt_ubase_t  init_tick;                              /**< thread\'s initialized tick */
rt_ubase_t  remaining_tick;                         /**< remaining tick */
struct rt_timer thread_timer;                       /**< built-in thread timer */
void (*cleanup)(struct rt_thread *tid);             /**< cleanup function when thread exit */
rt_uint32_t user_data;                             /**< private user data beyond this thread */
};```

看完rt_thread包含的成员,挺多的,但每个成员的作用,还算是比较清晰的。那么我们继续来聊thread相关的函数。

使用特权

评论回复
评论
cooldog123pp 2023-4-29 08:29 回复TA
给你点赞 
沙发
stormwind123|  楼主 | 2023-4-28 16:14 | 只看该作者
主要是以下几个函数,
```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_err_t rt_thread_detach(rt_thread_t thread);
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_thread_t rt_thread_self(void);
rt_thread_t rt_thread_find(char *name);
rt_err_t rt_thread_startup(rt_thread_t thread);
rt_err_t rt_thread_delete(rt_thread_t thread);

rt_err_t rt_thread_yield(void);
rt_err_t rt_thread_delay(rt_tick_t tick);
rt_err_t rt_thread_mdelay(rt_int32_t ms);
rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void arg);
rt_err_t rt_thread_suspend(rt_thread_t thread);
rt_err_t rt_thread_resume(rt_thread_t thread);
void rt_thread_timeout(void parameter);```

可以看到作为继承于object的子类,rt_thread类基本上重写了object相关的所有函数,现在来逐一分析每个函数所做的操作:

一、首先是rt_thread_init()以及rt_thread_create()

rt_thread_init()与rt_thread_create()的差异同样是在一个是静态线程的初始化,一个是动态的创建线程,函数内部的差异仅仅是在rt_thread_create()会为创建的线程从堆中申请栈空间并为要实例化的rt_thread申请内存。

这两个函数可以看做C++中的构造函数。
```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)
{
struct rt_thread thread;
void stack_start;
/**申请内存**/
thread = (struct rt_thread *)rt_object_allocate(RT_Object_Class_Thread,
name);
if (thread == RT_NULL)
return RT_NULL;

stack_start = (void *)RT_KERNEL_MALLOC(stack_size);
if (stack_start == RT_NULL)
{
    /* allocate stack failure */
    rt_object_delete((rt_object_t)thread);
    return RT_NULL;
}
/*/

/**成员初始化*/
_rt_thread_init(thread,
name,
entry,
parameter,
stack_start,
stack_size,
priority,
tick);
/*/
return thread;
}
RTM_EXPORT(rt_thread_create);```

两个函数最终都在_rt_thread_init()这个私有函数中进行成员初始化。

_rt_thread_init主要执行以下操作:

1.双向链表指针指向自身。

2.赋值入口函数地址。

3.赋值入口函数参数。

4.线程栈相关的初始化:

赋值栈顶地址,赋值栈大小。

将栈空间所有ram设置为’#’(一般是用做检测栈的使用率)。

调用rt_hw_stack_init()对栈顶数据进行初始化(这个设计到线程切换时,保存当前线程的相应PC地址,临时变量,以及MCU寄存器的值等既保存线程的上下文),之后会详细介绍这个函数。

5.初始化优先级。

6.初始化init_tick,remaining_tick (作用上文提过)

7.初始化错误标志。

8.初始化线程状态。

9.初始化clean_up函数和user_data,这里直接初始化为0,默认不使用。

10.初始化定时器,将定时器的time_out回调设置为rt_thread_timeout(),这个函数会将线程从阻塞队列中移出,加入就绪队列,并将error成员置为RT_ETIMEOUT

11.调用rt_thread_inited_hook()钩子函数

```static rt_err_t _rt_thread_init(struct rt_thread *thread,

使用特权

评论回复
板凳
stormwind123|  楼主 | 2023-4-28 16:14 | 只看该作者
说到rt_hw_stack_init()函数,首先需要介绍下MCU里的程序的运行规律,在裸机环境下,程序的临时变量,函数的参数,代码运行的地址(PC)统统放在一个叫栈的地方,栈是MCU RAM 里面一段连续的内存空间,栈的大小一般在启动文件或者链接脚本里面指定, 最后由 C 库函数_main 进行初始化。

但是, 在多线程的RTOS中,每个线程都是独立的,互不干扰的,所以要为每个线程都分配独立的栈空间,这个栈空间可以是一个预先定义好的全局数组(静态创建的线程), 也可以是动态分配的一段内存空间(动态创建的线程),但归根结底它们都存在于 MCU的RAM 中。

学过汇编指令的肯定都熟悉,当MCU执行中断或者跳转函数的时候,有一个保护现场的动作,把MCU相应的寄存器数据,SP指针,PC地址PUSH入栈,这些入栈出栈操作有些是MCU自动完成的,有些必须要通过程序要完成。入栈结束后再执行跳转,当中断结束后,要返回当前程序位置的时候再从栈中把数据取出来,重新跳转到之前程序位置,恢复临时变量的值,这就叫恢复现场。

这个两个步骤其实也就是RTOS实现线程切换的最根本的原理。

重新回到rt_hw_stack_init()函数,它其实完成的就是对线程栈的最基础的初始化。

首先在栈顶空出了4个字节,然后进行了8字节对齐操作,然后留出了空间用来存储stack_frame中的内容,stack_frame包含与CPU、FPU相关的一些寄存器(R0-R11,PC,LR等等)。
```rt_uint8_t rt_hw_stack_init(void tentry,
void parameter,
rt_uint8_t stack_addr,
void texit)
{
struct stack_frame stack_frame;
rt_uint8_t *stk;
unsigned long i;

stk  = stack_addr + sizeof(rt_uint32_t);
stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
stk -= sizeof(struct stack_frame);
stack_frame = (struct stack_frame *)stk;
/* init all register */
for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
{
    ((rt_uint32_t *)stack_frame)* = 0xdeadbeef;
}
stack_frame->exception_stack_frame.r0  = (unsigned long)parameter; /* r0 : argument */
stack_frame->exception_stack_frame.r1  = 0;                        /* r1 */
stack_frame->exception_stack_frame.r2  = 0;                        /* r2 */
stack_frame->exception_stack_frame.r3  = 0;                        /* r3 */
stack_frame->exception_stack_frame.r12 = 0;                        /* r12 */
stack_frame->exception_stack_frame.lr  = (unsigned long)texit;     /* lr */
stack_frame->exception_stack_frame.pc  = (unsigned long)tentry;    /* entry point, pc */
stack_frame->exception_stack_frame.psr = 0x01000000L;              /* PSR */
if USE_FPU
stack_frame->flag = 0;
endif / USE_FPU /
/* return task\'s current stack address */
return stk;
}```

二、rt_err_t rt_thread_detach()和rt_thread_delete()

这两个函数都用来删除线程的,区别是第一个是作用到静态创建的线程,第二个的作用是删除动态创建的线程。

1.参数检查。

2.将线程移出调度器队列(线程将不会再被调度)

3.将线程的timer移出timer队列(timer将不会进行计时)

4.设置stat成员为RT_THREAD_CLOSE

5.对于动态创建的线程,直接将线程加入rt_thread_defunct队列,对于静态的线程,只有在有clean_up函数的时候才加入rt_thread_defunct。在idle任务中,会对处在defunct队列中的线程进行释放内存等收尾工作。
```rt_err_t rt_thread_delete(rt_thread_t thread)
{
rt_base_t lock;

/* thread check */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
RT_ASSERT(rt_object_is_systemobject((rt_object_t)thread) == RT_FALSE);
if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_INIT)
{
    /* remove from schedule */
    rt_schedule_remove_thread(thread);
}
/* release thread timer */
rt_timer_detach(&(thread->thread_timer));
/* change stat */
thread->stat = RT_THREAD_CLOSE;
/* disable interrupt */
lock = rt_hw_interrupt_disable();
/* insert to defunct thread list */
rt_list_insert_after(&rt_thread_defunct, &(thread->tlist));
/* enable interrupt */
rt_hw_interrupt_enable(lock);
return RT_EOK;
}

rt_err_t rt_thread_detach(rt_thread_t thread)
{
rt_base_t lock;

/* thread check */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
RT_ASSERT(rt_object_is_systemobject((rt_object_t)thread));
if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_INIT)
{
    /* remove from schedule */
    rt_schedule_remove_thread(thread);
}
/* release thread timer */
rt_timer_detach(&(thread->thread_timer));
/* change stat */
thread->stat = RT_THREAD_CLOSE;
/* detach object */
rt_object_detach((rt_object_t)thread);
if (thread->cleanup != RT_NULL)
{
    /* disable interrupt */
    lock = rt_hw_interrupt_disable();
    /* insert to defunct thread list */
    rt_list_insert_after(&rt_thread_defunct, &(thread->tlist));
    /* enable interrupt */
    rt_hw_interrupt_enable(lock);
}
return RT_EOK;
}```

三、rt_err_t rt_thread_startup()线程启动函数

启动函数主要执行了:

1.参数检查

2.将current_priority 重新赋值为init_priority

3.为number_mask赋值,调度器会使用,之后再说

4.将stat置为RT_THREAD_SUSPEND挂起状态

5.调用rt_thread_resume启动线程。

6.判断当前是否有线程在运行(既调度器是否启动),若有则启动一次调度(此时若线程具有最高优先级,则会切换到该线程开始运行)
```rt_err_t rt_thread_startup(rt_thread_t thread)
{
/ thread check /
RT_ASSERT(thread != RT_NULL);
RT_ASSERT((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);

/* set current priority to init priority */
thread->current_priority = thread->init_priority;
/* calculate priority attribute */
if RT_THREAD_PRIORITY_MAX > 32
thread->number      = thread->current_priority >> 3;            /* 5bit */
thread->number_mask = 1L << thread->number;
thread->high_mask   = 1L << (thread->current_priority & 0x07);  /* 3bit */
else
thread->number_mask = 1L << thread->current_priority;
endif
RT_DEBUG_LOG(RT_DEBUG_THREAD, (\"startup a thread:%s with priority:%d
\",
                               thread->name, thread->init_priority));
/* change thread stat */
thread->stat = RT_THREAD_SUSPEND;
/* then resume it */
rt_thread_resume(thread);
if (rt_thread_self() != RT_NULL)
{
    /* do a scheduling */
    rt_schedule();
}
return RT_EOK;
}```

四、rt_thread_control()函数

该函数根据参数cmd的不同有几种不同的功能,

cmd可取的值为:
RT_THREAD_CTRL_CHANGE_PRIORITY 修改线程的优先级 RT_THREAD_CTRL_STARTUP 启动线程 RT_THREAD_CTRL_CLOSE 关闭删除线程

只有修改优先级是独有的功能
```rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void *arg)
{
register rt_base_t temp;

/* thread check */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
switch (cmd)
{
case RT_THREAD_CTRL_CHANGE_PRIORITY:
    /* disable interrupt */
    temp = rt_hw_interrupt_disable();
    /* for ready thread, change queue */
    if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_READY)
    {
        /* remove thread from schedule queue first */
        rt_schedule_remove_thread(thread);
        /* change thread priority */
        thread->current_priority = *(rt_uint8_t *)arg;
        /* recalculate priority attribute */
if RT_THREAD_PRIORITY_MAX > 32
        thread->number      = thread->current_priority >> 3;            /* 5bit */
        thread->number_mask = 1 << thread->number;
        thread->high_mask   = 1 << (thread->current_priority & 0x07);   /* 3bit */
else
        thread->number_mask = 1 << thread->current_priority;
endif
        /* insert thread to schedule queue again */
        rt_schedule_insert_thread(thread);
    }
    else
    {
        thread->current_priority = *(rt_uint8_t *)arg;
        /* recalculate priority attribute */
if RT_THREAD_PRIORITY_MAX > 32
        thread->number      = thread->current_priority >> 3;            /* 5bit */
        thread->number_mask = 1 << thread->number;
        thread->high_mask   = 1 << (thread->current_priority & 0x07);   /* 3bit */
else
        thread->number_mask = 1 << thread->current_priority;
endif
    }
    /* enable interrupt */
    rt_hw_interrupt_enable(temp);
    break;
case RT_THREAD_CTRL_STARTUP:
    return rt_thread_startup(thread);
ifdef RT_USING_HEAP
case RT_THREAD_CTRL_CLOSE:
    return rt_thread_delete(thread);
endif
default:
    break;
}
return RT_EOK;
}```

五、其他函数
(1)rt_thread_t rt_thread_self(void); (2)rt_thread_t rt_thread_find(char *name); (3)rt_err_t rt_thread_yield(void); (4)rt_err_t rt_thread_delay(rt_tick_t tick); (5)rt_err_t rt_thread_mdelay(rt_int32_t ms); (6)rt_err_t rt_thread_suspend(rt_thread_t thread); (7)rt_err_t rt_thread_resume(rt_thread_t thread); (8)void rt_thread_timeout(void *parameter);

(1)获取当前正在运行的线程指针。

(2)通过线程名字获取线程的句柄。

(3)yield这个单词不好翻译,简单的来说这个函数的功能就是进行一次线程切换,把当前线程加入到就绪队列的最末尾,同时执行就绪队列的第一个线程。

(4)(5)延时当前线程,具体方式是将当前线程悬挂,并启动线程的timer,当时间到了之后通过time_out回调唤醒任务。

(6)挂起线程。

挂起线程会将线程移出调度器队列,使得线程无法被调度,将线程状态置为RT_THREAD_SUSPEND,并关闭线程的timer,最后会调用rt_thread_suspend_hook()钩子函数

(7)恢复线程

恢复线程会将线程移出挂起队列(当线程被相应信号挂起的时候,就会被加入到对应信号的挂起队列中,等待信号被释放),重新加入调度器就绪队列。

(8)线程timer的time_out回调。

将线程的error设置为 -RT_ETIMEOUT,并加线程移出当前的队列,重新加入调度器就绪队列,并执行一次调度。

使用特权

评论回复
地板
cooldog123pp| | 2023-4-29 13:52 | 只看该作者
楼主讲的很详细,非常感谢楼主的讲解,mark一下,收藏学习,受教受教。

使用特权

评论回复
5
Wordsworth| | 2024-6-11 07:29 | 只看该作者

电阻属于一个普通的元件

使用特权

评论回复
6
Clyde011| | 2024-6-11 08:32 | 只看该作者

并且考虑到TVS相同的尺寸

使用特权

评论回复
7
公羊子丹| | 2024-6-11 09:25 | 只看该作者

板与板(或空板)之间用邮票孔连接

使用特权

评论回复
8
万图| | 2024-6-11 10:28 | 只看该作者

当异常过压消失,恢复至高阻态

使用特权

评论回复
9
Uriah| | 2024-6-11 11:31 | 只看该作者

微控制器、数字信号控制器和处理器都具有内部的ESD钳位二极管

使用特权

评论回复
10
帛灿灿| | 2024-6-11 13:27 | 只看该作者

ESD电压一般超过导通电压

使用特权

评论回复
11
Bblythe| | 2024-6-11 14:30 | 只看该作者

模块电源选购灌封材料的时候,需要注意导热系数要能达到电子部件散热的需求,不过粘接能力不太强

使用特权

评论回复
12
周半梅| | 2024-6-11 16:26 | 只看该作者

可能击穿开关器件,

使用特权

评论回复
13
Pulitzer| | 2024-6-11 17:29 | 只看该作者

如果想要让它正常工作且不会损坏

使用特权

评论回复
14
童雨竹| | 2024-6-11 19:25 | 只看该作者

变压器副边同时对负载供电。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

417

主题

2169

帖子

2

粉丝