FreeRTOS的编程风格

学习一个RTOS,搞懂它的编程的风格很重要,这可以大大提供我们阅读代码的效率。下面我们就以 FreeRTOS里面的数据类型、变量名、函数名和宏这几个方面做简单介绍。

1、数据类型

FreeRTOS中,使用的数据类型虽然都是标准C里面的数据类型,但是针对不同的处理器,对标准C的数据类型又进行了重定义,给它们取了一个新的名字,比如char重新定义了一个名字 porCHAR,这里面的port表示接口的意思,就是FreeRTOS要移植到这些处理器上需要这些接口文件来把它们连接在一起。但是用户在写程序的时候并非一定要遵循 FreeRTOS的风格,我们还是可以直接用C语言的标准类型。在FreeRTOS中**,int型从不使用**,只使用shortlong型。在Cortex-M内核的MCU中,short为16位,long为32位

FreeRTOS中详细的数据类型重定义在portmacro.h这个头文件中实现
【FreeRTOS】FreeRTOS学习笔记(2)— 学习FreeRTOS的编程风格和本质_linux【FreeRTOS】FreeRTOS学习笔记(2)— 学习FreeRTOS的编程风格和本质_linux_02

/* Type definitions. */
    #define portCHAR          char
    #define portFLOAT         float
    #define portDOUBLE        double
    #define portLONG          long
    #define portSHORT         short
    #define portSTACK_TYPE    uint32_t
    #define portBASE_TYPE     long

    typedef portSTACK_TYPE   StackType_t;
    typedef long             BaseType_t;
    typedef unsigned long    UBaseType_t;

    #if ( configUSE_16_BIT_TICKS == 1 )
        typedef uint16_t     TickType_t;
        #define portMAX_DELAY              ( TickType_t ) 0xffff
    #else
        typedef uint32_t     TickType_t;
        #define portMAX_DELAY              ( TickType_t ) 0xffffffffUL

/* 32-bit tick type on a 32-bit architecture, so reads of the tick count do
 * not need to be guarded with a critical section. */
        #define portTICK_TYPE_IS_ATOMIC    1
    #endif
/*-----------------------------------------------------------*/

在编程的时候,如果用户没有明确指定char的符号类型,那么编译器会默认的指定char型的变量为无符号或者有符号。正是因为这个原因,在FreeRtos中,我们都需要明确的指定变量char是有符号的还是无符号的。在keil中,默认char是无符号的,但是也可以配置为有符号的,具体配套过程见下图。
【FreeRTOS】FreeRTOS学习笔记(2)— 学习FreeRTOS的编程风格和本质_数据类型_03

2、变量名

FreeRTOS中,定义变量的时候往往会把变量的类型当作前缀加在变量上,这样的好处是让用户一看到这个变量就知道该变量的类型。比如char型变量的前缀是cshort型变量的前缀是slong型变量的前缀是lportBASE_TYPE类型变量的前缀是x。还有其他的数据类型,比如数据结构,任务句柄,队列句柄等定乂的变量名的前缀也是ⅹ。如果一个变量是无符号型的那么会有一个前缀u,如果是一个指针变量则会有一个前缀p。因此,当我们定义一个无符号的char型变量的时候会加一个u前缀,当定义一个char型的指针变量的时候会有一个pc前缀

3、函数名

函数名包含了函数返回值的类型、函数所在的文件名和函数的功能,如果是私有的函数则会加一个prv(private)的前缀。特别的,在函数名中加入了函数所在的文件名,这大大的帮助了用户提高寻找函数定义的效率和了解函数作用的目的,具体的举例如下

1、vTaskPrioritySet()函数的返回值为void型,在task.c这个文件中定义。
2、xQueueReceive()函数的返回值为portBASE_TYPE型(long),在queue.c这个文件中定义。
3、vSmaphoreCreateBinary()函数的返回值为void型,在semper.h这个文件中定义。

4、宏

宏均是由大写字母表示,并配有小写字母的前缀,前缀用于表示该宏在哪个头文件定义,部分举例具体见下表
【FreeRTOS】FreeRTOS学习笔记(2)— 学习FreeRTOS的编程风格和本质_数据类型_04这里有个地方要注意的是信号量的函数都是一个宏定义,但是它的函数的命名方法是遵循函数的命名方法而不是宏定义的方法。

在贯穿FreeRTOS的整个代码中,还有几个通用的宏定义我们也要注意下,都是表示0和1的宏
【FreeRTOS】FreeRTOS学习笔记(2)— 学习FreeRTOS的编程风格和本质_数据类型_05

5、格式

TAB键盘等于四个空格键。我们在编程的时候最好使用空格键而不是使用TAB键,当两个编译器的TAB键设置的大小不一样的时候,代码移植的时候代码的格式就会变乱,而使用空格键则不会出现这种问题。

RTOS的简单介绍

RTOS很简单,听起来叫做实时操作系统,有一点吓唬人。但是学起来真的很简单,你不要把他想象的太复杂。这玩意其实就是一个任务调度器,在裸机中程序只有一个死循环,但是使用了RTOS程序中就有了多个死循环,RTOS就是调度每个死循环依次执行,执行的速度很快,看起来就相当于并行执行。

既然叫RTOS肯定是实时操作系统,与他相对应的就叫做OS操作系统,一个实时一个非实时。实际上不管是实时的还是非实时的,事件的响应都是有时间延时的,但是非实时的延时比较长而实时则很短,这种长短对于人来并不敏感,但是对于设备来说却很敏感,特别是对实时性要求很高的设备来说,实时性与否将会非常关键。所以你在初学时不要纠结实时还是非实。

我们在单片机或者其他MCU上使用的都是实时操作系统RTOS,常见实时OS有VXworks、NUcleus、QNX、RT-Linux、Ucos、RTX、embos、FreeR0S、MQX、Ali0S、LiteOS,其中Ucos、FreeRTOS为比较常用的实时OS,而RTX是ARM公司自己推出的实时OS。常见非实时OS消费类电子的民用产品比如PC、手机、平板等所用OS大多是非实时的,这类产品常用的OS有windows、unⅸx、linux、OS、安卓(基于 Linux)。

RTOS的特点

1、OS要做的事情很简单,核心就是任务管理(线程管理)在RTOS这种简单OS中,任务指的就是线程。
2、控制硬件时,应用代码直接调用底层代码,不经过OS的转换,RTOS只进行任务管理和存储管理。
【FreeRTOS】FreeRTOS学习笔记(2)— 学习FreeRTOS的编程风格和本质_数据类型_06

任务与进程、线程的关系

在复杂OS中任务就是进程,但是在RTOS这种简单实时OS中不存在进程这个东西,有的只是线程,因此在RTOS中任务就是线程

RTOS的本质

1、其实一个线程管理器

RTOS其实就是一个线程管理器,所有的线程并发运行,RTOS的“任务管理”会负责线程的调度,因此RTOS就是一个简单的线程管理器,事实上线程的实现原理并不复杂。

2、RTOS线程的实现原理

每一个线程本质就是函数,当该函数在没有被注册为线程时就是一个普通的函数,我们可以以普通函数的方式去调用,当注册为线程之后就是一个线程函数,线程的特定就是并发运行。

所谓并发运行就是某个线程函数运行了一段时间后就会切换运行其它线程,然再切换运行其它线程,然后再切换回原来的线程接着运行,由于每个线程运行的时间片很短,而且切换的速度又非常快,因此在宏观上我们会感觉到所有的线程都在同时运行,这就是并发运行

A线程切换运行其它线程时(vTaskStartScheduler()就是用来开启任务调度的),为了保证能够再一次切换回A线程上,切换时必须保存A线程被中断处的信息**(TCB任务控制块来保存)**,然后才能通过中断信息返回,实际上我们调用普通函数时,为能够返回到调用处(中断处),也必须保存被调用中断处的信息,道理其实都是一样的。

【FreeRTOS】FreeRTOS学习笔记(2)— 学习FreeRTOS的编程风格和本质_任务管理_07
【FreeRTOS】FreeRTOS学习笔记(2)— 学习FreeRTOS的编程风格和本质_任务管理_08
定时器所定的时间片到后,PC就会指向下一个线程的中断处(或者最开始处),CPU开始切换运行该线程的指令,OS所用的定时器就是我们以前课程所介绍过的systick定时器,systick定时器就是专门用来给RTOS干这个事情的。

不过为了让任务能够处理实时性的事件,凡是处理实时性事件的高优先级任务,可以不等当前线程的时间片到而直接抢占CPU运行,总之高优先级的线程(任务)可以抢占低优先级的CPU资源,以保证能够实时的处理实时性事件。

CPU从当前线程切换运行其它线程时,到底切换到哪一个线程,以及高优先级线程如何实现抢占,这都是由RTOS的任务管理来实现的,所以说RTOS的本质其实就是一个线程(任务)管理器。

线程|D

每个线程(任务)都有一个唯一识别号,这个识别号就是线程ID,任务管理器就是通过这个ID来识别和管理线程的。

掌握了这些知识,然后我们再去看用FreeRTOS写得代码,心中就清楚多了。