您的位置:首页 > 其它

学习rt-thread

2017-01-30 22:39 134 查看
隐隐感觉自己要把 一起来学rt-thread 抄一遍了

官方网站

http://www.rt-thread.org/

查看代码

以stm32f10x为例 使用keil mdk 下载源码进入\bsp\stm32f10x目录 打开工程

启动过程

在startup.c中找到main函数 如下

int main(void)
{
/* disable interrupt first */
rt_hw_interrupt_disable();

/* startup RT-Thread RTOS */
rtthread_startup();

return 0;
}


第一行 关中断操作

第二行 启动rt-thread

第三行 见鬼才能执行到的代码

第二行代码的rtthread_startup()函数如下

/**
* This function will startup RT-Thread RTOS.
*/
void rtthread_startup(void)
{
/* 初始化硬件平台相关:时钟设置、中断设置、系统滴答设置、串口设置 */
rt_hw_board_init();
up_mcu_show();
/* 打印 RT-Thread 版本信息 */
rt_show_version();
/* init tick */
rt_system_tick_init();
/* 内核对象初始化 */
rt_system_object_init();
/* 系统定时器初始化 */
rt_system_timer_init();
/* 如果使用动态内存分配,则配置之 */
#ifdef RT_USING_HEAP
#if STM32_EXT_SRAM
rt_system_heap_init((void*)STM32_EXT_SRAM_BEGIN,(void*)STM32_EXT_SRAM_END);
#else
#ifdef __CC_ARM
rt_system_heap_init((void*)&Image$$RW_IRAM1$$ZI$$Limit,(void*)STM32_SRAM_EN
D);
#elif __ICCARM__
rt_system_heap_init(__segment_end("HEAP"),(void*)STM32_SRAM_END);
#else
/* init memory system */
rt_system_heap_init((void*)&__bss_end,(void*)STM32_SRAM_END);
#endif
#endif
#endif
/* 系统调度器初始化 */
rt_system_scheduler_init();
#ifdef RT_USING_DFS
/* init sdcard driver */
#if STM32_USE_SDIO
rt_hw_sdcard_init();
#else
rt_hw_msd_init();
#endif
#endif
/*下面可加入用户所需的相关初始化 */
/*上面可加入用户所需的相关初始化 */
/* 实时时钟初始化 */
rt_hw_rtc_init();
/* 系统设备对象初始化 */
rt_device_init_all();
/* 用户应用初始化 */
rt_application_init();
#ifdef RT_USING_FINSH
/* init finsh */
finsh_system_init();
finsh_set_device("uart1");
#endif
list_date();/*显示当前时间 by jiezhi320 */
/* 初始化软件定时器 */
rt_system_timer_thread_init();
/* 初始化空闲线程 */
rt_thread_idle_init();
/* 开始线程调度此后便进入各个线程的无限循环 */
rt_system_scheduler_start();
/* never reach here */
return;
}


上面的函数完成了系统启动前的所有初始化动作,包括必要的硬件初始化、堆栈初始化、

系统相关组件初始化、用户应用程序初始化,然后启动调度机制。

1、 rt_hw_board_init()

完成中断向量表设置、系统滴答时钟设置,为系统提供心跳、串口初始化,将系统输入输

出终端绑定到这个串口,后续系统运行信息就会从串口打印出来。

2、 rt_system_heap_init()

RT-Thread 提供动态内存管理机制(由 RT_USING_HEAP 宏来选择性开启,默认开启),

这个函数用来设置需要系统来管理的内存段地址。对于 stm32f1这样的芯片这里有两种选择:

一种是除去编译时分配的全局变量、静态局部变量外的其他剩余内存被设置为系统堆空

间,被系统管理起来。比如魔笛 F1 板子芯片内部有 64k ram, 除去编译后的 RW、 ZI 所占

去的内存,剩余的就让系统管理起来:

rt_system_heap_init((void*)&Image$$RW_IRAM1$$ZI$$Limit, (void*)STM32_SRAM_END);


一种是板子有外扩 ram,这整个外扩 ram 可被设置为堆空间,被系统管理起来

rt_system_heap_init((void*)STM32_EXT_SRAM_BEGIN, (void*)STM32_EXT_SRAM_END);


内存管理设置好以后,应用程序就可以使用 rt_malloc、 rt_realloc、 re_free 等函数了。

3、 rt_application_init()

这个函数是为用户准备的,用户可以在这个函数里创建自己的应用线程。

线程

线程的组成

RT-Thread 中的“线程”一般由三部分组成:线程代码(函数)、线程控制块、线程堆



以led闪烁线程为例

线程代码:

void led_thread_entry(void* parameter)
{
unsigned int count=0;
rt_hw_led_init();
while(1)
{
#ifndef RT_USING_FINSH
rt_kprintf("led on, count : %d\r\n",count);
#endif
count++;
rt_hw_led_on(0);
rt_thread_delay( RT_TICK_PER_SECOND/2 );
#ifndef RT_USING_FINSH
rt_kprintf("led off\r\n");
#endif
rt_hw_led_off(0);
rt_thread_delay( RT_TICK_PER_SECOND/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 */

#ifdef RT_USING_MODULE
void       *module_id;                              /**< id of application module */
#endif

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_uint16_t stack_size;                             /**< stack size */

/* error code */
rt_err_t    error;                                  /**< error code */

rt_uint8_t  stat;
e72d
/**< thread stat */

/* 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;

#if defined(RT_USING_EVENT)
/* thread event */
rt_uint32_t event_set;
rt_uint8_t  event_info;
#endif

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 */
};
typedef struct rt_thread *rt_thread_t;


它记录了线程的各个属性,系统用线程控制块链表对其进行管理。

线程堆栈

static rt_uint8_t led_stack[512];


线程堆栈是一段连续的内存块,当线程切换后,为了满足线程切换和响应中断时保存

cpu 寄存器中的内容及任务调用其它函数时的准备,每个线程都要配有自己的堆栈

创建一个线程

RT-Thread 中的线程分为静态线程—线程堆栈由编译器静态分配,使用 rt_thread_init()

函数创建和动态线程—线程堆栈由系统动态分配,使用 rt_thread_create()函数创建。

代码:

/* 静态线程的线程堆栈*/
static rt_uint8_t led1_stack[512];
/* 静态线程的线程控制块 */
static struct rt_thread led1_thread;

void demo_thread_creat(void)
{
rt_err_t result;

/* 动态线程的线程控制块指针 */
rt_thread_t led2_thread;

rt_hw_led_init();

/* 创建静态线程:优先级 20 ,时间片 2 个系统滴答 */
result =rt_thread_init(&led1_thread,"led1",
static_thread_entry, RT_NULL,
(rt_uint8_t*)&led1_stack[0],
sizeof(led1_stack),20,2);
if(result == RT_EOK)
{
rt_thread_startup(&led1_thread);
}

/* 创建动态线程:堆栈大小 512 bytes ,优先级 21 ,时间片 2 个系统滴答 */
led2_thread =rt_thread_create("led2",
dynamic_thread_entry, RT_NULL,
512,21,2);
if(led2_thread != RT_NULL)
rt_thread_startup(led2_thread);
}


静态线程 VS 动态线程

从上例可看出,静态、动态线程在做同样的事情时,从效果上看,是没有任何差别的!

那么,我们在实际中如何抉择?

使用静态线程时,必须先定义静态的线程控制块,并且定义好堆栈空间,然后调用

rt_thread_init()来完成线程的初始化工作。采用这种方式,线程控制块和堆栈占用的内存

会放在 RW/ZI 段,这段空间在编译时就已经确定,它不是可以动态分配的,所以不能被释

放,而只能使用 rt_thread_detach()函数将该线程控制块从对象管理器中脱离。

使用动态定义方式 rt_thread_create()时, RT-Thread 会动态申请线程控制块和堆栈空间。

在编译时,编译器是不会感知到这段空间的,只有在程序运行时, RT-Thread 才会从系统堆

中申请分配这段内存空间,当不需要使用该线程时,调用 rt_thread_delete()函数就会将这段

申请的内存空间重新释放到内存堆中。

这两种方式各有利弊,静态定义方式会占用 RW/ZI 空间,但是不需要动态分配内存,

运行时效率较高,实时性较好。动态方式不会占用额外的 RW/ZI 空间,占用空间小,但是

运行时需要动态分配内存,效率没有静态方式高

线程的调度和管理

线程状态



 线程优先级 系统时钟

RT-Thread 共支持 256 个优先级(0-255,数值越小的优先级越高, 0 为最高优先级, 255分配给空闲线程使用;线程总数不受限制,只和能提供给系统的 ram 有关),一般通过在 rt_config.h 配置文件中将系统配置为 32 个优先级

/* PRIORITY_MAX */
#define RT_THREAD_PRIORITY_MAX 32


系统心跳时钟 时间片 根据CPU的处理能力决定 一般设置为10-100HZ,频率越快,系统负荷越大。在stm32平台一般设置为100hz 每个时间片10ms 在rt_config.h中可自行设置

/* Tick per Second */
#define RT_TICK_PER_SECOND 100//1 秒 100 次即 10ms 一次


RT-Thread 中配置系统时钟的代码如下:

SystemInit();
/* NVIC Configuration */
NVIC_Configuration();
/* 一上电就尽快配置系统时钟 */
SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );


空闲线程

是系统线程中一个比较特殊的线程,它具备最低的优先级,当系统中无其他线程可运行时,调度器将调度到空闲线程。空闲线程通常是一个死循环,永远不被挂起。在RT-Thread 实时操作系统中空闲线程提供了钩子函数,可以让系统在空闲的时候执行一定任务,例如系统运行指示灯闪烁, CPU 使用率统计等等。另外空闲线程还负责一些系统资源

回收。

线程调度规则

不同优先级的线程根据优先级顺序进行调度;相同优先级的线程根

据时间片来调度

线程间同步和通信

禁止系统调度

一般可通过关闭中断和调度器上锁这两种简单的途径来禁止系统调度,防止线程被打断,从而保证临界区不被破坏。

关闭中断

void test1_thread_entry(void* parameter)
{
rt_base_t level;
while(1)
{
/* 关闭中断*/
level = rt_hw_interrupt_disable();
/* 以下是临界区*/
. . . .
/* 关闭中断*/
rt_hw_interrupt_enable(level);
}
}


当我们关闭中断后,系统将不能再进行调度,即也不会被其他线程抢占

调度器上锁

void test1_thread_entry(void* parameter)
{
while(1)
{
/* 调度器上锁,上锁后,将不再切换到其他线程,仅响应中断 */
rt_enter_critical();
/* 以下进入临界区 */
. . . .
/* 调度器解锁 */
rt_exit_critical();
}
}


对调度器上锁,系统依然能响应外部中断,中断服务例程依然有可能被运行

信号量的基本操作

RT-Thread 中的信号量有静态和动态之分(同静态线程、动态线程),和信号量有关的

操作如下:

初始化—rt_sem_init()(对应静态信号量) ;

建立—rt_sem_create()(对应动态信号量);

获取—rt_sem_take();

释放—rt_sem_release();

脱离—rt_sem_detach()(对应静态信号量) ;

删除—rt_sem_delete()(对应动态信号量) ;

每释放一次信号量加1 获取一次信号量减一(如果不为0) 可指定等待时间

做按键实验的时候注意不要死循环释放了多次信号量,否则会跟预想结果不一样

互斥锁

互斥锁和信号量很相似, RT-Thread 中的互斥锁也有静态和动态之分,和互斥锁有关的

操作如下:

初始化—rt_mutex_init()(对应静态互斥锁);

建立—rt_mutex_create()(对应动态互斥锁);

获取—rt_mutex_take();

释放—rt_mutex_release();

脱离—rt_mutex_detach()(对应静态信号量) ;

删除—rt_mutex_delete()(对应动态信号量);

信号量到处都可以释放,互斥锁只有拥有其所有权才能释放。

具有数据交换功能的ipc对象:

邮箱

邮箱是一种典型的任务间通信方法,一封邮件只能容纳固定的4字节内容

中断服务和定时器都可以像邮箱发送信息,只有线程能够接收信息

RT-Thread 的邮箱中共可存放固定条数的邮件,邮箱容量在创建邮箱时设定需要在线程间传递比较大的消息时,可以传递指向一个缓冲区的指针

当邮箱满时,线程等不再发送新邮件,返回-RT EFULL。

当邮箱空时,将可能挂起正在试图接收邮件的线程,使其等待,当邮箱中有新邮件时,再唤醒等待在邮箱上的线程,使其能够接收新邮件并继续后续的处理

RT-Thread 中的邮箱依然是有静态和动态之分,和邮箱有关的操作如下:

初始化—rt_mb_init()(对应静态邮箱);

建立—rt_mb_create()(对应动态邮箱);

发送邮件—rt_mb_send();

接收邮件—rt_mb_recv();

脱离—rt_mb_detach()(对应静态邮箱) ;

删除—rt_mb_delete()(对应动态邮箱);

消息队列

消息队列能够接受来自线程的不固定长度的消息(相比邮件的4字节)并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。而当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。

消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。 RT-Thread 的消息队列对象由多个元素组成,当消息队列被创建时,它就被分配了消息队列控制块:队列名,内存缓冲区,消息大小以及队列长度等。同时每个消息队列对象中包含着多个消息框,每个消息框可以存放一条消息。消息队列中的第一个和最后一个消息框被分别称为队首和队尾,对应于消息队列控制块中的 msg_queue_head 和msg_queue_tail。所有消息队列中的消息框总数即是消息队列的长度,这个长度可在消息队列创建时指定。

初始化—rt_mq_init()(对应静态消息队列) ;

建立—rt_mq_create()(对应动态消息队列) ;

发送消息—rt_mq_send();

发送紧急消息—rt_mq_urgent();

接收消息—rt_mq_recv();

脱离—rt_mq_detach()(对应静态消息队列) ;

删除—rt_mq_delete()(对应动态消息队列) ;

消息队列提供发送紧急消息的操作,当发送紧急消息时,消息块不是挂到消息队列的队尾,而是挂到队首,这样,接收者能够优先接收到紧急消息,从而及时进行消息处理。

IPC对象会造成线程阻塞,不要在中断中尝试执行获取!!!

事件机制

RT-Thread 中的信号量主要用于“一对一”的线程同步,当需要“一对多”、“多对一”、“多对多”的同步时,就需要事件机制来处理了。

RT-Thread 中的事件用一个 32 位无符号整型变量来表示,变量中的一位代表一个事件,线程通过“逻辑与”或“逻辑或”与一个或多个事件建立关联形成一个事件集

事件的相关操作如下:

初始化—rt_event_init()(对应静态事件) ;

建立—rt_event_create()(对应动态事件) ;

发送事件—rt_event_send();

接收事件—rt_event_recv();

脱离—rt_event_detach()(对应静态事件) ;

删除—rt_event_delete()(对应动态事件) ;

全局变量

注意并发和竞态问题
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: