RT-Thread—RTT启动流程

文章目录

  • RTOS的俩种启动方式
    • 方式一:先创建所有线程,再调度
    • 方式二:在启动线程中,创建线程
  • RTT的启动流程
    • 1.上电后首先执行的复位函数
    • 2.调用__main函数
    • 3.main函数的预操作
      • `$Sub$$main`函数
      • `rtthread_startup()`函数
      • `rt_application_init()`函数
      • `main_thread_entry`函数
    • 4.main函数中创建线程

RTOS的俩种启动方式

RTOS有俩种主流的启动方式:
1.将准备工作(包括创建线程)都做好后,启动RTOS调度器
2.先创建一个启动线程并启动RTOS调度器,在启动线程中创建其他应用线程

俩种方式有着截然不同的思路,下面简单介绍一下俩种启动方式的流程:

方式一:先创建所有线程,再调度

在main函数中完成以下任务:

  • 初始化硬件
  • 初始化RTOS系统
  • 创建应用线程
  • 启动RTOS调度器

通过伪代码来简单描述一下启动流程:

int main( void )
{
    /* 硬件初始化 */
	HardWare_Init();
	/* RTOS系统初始化 */
	RTOS_Init();
	/* 创建应用线程 */
	RTOS_ThreadCreate( Thread1 );
	RTOS_ThreadCreate( Thread2 );
	……
	/* 启动RTOS调度器 */
	RTOS_Start();
}

方式二:在启动线程中,创建线程

启动步骤如下:

  • 在main函数中初始化硬件
  • 在main函数中初始化RTOS系统
  • 在main函数中创建启动线程
  • 在main函数中开启RTOS调度
  • 在启动线程中创建应用线程

通过伪代码来简单描述一下启动流程:

int main( void )
{
	/* 初始化硬件 */
	HardWare_Init();
	/* 初始化RTOS系统 */
	RTOS_Init();
	/* 创建启动线程 */
	RTOS_ThreadCreate( AppThreadStart );
	/* 启动RTOS调度器 */
	RTOS_Start();
}

void AppThreadStart( void *arg )
{
	/* 创建应用线程 */
	RTOS_ThreadCreate( Thread1 );
	RTOS_ThreadCreate( Thread2 );
	……
	/* 关闭启动线程 */
	RTOSThreadClose( AppThreadStart );
}

注意:在起始线程中创建完应用线程后要,关闭起始线程。

ucos第一种第二种启动方法都可以;freertos和RTT默认使用第二种。

RTT的启动流程

RTT中使用的是第二种启动方式,也就是先创建一个启动线程并开启系统调度,然后在启动线程中创建其他应用线程。上面说了,一些初始化工作和创建启动线程的工作都在main函数中完成,可实际中RTT例程中main函数中并没有做这些工作!!!
RTT的实际启动流程比上面讲的高级一点,大体流程如下:

  • 系统上电后首先执行启动文件中的复位函数
  • 复位函数最后一步会调用C库的__main函数
  • __main函数中初始化系统堆栈
  • 执行完__main函数后跳转到$Sub$$main函数
  • $Sub$$main函数中按照上面方式二进行配置
  • 通过$Super$$main函数跳转到main函数中
  • main函数中创建应用线程

所以RTT的启动流程相比于之前的,只是把方式二的操作放在$Sub$$main函数中执行了,下面捋一捋RTT的具体启动流程:

1.上电后首先执行的复位函数

复位函数是由汇编语言写的,代码如下:

Reset_Handler PROC
 EXPORT Reset_Handler [WEAK]
 IMPORT SystemInit
 IMPORT __main

 CPSID I ; 关中断
 LDR R0, =0xE000ED08
 LDR R1, =__Vectors
 STR R1, [R0]
 LDR R2, [R1]
 MSR MSP, R2
 LDR R0, =SystemInit
 BLX R0
 CPSIE i ; 开中断
 
 LDR R0, =__main  ;在这里进入__main函数
 
 BX R0
 ENDP

关于汇编语言之前整理过一部分,链接:汇编语言基本知识
其余的代码和RTT启动流程没啥关系。

2.调用__main函数

__main函数主要就是初始化一下系统堆栈,然后在函数的最后跳转到$Sub$$main函数,由于__main函数中的其他部分与RTT启动流程没关系,所以就不讲了,__main函数主要就是一个过渡的作用,从复位函数过渡到$Sub$$main函数。

3.main函数的预操作

$Sub$$函数与$Super$$函数是编译器(此处是KEIL)自带的一对扩展函数,它们的作用就是对要扩展的函数执行预操作,比如$Sub$$main就是在main函数执行前,执行的函数,执行完$Sub$$main函数后,用$Super$$main函数跳转到main函数中。

了解$Sub$$main函数的原理后,来看一下RTT中$Sub$$main函数的代码:

$Sub$$main函数

int $Sub$$main( void )
{
	/* 关闭中断 */
	rt_hw_interrupt_disable();
	/* 启动RTT */
	rtthread_startup();
	return 0;
}

$Sub$$main函数中是通过rtthread_startup()函数来启动RTT的。

rtthread_startup()函数

int rtthread_startup( void )
{
	/* 关中断,在硬件初始化前习惯性的关中断 */
	rt_hw_interrupt_disable();
	/* 硬件初始化 */
	rt_hw_board_init();
	/* 定时器初始化 */
	rt_system_timer_init();
	/* 调度器初始化 */
	rt_system_scheduler_init();
	
#ifdef RT_USING_SIGNALS
	/* 信号量初始化 */
	rt_system_signal_init();
#endif

	/* 创建启动线程 */
	rt_application_init();
	/* 初始化定时器线程 */
	rt_system_timer_thread_init();
	/* 初始化空闲线程 */
	rt_thread_idle_init();
	
	/* 启动调度器 */
	rt_system_scheduler_start();
	return 0;
}

重点是rt_application_init()函数,因为创建启动线程就在此函数中进行,要注意的是rt_application_init()函数只是对启动线程进行创建,并没有启动系统调度,系统调度是在最后才启动的。

rt_application_init()函数

void rt_application_init(void)
{
    rt_thread_t tid;

#ifdef RT_USING_HEAP
    /* 静态创建启动线程 */
    tid = rt_thread_create("main", 
   						   main_thread_entry, 
 						   RT_NULL,
       					   RT_MAIN_THREAD_STACK_SIZE, 
       					   RT_THREAD_PRIORITY_MAX / 3,
        				   20);
    RT_ASSERT(tid != RT_NULL);
#else
    rt_err_t result;
    /* 动态创建启动线程 */
    tid = &main_thread;
    result = rt_thread_init(tid, 
                            "main", main_thread_entry, 
                            RT_NULL,
                            main_stack, 
                            sizeof(main_stack), 
                            RT_THREAD_PRIORITY_MAX / 3, 
                            20);
    RT_ASSERT(result == RT_EOK);
	
    /* if not define RT_USING_HEAP, using to eliminate the warning */
    (void)result;
#endif

    rt_thread_startup(tid);
}

创建启动线程有静态创建和动态创建俩种方法,具体的方式可以根据宏定义来选择。
可以看到启动线程的人口函数是main_thread_entry(),下面再看一下main_thread_entry()函数。

main_thread_entry函数

void main_thread_entry( void *parameter )
{
	extern int main( void );
	extern int $Super$$main( void );

	/* RTT组件初始化 */
	rt_components_init();
	/* 跳转到main函数 */
	$Super$$main();
}

4.main函数中创建线程

int main(void)
 {
 /*
 * 开发板硬件初始化, RT-Thread 系统初始化已经在 main 函数之前完成,
 * 即在 component.c 文件中的 rtthread_startup()函数中完成了。 (1)
 * 所以在 main 函数中,只需要创建线程和启动线程即可。
 */

 thread1 = /* 线程控制块指针 */
 rt_thread_create("thread1", /* 线程名字,字符串形式 */
 thread1_entry, /* 线程入口函数 */
 RT_NULL, /* 线程入口函数参数 */
 HREAD1_STACK_SIZE, /* 线程栈大小,单位为字节 */
 THREAD1_PRIORITY, /* 线程优先级,数值越大,优先级越小 */
 THREAD1_TIMESLICE); /* 线程时间片 */

 if (thread1 != RT_NULL)
 rt_thread_startup(thread1);
 else
 return -1;

 thread2 = /* 线程控制块指针 */
 rt_thread_create("thread2", /* 线程名字,字符串形式 */
 thread2_entry, /* 线程入口函数 */
 RT_NULL, /* 线程入口函数参数 */
 THREAD2_STACK_SIZE, /* 线程栈大小,单位为字节 */
 THREAD2_PRIORITY, /* 线程优先级,数值越大,优先级越小 */
 THREAD2_TIMESLICE); /* 线程时间片 */

 if (thread2 != RT_NULL)
 rt_thread_startup(thread2);
 else
 return -1;
 (4)
 thread3 = /* 线程控制块指针 */
 rt_thread_create("thread3", /* 线程名字,字符串形式 */
 thread3_entry, /* 线程入口函数 */
 RT_NULL, /* 线程入口函数参数 */
 THREAD3_STACK_SIZE, /* 线程栈大小,单位为字节 */
 THREAD3_PRIORITY, /* 线程优先级,数值越大,优先级越小 */
 THREAD3_TIMESLICE); /* 线程时间片 */

 if (thread3 != RT_NULL)
 rt_thread_startup(thread3);
 else
 return -1;

 /* 执行到最后,通过 LR 寄存器执行的地址返回 */ (5)
 }

在main函数中只需要创建相应的应用线程即可!

你可能感兴趣的:(RT-Thread)