基于STM32F103的FreeRTOS系列(二)·多任务系统

基于STM32F103的FreeRTOS系列(一)·单片机设计模式介绍·裸机程序的设计模式-CSDN博客

目录

1.  多任务模式

2.  互斥操作

3.  同步操作


1.  多任务模式

        对于裸机程序,无论使用哪种模式进行精心的设计,在最差的情况下都无法解决这个问题:假设有A、B两个都很耗时的函数,无法降低它们相互之间的影响。使用状态机模式时,如果函数拆分得不好,也会导致这个问题。本质原因是:函数是轮流执行的。假设“喂一口饭”需要t1~t5这5段时间,“回一个信息需要”ta~te这5段时间,轮流执行时:先执行完t1~t5,再执行ta~te,如下图所示:

对于职场妈妈,她怎么解决这个问题呢?她是一个眼明手快的人,可以一心多用,她这样做:

  • 左手拿勺子,给小孩喂饭
  • 右手敲键盘,回复同事
  • 两不耽误,小孩“以为”妈妈在专心喂饭,同事“以为”她在专心聊天
  • 但是脑子只有一个啊,虽然说“一心多用”,但是谁能同时思考两件事?
  • 只是她反应快,上一秒钟在考虑夹哪个菜给小孩,下一秒钟考虑给同事回复什么信息
  • 本质是:交叉执行,t1~t5和ta~te交叉执行,如下图所示:

基于多任务系统编写程序时,示例代码如下:

// RTOS程序
喂饭任务()
{
    while (1)
    {
        喂一口饭();
    }
}

回信息任务()
{
    while (1)
    {
        回一个信息();
    }
}

void main()
{
    // 创建2个任务
    create_task(喂饭任务);
    create_task(回信息任务);

    // 启动调度器
    start_scheduler();
}

        首先,创建2个任务:

    create_task(喂饭任务);
    create_task(回信息任务);

        启动调度器:

 start_scheduler();

        之后,这2个任务就会交叉执行了

        基于多任务系统编写程序时,反而更简单了:“喂饭任务”的代码和“回信息任务”的代码,编写它们时甚至都不需要考虑它和其他函数的相互影响。就好像有2个单板:一个只运行“喂饭任务”这个函数、另一个只运行“回信息任务”这个函数。

        多任务系统会依次给这些任务分配时间:你执行一会,我执行一会,如此循环。只要切换的间隔足够短,用户会“感觉这些任务在同时运行”。如下图所示:

2.  互斥操作

        多任务系统中,多个任务可能会“同时”访问某些资源,需要增加保护措施以防止混乱。比如任务A、B都要使用串口,能否使用一个全局变量让它们独占地、互斥地使用串口?示例代码如下:

// RTOS程序
int g_canuse = 1;

void uart_print(char *str)
{
    if (g_canuse)
    {
        g_canuse = 0;
        printf(str);
        g_canuse = 1;
    }
}

task_A()
{
    while (1)
    {
        uart_print("0123456789\n");
    }
}

task_B()
{
    while (1)
    {
        uart_print("abcdefghij");
    }
}

void main()
{
    // 创建2个任务
    create_task(task_A);
    create_task(task_B);
    // 启动调度器
    start_scheduler();
}

        程序的意图是:task_A打印“0123456789”,task_B打印“abcdefghij”。在task_A或task_B打印的过程中,另一个任务不能打印,以避免数字、字母混杂在一起,比如避免打印这样的字符:“012abc”。

        使用全局变量g_canuse实现互斥打印,它等于1时表示“可以打印”。在进行实际打印之前,先把g_canuse设置为0,目的是防止别的任务也来打印。

        这个程序大部分时间是没问题的,但是只要它运行的时间足够长,就会出现数字、字母混杂的情况。下图把uart_print函数标记为①~④个步骤:

void uart_print(char *str)
{
    if( g_canuse )     ①
    {
        g_canuse = 0;  ②
        printf(str);   ③
        g_canuse = 1;  ④
    }
}

        task_B执行①时也会成功进入if语句,假设它执行到③,在printf打印完部分字符比如“abc”后又再次被切换为task_A。

        task_A继续从上次被暂停的地方继续执行,即从②那里继续执行,成功打印出“0123456789”。这时在串口上可以看到打印的结果为:“abc0123456789”。

        是不是“①判断”、“②清零”间隔太远了,uart_print函数改进成如下的代码呢?

oid uart_print(char *str)
{
    g_canuse--;            ① 减一
    if( g_canuse == 0 )    ② 判断
    {
        printf(str);     ③ 打印
    }
    g_canuse++;          ④ 加一
}

        即使改进为上述代码,仍然可能产生两个任务同时使用串口的情况。因为“①减一”这个操作会分为3个步骤:a.从内存读取变量的值放入寄存器里,b.修改寄存器的值让它减一,c.把寄存器的值写到内存上的变量上去。

        如果task_A执行完步骤a、b,还没来得及把新值写到内存的变量里,就被切换为task_B:在这一瞬间,g_canuse还是1。

        task_B执行①②时也会成功进入if语句,假设它执行到③,在printf打印完部分字符比如“abc”后又再次被切换为task_A。

        task_A继续从上次被暂停的地方继续执行,即从步骤c那里继续执行,成功打印出“0123456789”。这时在串口上可以看到打印的结果为:“abc0123456789”。

        从上面的例子可以看到,基于多任务系统编写程序时,访问公用的资源的时候要考虑“互斥操作”。任何一种多任务系统都会提供相应的函数。

3.  同步操作

        如果任务之间有依赖关系,比如任务A执行了某个操作之后,需要任务B进行后续的处理。如果代码如下编写的话,任务B大部分时间做的都是无用功。

// RTOS程序
int flag = 0;

void task_A()
{
    while (1)
    {
        // 做某些复杂的事情
        // 完成后把flag设置为1
        flag = 1;
    }
}

void task_B()
{
    while (1)
    {
        if (flag)
        {
            // 做后续的操作
        }
    }
}

void main()
{
    // 创建2个任务
    create_task(task_A);
    create_task(task_B);
    // 启动调度器
    start_scheduler();
}

        上述代码中,在任务A没有设置flag为1之前,任务B的代码都只是去判断flag。而任务A、B的函数是依次轮流运行的,假设系统运行了100秒,其中任务A总共运行了50秒,任务B总共运行了50秒,任务A在努力处理复杂的运算,任务B仅仅是浪费CPU资源。

        如果可以让任务B阻塞,即让任务B不参与调度,那么任务A就可以独占CPU资源加快处理复杂的事情。当任务A处理完事情后,再唤醒任务B。示例代码如下:

// RTOS程序
void task_A()
{
    while (1)
    {
        // 做某些复杂的事情
        // 释放信号量,会唤醒任务B;
    }
}

void task_B()
{
    while (1)
    {
        // 等待信号量, 会让任务B阻塞
        // 做后续的操作
    }
}

void main()
{
    // 创建2个任务
    create_task(task_A);
    create_task(task_B);
    // 启动调度器
    start_scheduler();
}
  • 第15行:任务B运行时,等待信号量,不成功时就会阻塞,不在参与任务调度。
  • 第7行:任务A处理完复杂的事情后,释放信号量会唤醒任务B。
  • 第16行:任务B被唤醒后,从这里继续运行。

在这个过程中,任务A处理复杂事情的时候可以独占CPU资源,加快处理速度。

FreeRTOS_时光の尘的博客-CSDN博客

相关推荐

  1. 基于STM32F103路灯监控系统设计

    2024-07-23 07:14:04       53 阅读
  2. 基于STM32F103智能书房系统设计

    2024-07-23 07:14:04       52 阅读
  3. 基于STM32F103C8宠物喂食系统设计

    2024-07-23 07:14:04       59 阅读
  4. 基于STM32F103维码识别项目

    2024-07-23 07:14:04       26 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-23 07:14:04       143 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-23 07:14:04       157 阅读
  3. 在Django里面运行非项目文件

    2024-07-23 07:14:04       132 阅读
  4. Python语言-面向对象

    2024-07-23 07:14:04       142 阅读

热门阅读

  1. PostgreSQL 8.4 ROW_NUMBER()函数

    2024-07-23 07:14:04       24 阅读
  2. 通过队列名寻找某队列-linux

    2024-07-23 07:14:04       24 阅读
  3. springboot业务逻辑写在controller层吗

    2024-07-23 07:14:04       24 阅读
  4. linux本地互传文件

    2024-07-23 07:14:04       23 阅读
  5. 异步TCP服务器;异步TCP客户端

    2024-07-23 07:14:04       24 阅读
  6. 【摸鱼笔记】了解itertools,优雅处理list

    2024-07-23 07:14:04       24 阅读
  7. Windows图形界面(GUI)-DLG-C/C++ - 滑动条(Trackbar)

    2024-07-23 07:14:04       28 阅读
  8. 【ffmpeg命令入门】再论ffmpeg通用选项

    2024-07-23 07:14:04       23 阅读
  9. windows启动不打开窗口命令

    2024-07-23 07:14:04       29 阅读
  10. Python应用—浅谈利用opencv去除水印

    2024-07-23 07:14:04       23 阅读
  11. [网络基础]——IP、MAC、子网掩码

    2024-07-23 07:14:04       28 阅读