本帖最后由 DKENNY 于 2024-5-20 20:00 编辑
#申请原创# @21小跑堂
在嵌入式系统开发中,实时操作系统(RTOS)扮演着至关重要的角色,特别是在需要处理多任务并发执行的场景下。Mbed OS作为一款流行的RTOS,为开发者提供了丰富的功能和工具,使得嵌入式应用程序的开发变得更加高效和可靠。
## 1、嵌入式实时操作系统概述
在日常使用中,我们常说的操作系统是一种软件,它帮助管理和控制计算机的各种资源,包括硬件和软件。比如我们熟知的 Windows 和 Linux。在这些操作系统上,用户编写的程序在其上运行,而不是直接和硬件打交道。这也意味着即使程序崩溃了,通常也不会导致整个系统崩溃,这样整体系统的安全性就比较高了。
但在嵌入式系统中,情况就有些不同了,因为这些系统的资源相对有限,比如像 mbed 这样的单片机系统。传统的操作系统并不能直接应用于这些嵌入式系统中。因此,嵌入式实时操作系统就应运而生了。这种操作系统运行在嵌入式系统上,它的特点是能够快速处理外部事件或数据,并在规定时间内做出响应,从而控制生产过程或者处理系统。比较常见的嵌入式实时操作系统有 eCos、uCos 和 FreeRTOS 等。
与传统操作系统不同的是,在嵌入式实时操作系统中,用户代码和操作系统代码通常一起编译。这类操作系统的主要功能是线程管理,包括线程的创建、撤销、调度、同步和通信。线程是程序的执行单位,线程管理负责决定哪个线程获得处理器资源并执行,以及协调线程之间的执行顺序。此外,线程间通信也是很重要的,它允许线程之间交换数据和消息。
在这里,我们有必要了解一下线程在系统中的状态及不同状态之间的切换原则,一个线 程可能是以下状态中的一种:
运行态:线程正在运行当中,一个时刻只能有一个线程处于运行态;
就绪态:线程已经获得必要的资源 ,一旦正在运行的线程终止或这进入等待状态,队列中具有最高优先级的线程将变成运行态;
等待态:线程正在等待相关的事件发生; 停止态:此时线程并没有创建,也没有消耗任何系统资源。
下面显示了线程各个状态之间的切换关系。
总之,操作系统在计算机中扮演着非常重要的角色,而嵌入式实时操作系统则更专注于快速响应和任务协调,适用于资源有限的嵌入式系统。
mbed 自带了 mbed-rtos 实时操作系统,实现了线程控制、线程同步、线程间通讯、线 程调度等功能。mbed-rtos 的实现建立在 CMSIS-RTOS 基础之上,CMSIS-RTOS 是 ARM 公司提供的用于线程控制、资源和时间管理的实时操作系统的标准化编程接口,可以方便各类实时操作系统地开发。
## 2. mbed rtos 的线程同步
mbed RTOS(Real-Time Operating System)中的线程同步是指多个线程之间协调和同步它们的执行,以确保线程能够有序地运行而不会发生冲突或竞争条件。在mbed RTOS中,线程同步通常涉及以下几个方面:
1. **互斥(Mutex)**:互斥是一种线程同步机制,用于确保在同一时间只有一个线程可以访问共享资源。通过使用互斥锁(Mutex Lock),线程可以互斥地访问共享资源,避免数据竞争和不一致性。
2. **信号量(Semaphore)**:信号量是一种用于线程同步的计数器,用于控制对共享资源的访问。通过信号量,可以限制同时访问共享资源的线程数量,从而避免资源的竞争和冲突。
3. **条件变量(Condition Variable)**:条件变量用于在线程之间进行通信和同步,允许线程等待特定条件的发生。当条件满足时,线程可以被唤醒并继续执行。
4. **事件标志(Event Flags)**:事件标志用于线程之间的事件通知和同步。线程可以等待特定的事件标志被设置,从而执行相应的操作。
mbed-rtos 提供了 Thread 对象来完成线程控制,它提供的主要方法有:
类名
| 方法
| 用途
| Thread
| Thread(void (*task)(void const *argument),
void *argument=NULL, osPriority priority=osPriorityNormal, uint32_t stack_size=DEFAULT_STACK_SIZE, unsigned char *stack_pointer=NULL);
| 构造函数,输出参数分别是任务函数 指针,函数参数指针,线程优先级, 堆栈大小,堆栈指针,默认为普通优 先级,2K 字节内存堆栈,使用内部分 配方式,如果传入堆栈指针,则使用 传入指针指向的内存
| osStatus terminate();
| 结束本线程
| osStatus set_priority(osPriority priority);
| 设置本线程优先级
| osPriority get_priority();
| 获取本线程优先级
| int32_t signal_set(int32_t signals);
| 设置本线程信号量
| State get_state();
| 静态函数,获取当前线程状态
| static osEvent signal_wait(int32_t signals, uint32_t millisec=osWaitForever);
| 静态函数,等待信号量
| static osStatus wait(uint32_t millisec);
| 静态函数,挂起当前线程 millisec 毫秒
| static osStatus yield();
| 静态函数,把运行权交给下一个线程
| static osThreadId gettid();
| 静态函数,获取当前线程 ID
|
这些线程同步机制在mbed RTOS中起着至关重要的作用,确保多个线程能够协调工作,共享资源,并避免竞争条件和数据不一致性问题。通过合理地使用这些机制,可以实现高效的线程管理和协作,提高系统的稳定性和性能。在 mbed 中对应的类是 Mutex 和 Semaphore,它们提供的主要方法如下:
类名
| 方法
| 用途
| Mutex
| Mutex();
| 构造函数,定义一个互斥锁
| osStatus lock(uint32_t millisec=osWaitForever);
| 等待直到互斥锁有效
| bool trylock();
| 判断互斥锁是否有效,它会立刻返回
| osStatus unlock();
| 释放本线程原先锁定互斥锁
| Semaphore
| Semaphore(int32_t count);
| 构造函数,定义一个信号量,传入参数 为允许的资源数
| int32_t wait(uint32_t millisec=osWaitForever);
| 等待直到资源有效,返回为可用的资源 数,-1 表示错误
| osStatus release(void);
| 释放本线程占用的资源
|
下面我们是采用互斥锁以后的线程调度代码:
#include "mbed.h"
#include "rtos.h"
#include "BufferedSerial.h"
Mutex stdio_mutex; // Semaphore slots(1);
uint8_t theadindex[3];
long count_value[3];
void printstr(void const *args)
{
while (true)
{
stdio_mutex.lock(); // slots.wait();
printf("Hello World,My Thread Name is %d,Thread ID is %d.\n",*(uint8_t *)args, (int)(ThisThread::get_id()));
count_value[*(uint8_t *)args-1]++;
stdio_mutex.unlock();//slot.release();
ThisThread::yield();
}
}
int main()
{
theadindex[0] = 1;
theadindex[1] = 2;
theadindex[2] = 3;
Thread thread1;
Thread thread2;
Thread thread3;
// Thread thread1(printstr, (void *)theadindex, osPriorityNormal);
// Thread thread2(printstr, (void *)(theadindex + 1), osPriorityNormal);
// Thread thread3(printstr, (void *)(theadindex + 2), osPriorityNormal);
thread1.start(callback(printstr,(void *)theadindex));
thread2.start(callback(printstr,(void *)(theadindex + 1)));
thread3.start(callback(printstr,(void *)(theadindex + 2)));
while (1)
{
stdio_mutex.lock(); // slots.wait();
printf("Thread1 count is %ld,Thread2 count is %ld,,Thread3 count is %ld. \n",count_value[0],count_value[1],count_value[2]);
stdio_mutex.unlock();//slot.release();
ThisThread::sleep_for(1s);
}
}
这段代码实现了创建三个线程并打印它们的线程名和线程ID,同时统计每个线程执行的次数,并在主循环中定期打印每个线程的执行次数。
#include "mbed.h"
#include "rtos.h"
#include "BufferedSerial.h"
导入所需的头文件。
定义一个互斥锁对象`stdio_mutex`,用于保护标准输出的访问。
uint8_t theadindex[3];
long count_value[3];
定义一个存储线程索引和执行次数的数组。
void printstr(void const *args)
{
while (true)
{
stdio_mutex.lock();
printf("Hello World,My Thread Name is %d,Thread ID is %d.\n",*(uint8_t *)args, (int)(ThisThread::get_id()));
count_value[*(uint8_t *)args-1]++;
stdio_mutex.unlock();
ThisThread::yield();
}
}
定义一个函数`printstr`,它将被作为线程的执行函数。该函数会循环执行以下操作:
- 获取互斥锁以保护标准输出。
- 打印当前线程的名字和ID。
- 增加对应线程执行次数的计数器。
- 释放互斥锁。
- 通过`ThisThread::yield()`让出执行权给其他线程。
int main()
{
theadindex[0] = 1;
theadindex[1] = 2;
theadindex[2] = 3;
初始化线程索引数组。
Thread thread1;
Thread thread2;
Thread thread3;
创建三个线程对象。
thread1.start(callback(printstr,(void *)theadindex));
thread2.start(callback(printstr,(void *)(theadindex + 1)));
thread3.start(callback(printstr,(void *)(theadindex + 2)));
启动三个线程,每个线程执行`printstr`函数,并传递相应的线程索引作为参数。
while (1)
{
stdio_mutex.lock();
printf("Thread1 count is %ld,Thread2 count is %ld,,Thread3 count is %ld. \n",count_value[0],count_value[1],count_value[2]);
stdio_mutex.unlock();
ThisThread::sleep_for(1s);
}
在主循环中,获取互斥锁以保护标准输出,并打印每个线程的执行次数。然后释放互斥锁,并通过`ThisThread::sleep_for()`函数让主线程休眠1秒钟。这段代码创建了三个线程,每个线程循环执行一个函数`printstr`,该函数打印线程名称和ID,并增加相应线程的执行次数。主循环定期打印每个线程的执行次数。整体来说,这段代码展示了如何使用Mbed OS的线程功能创建和管理多个线程,并演示了线程之间的并发执行。
除此之外,我们同样也可以用信号量的方式,只要定义一个只有一个资源的信号量即可,然后再采 用 wait 和 release 的方式实现同步,但需要注意的是,信号量的资源数决定 了有多少个线程可以同时访问,所以如果你在上面的代码中定义了具有多个资源的信号量, 那么依然会有乱码的情况发生,那么信号量用在什么场合合适的?简单地说,就是那些允许有限个线程共享同一资源的场合,如读文件场合,一个文件可以被多个线程打开,但有限制; 网络连接场合,一个服务器可以被有限个客户端打开。
mbed-rtos 另外还提供了一种称为 Signals(信号)的精简同步模式,在这种模式下,一 个线程必须等到特定的信号到来才继续,而另外一个线程则负责发送该信号,如下面的测试 代码,不同的串口数据可以使不同的线程工作:
#include "rtos.h"
#include "mbed.h"
#include "UnbufferedSerial.h"
UnbufferedSerial pc(USBTX,USBRX);
DigitalOut led2(LED2);
DigitalOut led3(LED3);
void led2_thread()
{
while (true)
{
osSignalWait(0xAA, 0);
led2 = !led2;
}
}
void led3_thread()
{
while (true)
{
osSignalWait(0xAA, 0);
led3 = !led3;
}
}
int main (void)
{
Thread thread2;
Thread thread3;
thread2.start(&led2_thread);
thread3.start(&led3_thread);
while (true)
{
char key;
pc.read(&key ,1);
if (key=='2')
{
osSignalSet(thread2.get_id(), 0xAA);
}
if (key=='3')
{
osSignalSet(thread3.get_id(), 0xAA);
}
}
}
这段代码的主要功能包括: - 初始化串口对象和LED对象。
- 定义了两个LED线程函数led2_thread和led3_thread,用于控制LED2和LED3的闪烁。
- 在主函数中创建了LED2和LED3的线程对象,并启动这两个线程。
- 主循环中通过串口读取用户输入的字符,如果输入的是'2',则向LED2线程发送信号,如果输入的是'3',则向LED3线程发送信号,从而控制LED的状态切换。
这些代码可以使用mbed studio 直接烧写到APM32F407IG-Tiny 板中的,具体的现象这里就不上传了,各位可以下载到开发板上查看,这里只是我针对这些介绍编写的一些简单的例程,这篇文章主要介绍到这里,如果要进行其他的一些复杂操作,可以去 mbed 的官网查看其 API 的使用。这篇文章就介绍到这里,下篇文章会讲解mbed 线程间的通讯以及在mbed-rtos 在中断服务程序中的应用。
|
从嵌入式实时操作系统的论述开始,再介绍mbed rtos 的线程同步,最后实现一个基础的操作系统案例,原理和代码讲解都较为详细,有一定的借鉴意义