打印
[应用相关]

mbed-rtos操作系统概述

[复制链接]
2640|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
zhuotuzi|  楼主 | 2016-1-3 15:57 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

我们平常意义上的操作系统指的是是一组控制和管理计算机硬件和软件资源,合理地对各类作业进行调度,以及方便用户的程序的集合,如windows 、linux等,对于这类操作系统而言,用户编写的各类程序都在操作系统之上运行,并不会直接操作硬件,而且用户程序的崩溃一般情况下也不会导致整个系统的崩溃,所以整体安全性较好。但由于受到嵌入式系统资源的限制,尤其是像mbed这样的单片机系统,计算机操作系统的理念并不能直接应用到嵌入式操作系统中(linux虽然也可以应用到嵌入式系统中,但同样也需要较多的硬件资源,它们和mbed并不是同一级别的,所以不在本文讨论之列),我们需要新的操作系统类型,这就是嵌入式实时操作系统。


嵌入式实时操作系统定义如下:运行在嵌入式系统(用于控制、监视或者辅助操作机器和设备的装置)之上,当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系统作出快速响应,并控制所有实时任务协调一致的软件系统,常见的有eCos、uCos、FreeRTOS等。对于这类操作系统而言,用户代码和操作系统代码一般情况下是编译在一起的,当然也没有虚拟机的概念,所以在编写基于此类操作系统的应用时一定要小心,否则就有可能引起整体系统的崩溃。

对于同样操作系统而言,它需要完成进程(线程)管理、存储管理、设备管理、作业管理、安全管理等功能,但受到嵌入式系统资源及用途的闲置,嵌入式实时操作系统的主要功能就是线程管理,具体包括:

l  线程控制及调度:线程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配的单位。线程控制的主要任务就是创建线程、撤销线程和控制线程运行时候的各种状态转换,而线程调度的任务是从线程的就绪队列中按照一定的算法挑选出一个线程,把处理器资源分配给它,并准备好特定的执行环境让它执行起来。

l  线程同步:多个线程的执行是并发的,它们以异步的方式运行,它们的执行进度也是不可预知的;为了使多个线程可以有条不紊地运行,操作系统要提供线程同步机制,以协调线程的执行。一般有两种协调方式:互斥和同步。互斥指多个线程对临界资源访问时采用互斥的形式;同步则是在相互协作共同完成任务的线程之间,用同步机制协调它们之间的执行顺序。

l  线程间通信:线程间通信主要发生在相互协作的线程之间,由操作系统提供的线程间通信机制是它们之间相互交换数据和消息的手段。

l  线程调度:

在这里,我们有必要了解一下线程在系统中的状态及不同状态之间的切换原则,一个线程可能是以下状态中的一种:

l  运行态:线程正在运行当中,一个时刻只能有一个线程处于运行态;

l  就绪态:线程已经获得必要的资源,一旦正在运行的线程终止或这进入等待状态,队列中具有最高优先级的线程将变成运行态;

l  等待态:线程正在等待相关的事件发生;

l  停止态:此时线程并没有创建,也没有消耗任何系统资源。

下图显示了线程各个状态之间的切换关系:

mbed自带了mbed-rtos实时操作系统,实现了线程控制、线程同步、线程间通讯、线程调度等功能。mbed-rtos的实现建立在CMSIS-RTOS基础之上, CMSIS-RTOS是ARM公司提供的用于线程控制、资源和时间管理的实时操作系统的标准化编程接口,可以方便各类实时操作系统地开发。


沙发
zhuotuzi|  楼主 | 2016-1-3 15:58 | 只看该作者
mbed-rtos的线程控制

线程控制的主要目的是允许用户建立多个线程并发执行,从而简化各类应用的开发,这样的需求是很常见的,如我们需要程序一方面处理串口数据,另一方便处理LED计数的变化,我们先看一个不用线程的实现:

DigitalOut led1(LED1);

DigitalOut led2(LED2);

DigitalOut led3(LED3);

DigitalOut led4(LED4);

Serial pc(USBTX,USBRX);

uint8_t ledvalue=0;

Ticker tick;

void setLed(uint8_t  val)

{

    led1=val % 2;

    led2=(val /2 ) % 2;

    led3=(val /4 ) % 2;

    led4=(val /5 ) % 2;

}

void printstr()

{

    pc.printf("Hello World,Now value is %d . \n",ledvalue);

    wait(1);

}

int main()

{

    tick.attach(&printstr,2);

    while (1)

    {

                        setLed(++ledvalue);

                        wait(0.1);

    }

}

但上载此程序后发现,因为在串口输出中需要1秒的等待,所以在这1秒内LED停止了计数过程,另外,如果你把tick的间隔时间设成小于1秒的后,LED计算根本就没有机会执行,显然,这样的处理很难满足实际的需求,下面我们给一个使用线程控制的实现(注释部分为传入函数参数的方式):

#include "rtos.h"

DigitalOut led1(LED1);

DigitalOut led2(LED2);

DigitalOut led3(LED3);

DigitalOut led4(LED4);

Serial pc(USBTX,USBRX);

uint8_t ledvalue=0;

Ticker tick;

void setLed(uint8_t  val)

{

    led1=val % 2;

    led2=(val /2 ) % 2;

    led3=(val /4 ) % 2;

    led4=(val /5 ) % 2;

}

void printstr(void const *args)

{

    while (true) {

                        pc.printf("Hello World,Now value is %d.\n",ledvalue);

                        // pc.printf("Hello World,Now value is %d.\n",*(uint8_t *)args);

                        Thread::wait(1000);

    }

}

int main()

{

    Thread thread(printstr);

  Thread thread(printstr,&ledvalue);

    while (1)

    {

                        setLed(++ledvalue);

                        Thread::wait(100);

    }

}

此时你就会发现串口输出和LED计算不再相互影响,就像两个程序独立执行一样,下面是串口输出的片断:

Hello World,Now value is 174.

Hello World,Now value is 184.

Hello World,Now value is 195.

Hello World,Now value is 205.

Hello World,Now value is 215.


使用特权

评论回复
板凳
zhuotuzi|  楼主 | 2016-1-3 15:59 | 只看该作者

从此输出可以看出,两者交替执行的时间间隔总体差不多但还是有一定区别的,但不管怎样,我们已经看出操作系统的价值,它可以简化我们复杂应用的开发。mbed-rtos在使用时必须导入rtos库和相应mcu架构的底层实现,xbed LPC1768使用的是cortex-m3的价格,所以我们需要导入mbed-rtos库和TARGET_M3两个库。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

为了更好地理解以上方法的用法,我们先来看下面的例子:

#include "rtos.h"

Serial pc(USBTX,USBRX);

long count[2];

void printstr1(void const *args)

{

    while (true) {

                        pc.printf("Hello World,My Thread Name is %s,Thread ID is %d.\n",args,Thread::gettid());

                        count[0]++;

                        Thread::yield();

    }

}

void printstr2(void const *args)

{

    while (true) {

                        pc.printf("Hello World,My Thread Name is %s,Thread ID is %d.\n",args,Thread::gettid());

                        count[1]++;

                        Thread::yield();

    }

}

int main()

{

    Thread thread1(printstr1,(void *)"1",osPriorityNormal);

    Thread thread2(printstr2,(void *)"2",osPriorityNormal);

    while (1)

    {

                        pc.printf("Thread1 count is %ld,Thread2 count is %ld. \n",count[0],count[1]);

                        Thread::wait(1000);

    }

}

以上代码创建了两个优先级一样的子线程和一个在main中运行的主线程,运行后你会发现两个子线程运行占用的时间片是一样的,也即是打印的次数一样多,这也符合调度的机制,让大家获得平等的运行机会,但我们会发现串口的输出全乱了,这是因为mbed的串口输出使用了缓冲区机制,由于没有线程同步,导致不同线程的缓冲区相互覆盖从而造成错误输出,这可以使用后续章节的mutex即互斥锁机制完成。


使用特权

评论回复
地板
zhuotuzi|  楼主 | 2016-1-3 16:00 | 只看该作者

为了刚好地理解优先级,我们可以把一个子线程的优先级设成osPriorityHigh,重新上载后你就会发现只有该线程获得了运行机会,这是因为该线程在使用yield释放出运行权后,系统调度程序找到的最高优先级就绪线程还是它,当然,这是一种很极端的情况,如果我们使用Thread::wait方法就不会一样了,因为它等待的时间内并不需要运行权,所以别的低优先级的线程就有了运行的机会,但一定要注意的是,任何一个子线程必须有释放运行权的机制,否则别的线程就不再有运行的机会,下面是测试代码:

#include "rtos.h"

Serial pc(USBTX,USBRX);

uint8_t theadindex[2];

long count[2];

void printstr(void const *args)

{

    while (true) {

                        count[*(uint8_t *)args-1]++;

                        Thread::wait(500);

    }

}

int main()

{

    theadindex[0]=1;

    theadindex[1]=2;

    Thread thread1(printstr,(void *)theadindex,osPriorityNormal);

    Thread thread2(printstr,(void *)(theadindex+1),osPriorityHigh);

    while (1)

    {

                        pc.printf("Thread1 count is %ld,Thread2 count is %ld. \n",count[0],count[1]);

                        Thread::wait(1000);

    }

}


使用特权

评论回复
5
zhuotuzi|  楼主 | 2016-1-3 16:01 | 只看该作者

另外,mbed-rtos为了方便用户定时执行特定函数,它还另外提供了RtosTimer类,它提供的主要方法如下:

类名
方法
用途
RtosTimer
RtosTimer(void (*task)(void const *argument),
   os_timer_type type=osTimerPeriodic,
   void *argument=NULL);
构造函数,传入参数分别是和本Timer相关的函数,Timer类型,可以使周期性的也可以是一次性的,传入函数的参数
osStatus stop(void);
停止Timer
osStatus start(uint32_t millisec);
开始Timer,时间周期是millisec毫秒

         下面是一个简单的应用示例,从效果上来说是和Ticker及Timeout的运行结果一致的:

#include "rtos.h"

Timer mytimer;

DigitalOut led1(LED1);

DigitalOut led2(LED2);

void blinkled1(void const *v)

{

    led1=!led1;

}

void blinkled2(void const *v)

{

    led2=!led2;

}

Serial pc(USBTX,USBRX);

int main (void) {

    RtosTimer led1_timer(blinkled1,osTimerPeriodic);

    RtosTimer led2_timer(blinkled2,osTimerOnce);

    led1_timer.start(1000);

    led2_timer.start(1000);

    mytimer.start();

    while (true) {

                                            Thread::wait(1000);

                                            pc.printf("Time passed %fs.\n",mytimer.read());

    }


}


使用特权

评论回复
6
whirt_noob| | 2016-1-3 22:56 | 只看该作者
这操作系统较其他的操作系统有什么优势?

使用特权

评论回复
7
shdjdq| | 2016-1-4 07:55 | 只看该作者
对于嵌入式系统,系统中断优级不能很高,而且不能全关中断。不然就不实用,因为用的人还要改变内核才能解决问题。这样推广就没有动力。你每推出一个版本都要修改内核

使用特权

评论回复
8
zhuotuzi|  楼主 | 2016-1-5 22:53 | 只看该作者
一个线程可能是以下状态中的一种:
l  运行态:线程正在运行当中,一个时刻只能有一个线程处于运行态;
l  就绪态:线程已经获得必要的资源,一旦正在运行的线程终止或这进入等待状态,队列中具有最高优先级的线程将变成运行态;
l  等待态:线程正在等待相关的事件发生;
l  停止态:此时线程并没有创建,也没有消耗任何系统资源。

使用特权

评论回复
9
zhuotuzi|  楼主 | 2016-1-5 22:54 | 只看该作者
whirt_noob 发表于 2016-1-3 22:56
这操作系统较其他的操作系统有什么优势?

高度抽象,将硬件和软件完全分离开,只需要对公共使用的MBED的API接口使用就行了。配置的时候,只需要在几个特别文件进行资源分配一下管脚就可以。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

201

主题

3315

帖子

7

粉丝