5. 裸机系统与多线程系统

在真正开始动手写RTOS之前,我们先来讲解下单片机编程中的裸机系统和多线程系统的区别。

5.1. 裸机系统

裸机系统通常分成轮询系统和前后台系统,有关这两者的具体实现方式请看下面的讲解。

5.1.1. 轮询系统

轮询系统即是在裸机编程的时候,先初始化好相关的硬件,然后让主程序在一个死循环里面不断循环,顺序地做各 种事情,大概的伪代码具体见 代码清单:多线程系统-1。轮询系统是一种非常简单的软件结构,通常只适用于那些只需要顺序 执行代码且不需要外部事件来驱动的就能完成的事情。在 代码清单:多线程系统-1 中,如果只是实现LED翻转,串口输出,液 晶显示等这些操作,那么使用轮询系统将会非常完美。但是,如果加入了按键操作等需要检测外部信号的事件,用 来模拟紧急报警,那么整个系统的实时响应能力就不会那么好了。假设DoSomething3是按键扫描,当外部按键被 按下,相当于一个警报,这个时候,需要立马响应,并做紧急处理,而这个时候程序刚好执行到DoSomething1,要 命的是DoSomething1需要执行的时间比较久,久到按键释放之后都没有执行完毕,那么当执行到DoSomething3的 时候就会丢失掉一次事件。足见,轮询系统只适合顺序执行的功能代码,当有外部事件驱动时,实时性就会降低。

代码清单:多线程系统-1 轮询系统伪代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
int main(void)
{
    /* 硬件相关初始化 */
    HardWareInit();
    /* 无限循环 */
    for (;;)
    {
        /* 处理事情1 */
        DoSomething1();
        /* 处理事情2 */
        DoSomething2();
        /* 处理事情3 */
        DoSomething3();
    }
}

5.1.2. 前后台系统

相比轮询系统,前后台系统是在轮询系统的基础上加入了中断。外部事件的响应在中断里面完成,事件的处理还 是回到轮询系统中完成,中断在这里我们称为前台,main函数里面的无限循环我们称为后台,大概的伪代码见 代码清单:多线程系统-1

代码清单:多线程系统-2 前后台系统伪代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
int flag1 = 0;
int flag2 = 0;
int flag3 = 0;

int main(void)
{
    /* 硬件相关初始化 */
    HardWareInit();
    /* 无限循环 */
    for (;;)
    {
        if (flag1)
        {
            /* 处理事情1 */
            DoSomething1();
        }

        if (flag2)
        {
            /* 处理事情2 */
            DoSomething2();
        }

        if (flag3)
        {
            /* 处理事情3 */
            DoSomething3();
        }
    }
}

void ISR1(void)
{
    /* 置位标志位 */
    flag1 = 1;
    /* 如果事件处理时间很短,则在中断里面处理如果事件处理时间比较长,在回到前台处理 */
    DoSomething1();
}

void ISR2(void)
{
    /* 置位标志位 */
    flag2 = 1;
    /* 如果事件处理时间很短,则在中断里面处理如果事件处理时间比较长,在回到前台处理 */
    DoSomething2();
}

void ISR3(void)
{
    /* 置位标志位 */
    flag3 = 1;
    /* 如果事件处理时间很短,则在中断里面处理如果事件处理时间比较长,在回到前台处理 */
    DoSomething3();
}

在顺序执行后台程序的时候,如果有中断来临,那么中断会打断后台程序的正常执行流,转而去执行中断服务程序,在中断服务程序里面标记事件,如果事件要处理的事情很简短,则可在中断服务程序里面处理,如果事件要处理的事情比较多,则返回到后台程序里面处理。虽然事件的响应和处理是分开了,但是事件的处理还是在后台里面顺 序执行的,但相比轮询系统,前后台系统确保了事件不会丢失,再加上中断具有可嵌套的功能,这可以大大的提高程序的实时响应能力。在大多数的中小型项目中,前后台系统运用的好,堪称有操作系统的效果。

5.2. 多线程系统

相比前后台系统,多线程系统的事件响应也是在中断中完成的,但是事件的处理是在线程中完成的。在多线程系统中,线程跟中断一样,也具有优先级,优先级高的线程会被优先执行。当一个紧急的事件在中断被标记之后,如果事件对应的线程的优先级足够高,就会立马得到响应。相比前后台系统,多线程系统的实时性又被提高了。多线程 系统大概的伪代码具体见 代码清单:多线程系统-3

代码清单:多线程系统-3 多线程系统伪代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
int flag1 = 0;
int flag2 = 0;
int flag3 = 0;

int main(void)
{
    /* 硬件相关初始化 */
    HardWareInit();
    /* OS初始化 */
    RTOSInit();
    /* OS启动,开始多线程调度,不再返回 */
    RTOSStart();
}

void ISR1(void)
{
    /* 置位标志位 */
    flag1 = 1;
}

void ISR2(void)
{
    /* 置位标志位 */
    flag2 = 2;
}

void ISR3(void)
{
    /* 置位标志位 */
    flag3 = 1;
}

void DoSomething1(void)
{
    /* 无限循环,不能返回 */
    for (;;)
    {
        /* 线程实体 */
        if (flag1) {
        }
    }
}

void DoSomething2(void)
{
    /* 无限循环,不能返回 */
    for (;;)
    {
        /* 线程实体 */
        if (flag2) {
        }
    }
}

void DoSomething3(void)
{
    /* 无限循环,不能返回 */
    for (;;)
    {
        /* 线程实体 */
        if (flag3) {
        }
    }
}

相比前后台系统中后台顺序执行的程序主体,在多线程系统中,根据程序的功能,我们把这个程序主体分割成一个个独立的,无限循环且不能返回的小程序,这个小程序我们称之为线程。每个线程都是独立的,互不干扰的,且具备自身的优先级,它由操作系统调度管理。加入操作系统后,我们在编程的时候不需要精心地去设计程序的执行流 ,不用担心每个功能模块之间是否存在干扰。加入了操作系统,我们的编程反而变得简单了。整个系统随之带来的额外开销就是操作系统占据的那一丁点的FLASH和RAM。现如今,单片机的FLASH和RAM是越来越大,完全足以抵挡RTOS那点开销。

无论是裸机系统中的轮询系统、前后台系统和多线程系统,我们不能一锤子的敲定孰优孰劣,它们是不同时代的产 物,在各自的领域都还有相当大的应用价值,只有合适才是最好。有关这三者的软件模型区别具体见下表。

表格:轮询、前后台和多线程系统软件模型区别

模型

事件响应

事件处理

特点

轮询系统

主程序

主程序

轮询响应事件,轮询处理事件

前后台系统

中断

主程序

实时响应事件,轮询处理事件

多线程系统

中断

线程

实时响应事件,实时处理事件