在我们使用uCOS/FreeRTOS编写代码时,首先要面临的一个问题是怎样创建任务并启动整个系统。一般来说,我们会有两种不同的方式。这两种方式不仅适用于uCOS/FreeRTOS,同时也适用于其它RTOS。
这两种方式各有优缺点。如果用一幅图来表示的话,见下方。
下面将分别说明这两种方式的具体应用方法。
这种方式是这样的:在启动启动前创建一个初始任务,然后在该任务跑起来后,再完成其它软硬件初始化、创建启动任务。
int main () {
OSInit();
CreateFirstTask(); // 创建初始任务
OSStart();
}
void FirstTask () {
DataInit(); // 初始化应用相关的数据
HardwareInit(); // 初始化硬件模块
CreateTask1(); // 创建任务1
CreateTask2(); // 创建任务2
}
相于接下来讲到的方式二而言,这种方式的一大优点是:所有的初始化及任务创建都是在整个跑起来之后进行。如果这些是在系统跑起来之前进行的话,则有可能存在潜在的隐患。具体可以看下面的方式二。
在完成所有初始化操作之后,该初始任务可以继续完成一些其它的功能。当然,也有的人直接让任务自行删除。不过我认为自行删除并不必要,因为在任务任务时,我们可能是通过全局变量定义任务的Task结构和堆栈空间。删除之后,这些空间肯定还要用的吧?如果是分配给别的任务,那么还得再创建一个任务。既然如此,那么为什么不直接不删除呢?
要特别注意一点:初始任务的优先级应尽可能的高。因为有些RTOS的任务创建接口中,创建任务之后可能会立即启动该任务,如果新任务的优先级比初始任务的优先级更高,则会切换至新任务运行。而这与我们所期望的流程就不一致。
这是最简单也是最常用的方式。简单来说是这样:在main()中调用RTOS的初始化函数,中间初始化软硬件、创建其它任务,然后再调用启动os的API。以uCOS为例,如果采用这种方式,其初始化流程可能是这样的。
int main() {
OSInit();
DataInit(); // 初始化应用相关的数据
HardwareInit(); // 初始化硬件模块
CreateTask1(); // 创建任务1
CreateTask2(); // 创建任务2
OSStart();
return 0;
}
可以看到采用这种方式,在整个系统跑起来之前已经完成了所有任务的初始化,系统跑起来之后,各个任务就可以专注实现其要实现的功能。
但是这种方式有一个隐藏的缺陷:如果在初始化硬件模块时产生中断,且在中断服务函数中又调用了RTOS相关的API,则有可能导致系统崩溃。
原因是什么呢?
这是因为当前RTOS还没有跑起来,甚至于有些内核数据还没初始化好。此时如果发生中断,在中断中调用的RTOS的API可能访问这些内核数据,或者尝试执行只能在RTOS跑起来后执行的操作。
举个实际的例子,假设当前正在配置GPIO外部上沿升输入中断,刚刚开启中断配置使能后恰恰外部输入了一个上升沿信号(外部往往不可控),则立即进入GPIO中断处理函数中。在中断处理函数中,简单快速处理后向Task 1发送消息。发送完消息后,退出中断时会执行任务切换,尝试切换至Task 1,而此时当前还没有任务运行,那么此时应该怎样完成切换呢?
当然,不排除RTOS在设计时有考虑这种情况。但是你能确定所用的RTOS一定能正确处理这种异常问题吗?
所以,如果你的应用中可能会遇到这种问题,与其花时间研究是否能正确处理这种问题,倒不如直接采用方式一,这是最稳妥也最简单的方式。
总结以上内容,可以看出方式一相对于方式二有着明显的优点:即系统启动之前不会受外部事件干扰的影响。
正是因为这样,我个人比较推荐第一种。目前,在有些RTOS的实现中,默认采用了方式一。其内部已经自动帮我们创建了一个初始任务,我们只需要额外实现一个其定义好的接口函数,然后在函数中添加软硬件初始化及其它任务的创建即可。
当然,实际在写代码时,采用的具体方式可能并不完全同上述两种方式,但基本流程差不太多。
该文章同时发布在:http://01ketang.cc/rtos/rtos-init.html