线程suspend是什么意思

suspend是什么意思  时间:2021-01-23  阅读:()

RT-ThreadProgrammingGuideRelease0.
3.
0BernardXiongJune29,2009CONTENTS1文档简介32实时系统52.
1嵌入式系统52.
2实时系统52.
3软实时与硬实时63快速入门93.
1准备环境93.
2初识RT-Thread143.
3系统启动代码183.
4用户入口代码203.
5跑马灯的例子213.
6生产者消费者问题224RT-Thread简介274.
1实时内核284.
2虚拟文件系统294.
3轻型IP协议栈294.
4shell系统294.
5支持的平台295内核对象模型315.
1C语言的对象化模型315.
2内核对象模型346线程调度与管理436.
1实时系统的需求436.
2线程调度器436.
3线程控制块446.
4线程状态466.
5空闲线程476.
6调度器相关接口47i6.
7线程相关接口487线程间同步与通信577.
1关闭中断577.
2调度器上锁597.
3互斥量597.
4信号量647.
5消息队列687.
6邮箱747.
7事件797.
8快速事件848内存管理898.
1静态内存池管理908.
2动态内存管理949异常与中断1019.
1中断处理过程1019.
2中断的底半处理1039.
3中断相关接口10510定时器与系统时钟10710.
1定时器管理10710.
2定时器管理控制块10810.
3定时器管理接口10811I/O设备管理11511.
1I/O设备管理控制块11511.
2I/O设备管理接口11711.
3设备驱动12012FinSHShell系统13512.
1基本数据类型13512.
2RT-Thread内置命令13612.
3应用程序接口13812.
4移植13912.
5选项13913文件系统14113.
1文件系统接口14213.
2目录操作接口14413.
3下层驱动接口14714TCP/IP协议栈14914.
1协议初始化14914.
2缓冲区函数151ii14.
3网络连接函数15514.
4BSDSocket库16215内核配置17115.
1编译环境配置17115.
2内核配置17215.
3手工配置17216ARM基本知识17716.
1ARM的工作状态17716.
2ARM处理器模式17716.
3ARM的寄存器组织17816.
4ARM的异常17916.
5ARM的IRQ中断处理18116.
6AT91SAM7S64概述18117GNUGCC移植18317.
1CPU相关移植18317.
2板级相关移植19718RealViewMDK移植20718.
1建立RealViewMDK工程20718.
2添加RT-Thread的源文件21218.
3线程上下文切换21418.
4启动汇编文件21718.
5中断处理22818.
6开发板初始化22819RT-Thread/STM32说明22919.
1ARMCortexM3概况22919.
2ARMCortexM3移植要点23119.
3RT-Thread/STM32说明23519.
4RT-Thread/STM32移植默认配置参数23620Indicesandtables237iiiivRT-ThreadProgrammingGuide,Release0.
3.
0Contents:CONTENTS1RT-ThreadProgrammingGuide,Release0.
3.
02CONTENTSCHAPTERONE文档简介RT-Thread做为国内有较大影响力的开源实时操作系统,本文是RT-Thread实时操作系统的编程指南文档,它旨在说明如何在RT-Thread实时操作系统上进行编程、把它使用到具体的应用中去.
它分成几个部分分别讲述了:实时系统概念:实时系统是一个什么样的系统,它的特点是什么;RT-Thread快速入门,在无硬件平台的情况下,如何迅速地了解RT-Thread实时操作系统,如何使用RT-Thread实时操作系统最基本的一些元素;RT-Thread作为一个完整的实时操作系统,它能够满足各种实时系统的需求,所以接下来详细地介绍了各个模块的结构,以及编程时的注意事项.
RT-Thread外围组件的编程说明,RT-Thread不仅包括了一个强实时的内核,也包括外围的一些组件,例如shell,文件系统,协议栈等.
这部分对外围组件编程进行了描述.
RT-Thread中一些其他部分说明,包含了如何使用GNUGCC工具搭建RT-Thread的开发环境及RT-Thread在Cortex-M3系统上的说明.
本书面向使用RT-Thread系统的开发人员,并假定开发人员具备基本的C语言基础知识,如具备基本的实时操作系统知识将能更好的理解书中的一些概念.
本书是一本使用RT-Thread进行编程的书籍,对于RT-Thread的内部实现并不做过多、过细节性的分析.

本书中异常与中断由王刚编写,定时器与系统时钟,I/O设备管理,文件系统由邱祎编写,其他部分由熊谱翔编写.
3RT-ThreadProgrammingGuide,Release0.
3.
04Chapter1.
文档简介CHAPTERTWO实时系统2.
1嵌入式系统嵌入式系统是具备有特殊目的的计算系统,它具有特殊的需求,并运行预先定义好的任务.
如常见的嵌入式系统:电视用的机顶盒,网络中的路由器等,它们都是为了一专用目的而设计的.
从硬件资源上来讲,为了完成这一专有功能,嵌入式系统提供有限的资源,一般是恰到好处,在成本上满足一定的要求.
从电子产品的角度来说,嵌入式系统最终会由一些芯片及电路组成,有时会包含一定的机械控制等,在控制芯片当中会包含一定的计算单元.
总的来说,嵌入式系统提倡的是为了一个专用目的,其功能够用就好.

嵌入式系统嵌入式系统中会包含微控制器,用于存放代码的Flash,BootRom,运行时代码用到的内存,调试时需要的JTAG接口等.
2.
2实时系统实时计算可以定义成这样一类计算,即系统的正确性不仅取决于计算的逻辑结果,而且还依赖于产生结果的时间,关键有两点:正确地完成和在给定的时间内完成,而且两者重要5RT-ThreadProgrammingGuide,Release0.
3.
0性是等同的.
而针对于在给定的时间内功能性的要求可以划分出常说的两类实时系统,软实时和硬实时系统.
可以先看一个示例图:对于输入的信号、事件,实时系统应该能够在规定的时间内得到正确的响应,而不管这些事件是单一事件、多重事件还是同步信号或异步信号.
对于一个具体的例子,可以考虑子弹射向玻璃杯的问题:一颗子弹从20米处射出,射向一个玻璃杯.
假设子弹的速度是v米/秒,那么经过t1=20/v秒后,子弹将击碎玻璃杯.
而有一系统在看见子弹射出后,将把玻璃杯拿走,假设这整个过程将持续t2秒的事件.
如果t2>"进入下一画面10Chapter3.
快速入门RT-ThreadProgrammingGuide,Release0.
3.
0为了能够正常安装,需要同意它的条款(这是一款评估版),选择"Next>>"3.
1.
准备环境11RT-ThreadProgrammingGuide,Release0.
3.
0选择RealViewMDK对于的安装目录,默认C:Keil即可,选择"Next>>"12Chapter3.
快速入门RT-ThreadProgrammingGuide,Release0.
3.
0输入您的名字及邮件地址,选择"Next>>"3.
1.
准备环境13RT-ThreadProgrammingGuide,Release0.
3.
0安装完成,选择"Finish"有了RealViewMDK的利器,就可以轻松开始RT-Thread之旅,一起探索实时操作系统的奥秘.
请注意RealViewMDK正式版是收费的,如果您希望能够编译出更大体积的二进制文件,请购买RealViewMDK正式版.
RT-Thread也支持自由基金会的GNUGCC编译器,这是一款开源的编译器,想要了解如何使用GNU的相关工具搭建RT-Thread的开发环境请参考本书后面的附录部分,其中有在Windows/Linux环境下搭建采用GNUGCC做为RT-Thread开发环境的详细说明.
3.
2初识RT-ThreadRT-Thread做为一个操作系统,它会不会和Windows或Linux一样很庞大代码会不会达到惊人的上百万行代码级别弄清楚这些之前,我们先要做的就是获得本书配套的RT-Thread代码,它可以看成是目前RT-Thread正在开发中的0.
3.
0主线版本中的一个分支,内核核心的代码与0.
3.
0正式版RT-Thread是完全一致的,只是把不必要的外围组件先行移除掉.
这个是一个压缩包文件,请解压到一个目录,这里把它解压到D:目录中.
解压完成后的目录结构是这样的:D:\rtthread-0.
3.
0-kernel14Chapter3.
快速入门RT-ThreadProgrammingGuide,Release0.
3.
0-src-include-finsh-bsp|-sam7s-libcpu-arm-AT91SAM7Skernel目录就是RT-Thread的源代码目录,有chapter前缀的则是和本书每个章节相配套的代码.
在目录\bsp\sam7s下,有一个project.
Uv2文件它是一个RealViewMDK的工程文件,如果按照上节的步骤正确的安装了RealViewMDK,那么在这里直接双击鼠标可以打开这个文件.
打开后会出现如下的画面:3.
2.
初识RT-Thread15RT-ThreadProgrammingGuide,Release0.
3.
0这个就是RT-Thread工程文件夹画面,在工程文件列表中总共存在如下几个组Startup用户开发板相关文件及启动文件(对应kernelbsp目录)KernelRT-Thread内核核心实现(对应kernelsrc目录)AT91SAM7S针对ATMELAT91SAM7S64移植的代码(对应kernellibcpuAT91SAM7S目录)我们先让RealViewMDK编译运行试试:16Chapter3.
快速入门RT-ThreadProgrammingGuide,Release0.
3.
0没什么意外,最后会出现类似画面上的结果,虽然有一些警告但关系不大.

在编译完RT-Thread/AT91SAM7S后,我们可以通过RealViewMDK的模拟器来仿真运行RT-Thread.
模拟运行的结果如下图所示.
3.
2.
初识RT-Thread17RT-ThreadProgrammingGuide,Release0.
3.
0因为用户代码是空的,所以只是显示了RT-Thread的LOGO.
3.
3系统启动代码一般了解一份代码大多从启动部分开始,同样这里也采用这种方式,先寻找启动的源头:因为RealViewMDK的用户程序入口采用了main()函数,所以先看看main()函数在哪个文件中:startup.
c,它位于Startup组中,是在AT91SAM7S64的启动汇编代码(启动汇编在AT91SAM7SGroup的startrvds.
S中,在后面章节的移植一节中会详细讨论)后跳转到C代码的入口位置.
intmain(void){/*调用RT-Thread的启动函数,rtthreadstartup*/rtthreadstartup();return0;}很简单,main()函数仅仅调用了rtthreadstartup()函数.
RT-Thread因为支持多种平台,多种编译器,rtthreadstartup()函数是RT-Thread的统一入口点.
从rtthreadstartup()函数中我们将可以看到RT-Thread的启动流程:/*这个函数将启动RT-ThreadRTOS*/voidrtthreadstartup(void)18Chapter3.
快速入门RT-ThreadProgrammingGuide,Release0.
3.
0{/*初始化硬件中断控制器*/rthwinterruptinit();/*初始化硬件开发板*/rthwboardinit();/*显示RT-Thread的版本号*/rtshowversion();/*初始化系统节拍,用于操作系统的时间技术*/rtsystemtickinit();/*初始化系统对象*/rtsystemobjectinit();/*初始系统定时器*/rtsystemtimerinit();/**如果定义了宏RTUSINGHEAP,即RT-Thread使用动态堆*AT91SAM7S64的SRAM总共是16kB,地址范围是*0x200000-0x204000*所以在调用rtsystemheapinit函数时的最后一个参数是尾地址0x204000*前面的初始地址则根据编译器环境的不同而略微不同*/#ifdefRTUSINGHEAP#ifdefCCARMrtsystemheapinit((void*)&Image$$RWIRAM1$$ZI$$Limit,(void*)0x204000);#elifICCARMrtsystemheapinit(segmentend("HEAP"),(void*)0x204000);#elsertsystemheapinit((void*)&bssend,(void*)0x204000);#endif#endif/*初始化系统调度器*/rtsystemschedulerinit();/*如果使用了钩子函数,把rthwledflash函数挂到idle线程的执行中去*/#ifdefRTUSINGHOOK/*setidlethreadhook*/rtthreadidlesethook(rthwledflash);#endif/*如果使用了设备框架*/#ifdefRTUSINGDEVICE3.
3.
系统启动代码19RT-ThreadProgrammingGuide,Release0.
3.
0/*注册/初始化硬件串口*/rthwserialinit();/*初始化所有注册了的设备*/rtdeviceinitall();#endif/*初始化上层应用*/rtapplicationinit();/*如果系统中使用了shell系统*/#ifdefRTUSINGFINSH/*初始化finsh*/finshsysteminit();/*finsh的输入设备是uart1设备*/finshsetdevice("uart1");#endif/*初始化idle线程*/rtthreadidleinit();/*启动调度器,将进行系统的第一次调度*/rtsystemschedulerstart();/*这个地方应该是永远都不应该达到的*/return;}这部分启动代码,可以分为几个部分:初始化系统相关的硬件初始化系统组件,例如定时器,调度器初始化系统设备,这个主要是为RT-Thread的设备框架做的初始化初始化各个应用线程,并启动调度器3.
4用户入口代码上面的启动代码基本上可以说都是和RT-Thread系统相关的,那么用户代码在什么地方初始化/*初始化上层应用*/rtapplicationinit();20Chapter3.
快速入门RT-ThreadProgrammingGuide,Release0.
3.
0这里,用户代码入口位置是rtapplicationinit(),在这个函数中可以初始化用户应用程序的线程,当后面打开调度器后,用户线程也将得到执行.
在工程中,rtapplicationinit()的实现在application.
c文件中,目前跑的RT-Thread是最简单的,仅包含一个空的rtapplicationinit()实现:/*包含RT-Thread的头文件,每一个需要用到RT-Thread服务的文件都需要包含这个文件*/#include/*用户应用程序入口点*/intrtapplicationinit(){return0;}空的实现就意味着,系统中不存在用户的代码,系统只会运行和系统相关的一些代码.
在以后的例子中,如果没有特殊的说明,我们都将在这个文件中实现代码,并在rtapplicationinit()函数中进行初始化工作.
3.
5跑马灯的例子对于从事电子方面开发的技术工程师来说,跑马灯大概是最简单的例子,就类似于每种编程语言中的HelloWorld.
所以第一个例子就从跑马灯例子开始:创建一个线程,让它不定时的对LED进行更新(关或灭)/*因为要使用RT-Thread的线程服务,需要包含RT-Thread的头文件*/#include/*线程用到的栈,由于ARM是4字节对齐的,所以栈的空间必须是4字节对齐*/staticcharledthreadstack[512];/*线程的TCB控制块*/staticstructrtthreadledthread;/*线程的入口点,当线程运行起来后,它将从这里开始执行*/staticvoidledthreadentry(void*parameter){inti;/*这个线程是一个永远循环执行的线程*/while(1){/*开LED,然后延时10个tick*/ledon();rtthreaddelay(10);3.
5.
跑马灯的例子21RT-ThreadProgrammingGuide,Release0.
3.
0/*关LED,然后延时10个tick*/ledoff();rtthreaddelay(10);}}/*用户应用程序入口点*/intrtapplicationinit(){/**初始化一个线程*名称是`led`*入口位置是ledthreadentry*入口参数是空,这个参数会传递给入口函数的,可以是一个指针或一个数*优先级是25(AT91SAM7S64配置的最大优先级数是32,这里使用25)*时间片是8(如果有相同优先级的线程存在,时间片才会真正起作用)*/rtthreadinit(&ledthread,"led",ledthreadentry,RTNULL,&ledthreadstack[0],sizeof(ledthreadstack),25,8);/**上一步仅仅是初始化一个线程,也就是为一个线程的运行做准备,*这里则是启动这个线程**注:这个函数并不代表线程立刻就运行起来,当调度器启动起来后,*线程才得到真正的调度.
如果此时,调度器已经运行了,那么则取决于新启*动的线程优先级是否高于当前任务优先级,如果高于,则立刻执行新线程.

*/rtthreadstartup(&ledthread);return0;}在代码中rtthreaddelay(10)函数的作用是延时一段时间,即让led线程休眠10个tick(按照rtcong.
h中的配置,1秒=RTTICKPERSECOND个tick=100tick,即这份代码中是延时100ms).
在休眠的这段时间内,如果没有其他线程运行,操作系统会切换到idle线程运行.
3.
6生产者消费者问题生产者消费者问题是操作系统中的一个经典问题,在嵌入式操作系统中也经常能够遇到,例如串口中接收到数据,然后由一个任务统一的进行数据的处理:串口产生数据,任务作22Chapter3.
快速入门RT-ThreadProgrammingGuide,Release0.
3.
0为一个消费者消费数据.
在下面的例子中,将用RT-Thread的编程模式来实现一个生产者、消费者问题的解决代码.
#include/*定义最大5个元素能够被产生*/#defineMAXSEM5/*用于放置生产的整数数组*/rtuint32tarray[MAXSEM];structrtsemaphoresemlock;structrtsemaphoresemempty,semfull;/*指向生产者、消费者在array数组中的读写位置*/staticrtuint32tset,get;/*生成者线程入口*/voidproducerthreadentry(void*parameter){intcnt=0;/*运行100次*/while(cnta=1;parentptr->b=5;objptr->a=10;objptr->b=100;}在上面代码中,注意subobject结构中第一个成员p,这种声明方式代表subobject类型的数据中开始的位置包含一个parent类型的变量.
在函数func中obj是一个subobject对象,正向这个结构类型指示的,它前面的数据应该包含一个parent类型的数据.
在第行的强制类型赋值中parentptr指向了obj变量的首地址,实际上也就是obj变量中的p对象.
好了,现在parentptr指向的是一个真真实实的parent类型的结构,那么可以按照parent的方式访问其中的成员,当然也包括可以使用和parent结构相关的函数来处理内部数据,因为一个正常的,正确的代码,它是不会越界访问parent结构体以外的数据的.
经过这基本的结构体层层相套包含,对象简单的继存关系就体现出来了:父对象放于数据块的最前方,代码中可以通过强制类型转换获的父对象指针.
5.
1.
3多态多态是面向对象设计中极为重用的一环.
对象系统是一个归类的系统,类与类之间是有联系的,例如RT-Thread中的设备和串口设备,串口是设备的一种.
设备一般支持读写接口,但是统一的,串口是设备的一种,也应该支持设备的读写.
但串口的读写操作是串口所特有的,不应和其他设备操作完全相同,例如操作串口的操作不应应用于SD卡设备中.

RT-Thread对象模型采用结构封装中使用指针的形式达到面向对象中多态的效果,例如:structbaseclass{inta;void(*vfunc)(inta);}voidbaseclassvfunc(structbaseclass*self,inta){assert(self!
=NULL);assert(slef->vfunc!
=NULL);self->vfunc(a);}5.
1.
C语言的对象化模型33RT-ThreadProgrammingGuide,Release0.
3.
0structchildclass{structbaseclassparent;intb;};voidchildclassinit(structchildclass*self){structbaseclass*parent;parent=(structbaseclass*)self;assert(parent!
=NULL);parent->vfunc=childclassvfunc;}staticvoidchildclassvfunc(structchildclass*self,inta){self->b=a+10;}5.
2内核对象模型5.
2.
1静态对象和动态对象RT-Thread的内核映像文件在编译时会形成如下图所示的结构(以AT91SAM7S64为例):其中主要包括了这么几段:SegmentDescription.
text代码正文段.
data数据段,用于放置带初始值的全局变量.
rodata只读数据段,用于放置只读的全局变量(常量).
bssbss段,用于放置未初始化的全局变量34Chapter5.
内核对象模型RT-ThreadProgrammingGuide,Release0.
3.
0上图为AT91SAM7S64运行时内存映像图.
当系统运行时,这些段也会相应的映射到内存中.
在RT-Thread系统初始化时,通常bss段会清零,而堆(Heap)则是除了以上这些段以外可用的内存空间(具体的地址空间在系统启动时由参数指定),系统运行时动态分配的内存块就在堆的空间中分配出来的,如下代码:rtuint8t*msgptr;msgptr=(rtuint8t*)rtmalloc(128);rtmemset(msgptr,0,128);msgptr指向的128字节内存空间就是位于堆空间中的.
而一些全局变量则是存放于.
data和.
bss段中,.
data存放的是具有初始值的全局变量(.
rodata可以看成一个特殊的data段,是只读的),如下代码:#includeconststaticrtuint32tsensorenable=0x000000FE;rtuint32tsensorvalue;rtbooltsensorinited=RTFALSE;voidsensorinit(){[.
.
.
]}[.
.
.
]sensorvalue存放在.
bss段中,系统启动后会自动初始化成零.
sensorinited变量则存放在.
data段中,而sensorenable存放在.
rodata端中.
在RT-Thread内核对象中分为两类:静态内核对象和动态内核对象.
静态内核对象通常放在.
data或.
bss段中,在系统启动后在程序中初始化;动态内核对象则是从堆中创建的,而后手工做初始化.
5.
2.
内核对象模型35RT-ThreadProgrammingGuide,Release0.
3.
0RT-Thread中操作系统级的设施都是一种内核对象,例如线程,信号量,互斥量,定时器等.
以下的代码所示的即为静态线程和动态线程的例子:/*线程1的对象和运行时用到的栈*/staticrtuint8tthread1stack[512];staticstructrtthreadthread1;/*线程1入口*/voidthread1entry(void*parameter){inti;while(1){for(i=0;i32rtuint8tnumber;rtuint8thighmask;#endifrtuint32tnumbermask;#ifdefined(RTUSINGEVENT)||defined(RTUSINGFASTEVENT)/*线程事件*/rtuint32teventset;rtuint8teventinfo;#endifrtuint8tstat;/*线程状态*/rtubasetinittick;/*线程初始分配的时间片*/rtubasetremainingtick;/*线程当前运行时剩余的时间片*/structrttimerthreadtimer;/*线程定时器*/rtuint32tuserdata;/*用户数据*/6.
3.
线程控制块45RT-ThreadProgrammingGuide,Release0.
3.
0};最后的一个成员userdata可由用户挂接一些数据信息到线程控制块中,以实现类似线程私有数据.
6.
4线程状态在线程运行的过程中,在一个时间内只允许一个线程中处理器中运行,即线程会有多种不同的线程状态,如运行态,非运行态等.
在RT-Thread中,线程包含四种状态,操作系统会自动根据它运行的情况而动态调整它的状态.
RT-Thread中的四种线程状态:StateDescriptionRTTHREADINIT/CLOSE线程初始状态.
当线程刚开始创建还没开始运行时就处于这个状态;当线程运行结束时也处于这个状态.
在这个状态下,线程会参与调度RTTHREADSUSPEND挂起态.
线程此时被挂起:它可能因为资源不可用而等待挂起;或主动延时一段时间而被挂起.
在这个状态下,线程不参与调度RTTHREADREADY就绪态.
线程正在运行;或当前线程运行完让出处理机后,操作系统寻找最高优先级的就绪态线程运行RTTHREADRUNNING运行态.
线程当前正在运行,在单核系统中,只有rtthreadself()函数返回的线程处于这个状态;在多核系统中则不受这个限制.
RT-ThreadRTOS提供一系列的操作系统调用接口,使得线程的状态在这四个状态之间来回的变换.
例如一个就绪态的任务由于申请一个资源(例如使用rtsemtake),而有可能进入阻塞态.
又如,一个外部中断发生,转入中断处理函数,中断处理函数释放了某个资源,导致了当前运行任务的切换,唤醒了另一阻塞态的任务,改变其状态为就绪态等等.

三种状态间的转换关系如下图所示:46Chapter6.
线程调度与管理RT-ThreadProgrammingGuide,Release0.
3.
0线程通过调用函数rtthreadcreate/init调用进入到初始状态(RTTHREADINIT),通过函数rtthreadstartup调用后进入到就绪状态(RTTHREADREADY).
当这个线程调用rtthreaddelay,rtsemtake,rtmbrecv等函数时,将主动挂起或由于获取不到资源进入到挂起状态(RTTHREADSUSPEND).
在挂起状态的线程,如果它等待超时依然未获得资源或由于其他线程释放了资源,它将返回到就绪状态.
6.
5空闲线程空闲线程是系统线程中一个比较特殊的线程,它具备最低的优先级,当系统中无其他线程可运行时,调度器将自动调度到空闲线程.
空闲线程通常是一个死循环,永远不被挂起.

在RT-Thread中空闲线程提供了钩子函数,可以让系统在空闲的时候执行一定任务,例如系统运行指示灯闪烁,电源管理等.
6.
6调度器相关接口6.
6.
1调度器初始化在系统启动时需要执行调度器的初始化,以初始化调度器用到的一些全局变量.
调度器初始化可以调用以下接口.
voidrtsystemschedulerinit(void)6.
5.
空闲线程47RT-ThreadProgrammingGuide,Release0.
3.
06.
6.
2启动调度器在系统完成初始化后切换到第一个线程,可以调用如下接口.
voidrtsystemschedulerstart(void)在调用这个函数时,它会查找系统中最高的就绪态的线程,然后切换过去运行.
注:在调用这个函数前,必须先做idle线程的初始化.
此函数是永远不会返回的.
6.
6.
3执行调度让调度器执行一次线程的调度可通过如下接口.
voidrtschedule(void)调用这个函数后,系统会计算一次系统中就绪态的线程,如果存在比当前线程更高优先级的线程时,系统将会切换到高优先级的线程去.
通常情况下,用户不需要直接调用这个函数.
注:在中断服务例程中也可以调用这个函数,如果满足任务切换的条件,它会记录下中断前的线程及切换到更高优先级的线程,在中断服务例程处理完毕后执行正真的线程上下文切换.
6.
7线程相关接口6.
7.
1线程创建一个线程要成为可执行的对象就必须由操作系统内核来为它创建/初始化一个线程句柄.

可以通过如下的接口来创建一个线程.
rtthreadtrtthreadcreate(constchar*name,void(*entry)(void*parameter),void*parameter,rtuint32tstacksize,rtuint8tpriority,rtuint32ttick)在调用这个函数时,将为线程指定名称,线程入口位置,入口参数,线程栈大小,优先级及时间片大小.
线程名称的最大长度由宏RTNAMEMAX指定,多余部分会被自动截掉.
栈大小的单位是字节,在大多数系统中需要做对齐(例如ARM体系结构中需要向4字节对齐).
线程的优先级范围从0~255,数值越小优先级越高.
时间片的单位是操作系统的时钟节拍.
调用这个函数后,系统会从动态堆内存中分配一个线程句柄(或者叫做TCB)以及按照参数中指定的栈大小从动态堆内存中分配相应的空间.
创建一个线程的例子如下代码所示.
48Chapter6.
线程调度与管理RT-ThreadProgrammingGuide,Release0.
3.
0#include/*线程入口*/staticvoidentry(void*parameter){rtuint32tcount=0;while(1){rtkprintf("count:%d\n",++count);rtthreaddelay(50);}}/*用户应用程序入口*/intrtapplicationinit(){/*创建一个线程,入口是entry函数*/rtthreadtthread=rtthreadcreate("t1",entry,RTNULL,1024,200,10);if(thread!
=RTNULL)rtthreadstartup(thread);return0;}6.
7.
2线程删除当需要删除用rtthreadcreate创建出的线程时(例如线程出错无法恢复时),可以使用以下接口:rterrtrtthreaddelete(rtthreadtthread)调用该接口后,线程对象将会被移出线程队列并且从内核对象管理器中删除,线程占用的堆栈空间也会被释放.
Note:线程运行完成,自动结束时,系统会自动删除线程.
用rtthreadinit初始化的静态线程请不要使用此接口删除.
线程删除例子如下:#includevoidthread1entry(void*parameter){rtuint32tcount=0;6.
7.
线程相关接口49RT-ThreadProgrammingGuide,Release0.
3.
0while(1){rtkprintf("count:%d\n",++count);rtthreaddelay(50);}}rtuint32ttodeletethread1=0;rtthreadtthread1,thread2;voidthread2entry(void*parameter){while(1){if(todeletethread1==1){/*todeletethread1置位,删除thread1*/rtthreaddelete(thread1);/*删除完成后退出*/return;}/*todeletethread1未置位,等待100个时钟节拍后再查询*/rtthreaddelay(100);}}intrtapplicationinit(){/*创建thread1并运行*/thread1=rtthreadcreate("t1",thread1entry,RTNULL,1024,200,10);if(thread1!
=RTNULL)rtthreadstartup(thread1);/*创建thread2并运行*/thread2=rtthreadcreate("t2",thread2entry,RTNULL,1024,120,10);if(thread2!
=RTNULL)rtthreadstartup(thread2);return0;}50Chapter6.
线程调度与管理RT-ThreadProgrammingGuide,Release0.
3.
06.
7.
3线程初始化线程的初始化可以使用以下接口完成:rterrtrtthreadinit(structrtthread*thread,constchar*name,void(*entry)(void*parameter),void*parameter,void*stackstart,rtuint32tstacksize,rtuint8tpriority,rtuint32ttick);通常线程初始化函数用来初始化静态线程对象,线程句柄,线程栈由用户提供,一般都设置为全局变量在编译时被分配,内核不负责动态分配空间.
线程初始化例子如下所示:#includestaticrtuint8tthreadstack[512];staticstructrtthreadthread;/*线程入口*/staticvoidentry(void*parameter){inti;rtthreadtself;/*获得当前线程句柄*/self=rtthreadself();while(1){rtkprintf("thread[%s]count%d\n",self->name,++i);rtthreaddelay(100);}}intrtapplicationinit(){/*初始化线程*/rtthreadinit(&thread,"thread1",entry,RTNULL,&threadstack[0],sizeof(threadstack),200,10);/*启动线程*/rtthreadstartup(&thread);6.
7.
线程相关接口51RT-ThreadProgrammingGuide,Release0.
3.
0return0;}6.
7.
4线程脱离脱离线程将使线程对象被从线程队列和内核对象管理器中删除.
脱离线程使用以下接口.

rterrtrtthreaddetach(rtthreadtthread)注:这个函数接口是和rtthreaddelete相对应的,rtthreaddelete操作的对象是rtthreadcreate创建的句柄,而rtthreaddetach操作的对象是使用rtthreadinit初始化的句柄.
6.
7.
5线程启动创建/初始化的线程对象的状态是RTTHREADINIT状态,并未进入调度队列,可以调用如下接口启动一个创建/初始化的线程对象:rterrtrtthreadstartup(rtthreadtthread)6.
7.
6当前线程在程序的运行过程中,相同的一段代码可能会被多个线程执行,在执行的时候可以通过下面的接口获得当前执行的线程句柄.
rtthreadtrtthreadself(void)Note:请不要在中断服务程序中调用此函数,因为它并不能准确获得当前的执行线程.

6.
7.
7线程让出处理机当前线程的时间片用完或者该线程自动要求让出处理器资源时,它不再占有处理机,调度器会选择下一个最高优先级的线程执行.
这时,放弃处理器资源的线程仍然在就绪队列中.
线程让出处理器使用以下接口:rterrtrtthreadyield()调用该接口后,线程首先把自己从它所在的队列中删除,然后把自己挂到与该线程优先级对应的就绪线程链表的尾部,然后激活调度器切换到优先级最高的线程.

线程让出处理机代码例子如下所示:52Chapter6.
线程调度与管理RT-ThreadProgrammingGuide,Release0.
3.
0voidfuncion(){.
.
.
rtthreadyield();.
.
.
}Note:rtthreadyield函数和rtschedule函数比较相像,但在相同优先级线程存在时,系统的行为是完全不一样的.
当有相同优先级就绪线程存在,并且系统中不存在更高优先级的就绪线程时,执行rtthreadyield函数后,当前线程被换出.
而执行rtschedule函数后,当前线程并不被换成.
6.
7.
8线程睡眠在实际应用中,经常需要线程延迟一段时间,指定的时间到达后,线程重新运行,线程睡眠使用以下接口:rterrtrtthreadsleep(rttickttick)rterrtrtthreaddelay(rttickttick)以上两个接口作用是相同的,调用该线程可以使线程暂时挂起指定时间,它接受一个参数,该参数指定了线程的休眠时间(时钟节拍数).
6.
7.
9线程挂起当线程调用rtthreaddelay,rtsemtake,rtmbrecv等函数时,将主动挂起.
或者由于线程获取不到资源,它也会进入到挂起状态.
在挂起状态的线程,如果等待的资源超时或由于其他线程释放资源,它将返回到就绪状态.
挂起线程使用以下接口:rterrtrtthreadsuspend(rtthreadtthread)注:如果挂起当前任务,需要在调用这个函数后,紧接着调用rtschedule函数进行手动的线程上下文切换.
挂起线程的代码例子如下所示.
代码2-6挂起线程代码#include/*线程句柄*//*线程入口*/staticvoidentry(void*parameter){rtthreadtthread;6.
7.
线程相关接口53RT-ThreadProgrammingGuide,Release0.
3.
0while(1){/*获得当前线程*/thread=rtthreadself();/*挂起自己*/rtthreadsuspend(thread);/*调用rtthreadsuspend虽然把thread从就绪队列中删除,*但代码依然在运行,需要手动让调度器调度一次*/rtschedule();/*运行到此处,相当于thread已经被唤醒*/rtkprintf("threadisresumed\n");}}intrtapplicationinit(){rtthreadtthread;/*创建线程*/thread=rtthreadcreate("tid",entry,RTNULL,1024,250,20);/*启动线程*/rtthreadstartup(thread);return0;}6.
7.
10线程恢复线程恢复使得挂起的线程重新进入就绪状态.
线程恢复使用以下接口:rterrtrtthreadresume(rtthreadtthread)恢复挂起线程的代码例子如下所示.
#includertthreadtthread=RTNULL;voidfunction(){.
.
.
54Chapter6.
线程调度与管理RT-ThreadProgrammingGuide,Release0.
3.
0rtthreadresume(thread);.
.
.
}6.
7.
11线程控制rterrtrtthreadcontrol(rtthreadtthread,rtuint8tcmd,void*arg)6.
7.
12初始化空闲线程在系统调度器运行前,必须通过调用如下的函数初始化空闲线程.
voidrtthreadidleinit(void)6.
7.
13设置空闲线程钩子可以调用如下的函数,设置空闲线程运行时执行的钩子函数.
voidrtthreadidlesethook(void(*hook)())当空闲线程运行时会自动执行设置的钩子函数,由于空闲线程具有系统的最低优先级,所以只有在空闲时刻才会执行此钩子函数.
空闲线程是一个线程状态永远为就绪状态的线程,因此挂入的钩子函数必须保证空闲线程永远不会处于挂起状态,例如rtthreaddelay,rtsemtake等可能会导致线程挂起的函数不能使用.
6.
7.
线程相关接口55RT-ThreadProgrammingGuide,Release0.
3.
056Chapter6.
线程调度与管理CHAPTERSEVEN线程间同步与通信在多任务实时系统中,一项工作的完成往往可以通过多个任务来共同合作完成.
例如,一个任务从数据采集器中读取数据,然后放到一个链表中进行保存.
而另一个任务则从这个链表队列中把数据取出来进行分析处理,并把数据从链表中删除(一个典型的消费者与生产者的例子).
当消费者任务取到链表的最末端的时候,此时生产者任务可能正在往末端添加数据,那么就很有可能生产者拿到的末节点被消费者任务给删除了.
正常的操作顺序应该是在一个任务删除或添加动作完成时再进行下一个动作,生产任务与消费任务之间需要协调动作,而对于操作/访问同一块区域,称之为临界区.
任务的同步方式有很多中,其核心思想是,在访问临界区的时候只允许一个任务运行.

7.
1关闭中断关闭中断是禁止多任务访问临界区最简单的一种方式,即使是在分时操作系统中也是如此.
当关闭中断的时候,就意味着当前任务不会被其他事件所打断(因为整个系统已经不再响应外部事件,如果自身不主动放弃处理机,它也不会产生内部事件),也就是当前任务不会被抢占,除非这个任务主动放弃了处理机.
关闭中断/恢复中断API是由BSP实现的,根据不同的平台实现方式也不大相同.
关闭、打开中断由两个函数完成:关闭中断rtbasetrthwinterruptdisable()关闭中断并返回关闭中断前的中断状态恢复中断voidrthwinterruptenable(rtbasetlevel)57RT-ThreadProgrammingGuide,Release0.
3.
0使能中断,它采用恢复调用rthwinterruptdisable前的中断状态进行恢复中断状态,如果调用rthwinterruptdisable()前是关中断状态,那么调用此函数后依然是关中断状态.
使用的例子代码如下:#include/*同时访问的全局变量*/staticrtuint32tcnt;voidthreadentry(void*parameter){rtuint32tno;rtuint32tlevel;no=(rtuint32)parameter;while(1){/*关闭中断*/level=rthwinterruptdisable();cnt+=no;/*恢复中断*/rthwinterruptenable(level);rtkprintf("thread[%d]'scounteris%d\n",no,cnt);rtthreaddelay(no);}}/*用户应用程序入口*/voidrtapplicationinit(){rtthreadtthread;/*创建t10线程*/thread=rtthreadcreate("t10",threadentry,(void*)10,512,10,5);if(thread!
=RTNULL)rtthreadstartup(thread);/*创建t20线程*/thread=rtthreadcreate("t20",threadentry,(void*)20,512,20,5);if(thread!
=RTNULL)rtthreadstartup(thread);}58Chapter7.
线程间同步与通信RT-ThreadProgrammingGuide,Release0.
3.
07.
2调度器上锁同样把调度器锁住也能让当前运行的任务不被换出,直到调度器解锁.
但和关闭中断有一点不相同的是,对调度器上锁,系统依然能响应外部中断,中断服务例程依然有可能被运行.
所以在使用调度器上锁的方式来做任务同步时,需要考虑好,任务访问的临界资源是否会被中断服务例程所修改,如果可能会被修改,那么将不适合采用此种方式作为同步的方法.
RT-Thread提供的调度器操作API为::rtentercritical–进入临界区调用这个函数后,调度器将被上锁.
在系统锁住调度器的期间,系统依然响应中断,但因为中断而可能唤rtexitcritical–退出临界区在系统退出临界区的时候,系统会计算当前是否有更高优先级的任务就绪,如果有比当前线程更高优先级的线程就绪,那么将切换到这个高优先级线程中执行;如果无更高优先级线程就绪,那么将继续执行当前任务.
Note:rtentercritical/rtexitcritical可以多次嵌套调用,但有多少次rtentercritical就必须有成对的rtexitcritical调用.
嵌套的最大深度是256.
7.
3互斥量互斥量是管理临界资源的一种有效手段.
因为互斥量是独占的,所以在一个时刻只允许一个线程占有互斥量,利用这个性质来实现共享资源的互斥量保护.
互斥量工作示意图如下图所示,任何时刻只允许一个线程获得互斥量对象,未能够获得互斥量对象的线程被挂起在该互斥量的等待线程队列上.
使用互斥量会导致的一个潜在问题就是线程优先级翻转.
所谓优先级翻转问题即当一个高优先级线程通过互斥量机制访问共享资源时,该互斥量已被一低优先级线程占有,而这7.
2.
调度器上锁59RT-ThreadProgrammingGuide,Release0.
3.
0个低优先级线程在访问共享资源时可能又被其它一些中等优先级的线程抢先,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证.
例如:有优先级为A、B和C的三个线程,优先级A>B>C,线程A,B处于挂起状态,等待某一事件的发生,线程C正在运行,此时线程C开始使用某一共享资源S.
在使用过程中,线程A等待的事件到来,线程A转为就绪态,因为它比线程C优先级高,所以立即执行.
但是当线程A要使用共享资源S时,由于其正在被线程C使用,因此线程A被挂起切换到线程C运行.
如果此时线程B等待的事件到来,则线程B转为就绪态.
由于线程B的优先级比线程C高,因此线程B开始运行,直到其运行完毕,线程C才开始运行.
只有当线程C释放共享资源S后,线程A才得以执行.
在这种情况下,优先级发生了翻转,线程B先于线程A运行.
这样便不能保证高优先级线程的响应时间.
在RT-Thread中实现的是优先级继承算法.
优先级继承通过在线程C被阻塞期间提升线程A的优先级到线程C的优先级别从而解决优先级翻转引起的问题.
这防止了A(间接地防止C)被B抢占.
通俗地说,优先级继承协议使一个拥有资源的线程以等待该资源的线程中优先级最高的线程的优先级执行.
当线程释放该资源时,它将返回到它正常的或标准的优先级.
因此,继承优先级的线程避免了被任何中间优先级的线程抢占.
7.
3.
1互斥量控制块互斥量控制块的数据结构structrtmutex{structrtipcobjectparent;rtbasetvalue;/*互斥量的数值*/structrtthread*owner;/*当前拥有互斥量的线程*/rtuint8toriginalpriority;/*线程原始优先级*/rtbasethold;/*等待线程数量*/};rtmutex对象从rtipcobject中派生,由IPC容器所管理.
7.
3.
2互斥量相关接口创建互斥量创建一个互斥量时,内核首先创建一个互斥量控制块,然后完成对该控制块的初始化工作.
创建互斥量使用以下接口:rtmutextrtmutexcreate(constchar*name,rtuint8flag)使用该接口时,需要为互斥量指定一个名字,并指定互斥量标志,可以是基于优先级的或基于FIFO的.
60Chapter7.
线程间同步与通信RT-ThreadProgrammingGuide,Release0.
3.
0#defineRTIPCFLAGFIFO0x00/*IPC参数采用FIFO方式*/#defineRTIPCFLAGPRIO0x01/*IPC参数采用优先级方式*/采用基于优先级ag创建的IPC对象,将在多个线程等待资源时,由优先级高的线程优先获得资源.
而采用基于FIFOag创建的IPC对象,在多个线程等待资源时,按照先来先得的顺序获得资源.
删除互斥量系统不再使用互斥量时,通过删除互斥量以释放系统资源.
删除互斥量使用以下接口:rterrtrtmutexdelete(rtmutextmutex)删除一个互斥量,所有等待此互斥量的线程都将被唤醒,等待线程获得返回值是-RTERROR.
初始化互斥量系统选择静态内存管理方式时,系统会在编译时创建将会使用的各种内核对象,互斥量也会在此时被创建,此时使用互斥量就不再需要使用rtmutexcreate接口来创建它,而只需直接对内核在编译时创建的互斥量控制块进行初始化.
初始化互斥量使用以下接口:rterrtrtmutexinit(rtmutextmutex,constchar*name,rtuint8flag)使用该接口时,需指定内核已在编译时分配的静态互斥量控制块,指定该互斥量名称以及互斥量标志.
脱离互斥量脱离互斥量将使互斥量对象被从内核对象管理器中删除.
脱离互斥量使用以下接口.

rterrtrtmutexdetach(rtmutextmutex)使用该接口后,内核先唤醒所有挂在该互斥量上的线程(线程的返回值是-RTERROR),然后将该互斥量从内核对象管理器链表中删除.
获取互斥量线程通过互斥量申请服务获取对互斥量的控制权.
线程对互斥量的控制权是独占的,某一个时刻一个互斥量只能被一个线程控制.
在RT-Thread中使用优先级继承算法来解决优先级翻转问题.
成功获得该互斥量的线程的优先级将被提升到等待该互斥量资源的线程中优先级最高的线程的优先级,获取互斥量使用以下接口:7.
3.
互斥量61RT-ThreadProgrammingGuide,Release0.
3.
0rterrtrtmutextake(rtmutextmutex,rtint32time)如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得.
如果互斥量已经被当前线程线程控制,则该互斥量的引用计数加一.
如果互斥量已经被其他线程控制,则当前线程该互斥量上挂起等待,直到其他线程释放它或者等待时间超过指定的超时时间.

释放互斥量当线程完成互斥资源的访问后,应尽快释放它占据的互斥量,使得其他线程能及时获取该互斥量.
释放互斥量使用以下接口:rterrtrtmutexrelease(rtmutextmutex)使用该接口时,只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的访问计数就减一.
当该互斥量的访问计数为零时,它变为可用,等待在该信号量上的线程将被唤醒.
如果线程的运行优先级被互斥量提升,那么当互斥量被释放后,线程恢复原先的优先级.
使用互斥量的例子如下#include/*静态互斥锁对象*/structrtmutexmutex;/*线程1入口*/voidthread1entry(void*param){while(1){/*试图获得mutex互斥锁*/rtmutextake(&mutex,RTWAITINGFOREVER);/*再获得mutex互斥锁,因为mutex允许相同线程多次获得互斥锁,所以这步将立刻返回*/rtmutextake(&mutex,RTWAITINGFOREVER);/*打印获得mutex的信息,并休眠5秒*/rtkprintf("thread1:takenmutex\n");rtthreaddelay(500);/**释放mutex互斥锁,因为在前面对于mutex对象调用了两次rtmutextake,*所以这里也相应的释放两次.
*/rtmutexrelease(&mutex);rtmutexrelease(&mutex);62Chapter7.
线程间同步与通信RT-ThreadProgrammingGuide,Release0.
3.
0/*打印释放了mutex*/rtkprintf("thread1:releasemutex\n");}}/*线程2入口*/voidthread2entry(void*param){while(1){/*先休眠1秒钟*/rtkprintf("thread2:delay1second\n");rtthreaddelay(100);/*试图获得mutex互斥锁*/rtkprintf("thread2:takenmutex\n");rtmutextake(&mutex,RTWAITINGFOREVER);/*休眠10秒钟*/rtthreaddelay(1000);/*释放mutex*/rtmutexrelease(&mutex);}}intrtapplicationinit(){rtthreadtthread;rtmutexinit(&mutex,"mutext",RTIPCFLAGFIFO);thread=rtthreadcreate("t1",thread1entry,RTNULL,512,250,10);if(thread!
=RTNULL)rtthreadstartup();thread=rtthreadcreate("t2",thread2entry,RTNULL,512,200,10);if(thread!
=RTNULL)rtthreadstartup();return0;}7.
3.
互斥量63RT-ThreadProgrammingGuide,Release0.
3.
07.
4信号量信号量是用来解决线程同步和互斥的通用工具,和互斥量类似,信号量也可用作资源互斥访问,但信号量没有所有者的概念,在应用上比互斥量更广泛.
信号量比较简单,不能解决优先级翻转问题,但信号量是一种轻量级的对象,比互斥量小巧、灵活.
因此在很多对互斥要求不严格的系统中,经常使用信号量来管理互斥资源.
信号量工作示意图如图4-6所示,每个信号量对象有一个信号量值和一个线程等待队列,信号量的值对应了信号量对象的实例数目,假如信号量值为5,则表示共有5个信号量实例可以被使用,当信号量实例数目为零时,再申请该信号量的对象就会被挂起在该信号量的等待队列上,等待可用的信号量实例.
7.
4.
1信号量控制块structrtsemaphore{structrtipcobjectparent;/*信号量对象继承自ipc对象*/rtbasetvalue;/*信号量的值*/};rtsemaphore对象从rtipcobject中派生,由IPC容器所管理.
7.
4.
2信号量相关接口创建信号量当创建一个信号量时,内核首先创建一个信号量控制块,然后对该控制块进行基本的初始化工作,创建信号量使用以下接口:64Chapter7.
线程间同步与通信RT-ThreadProgrammingGuide,Release0.
3.
0rtsemtrtsemcreate(constchar*name,rtuint32value,rtuint8flag);使用该接口时,需为信号量指定一个名称,并指定信号量初始值和信号量标志.

删除信号量系统不再使用信号量时,通过删除信号量以释放系统资源.
删除信号量使用以下接口:rterrtrtsemdelete(rtsemtsem);删除一个信号量,必须确保该信号量不再被使用.
如果删除该信号量时,有线程正在等待该信号量,则先唤醒等待在该信号量上的线程(返回值为-RTERROR).
初始化信号量系统选择静态内存管理方式时,系统会在编译时创建将会使用的各种内核对象,信号量也会在此时被创建,此时使用信号量就不再需要使用rtmutexcreate接口来创建它,而只需直接对内核在编译时创建的互斥量控制块进行初始化.
初始化互斥量使用以下接口:rterrtrtseminit(rtsemtsem,constchar*name,rtuint32value,rtuint8flag)使用该接口时,需指定内核分配的静态信号量控制块,指定信号量名称以及信号量标志.

脱离信号量脱离信号量将使信号量对象被从内核对象管理器中删除.
脱离信号量使用以下接口.

rterrtrtmutexdetach(rtmutextmutex)使用该接口后,内核先唤醒所有挂在该信号量上的线程,然后将该信号量从内核对象管理器中删除.
获取信号量线程通过获取信号量来获得信号量资源实例,当信号量值大于零时,它每次被申请获得,值都会减一,获取信号量使用以下接口:rterrtrtsemtake(rtsemtsem,rtint32time)如果信号量的值等于零,那么说明当前资源不可用,申请该信号量的线程就必须在此信号量上等待,直到其他线程释放该信号量或者等待时间超过指定超时时间.

7.
4.
信号量65RT-ThreadProgrammingGuide,Release0.
3.
0获取无等待信号量当用户不想在申请的信号量上等待时,可以使用无等待信号量,获取无等待信号量使用以下接口:rterrtrtsemtrytake(rtsemtsem)跟获取信号量接口不同的是,当线程申请的信号量资源不可用的时候,它不是等待在该信号量上,而是直接返回-RTETIMEOUT.
释放信号量当线程完成信号量资源的访问后,应尽快释放它占据的信号量,使得其他线程能获得该信号量.
释放信号量使用以下接口:rterrtrtsemrelease(rtsemtsem)当信号量的值等于零时,信号量值加一,并且唤醒等待在该信号量上的线程队列中的首线程,由它获取信号量.
使用信号量的例子#include/*信号量对象*/structrtsemaphoresem;/*thread1线程入口*/voidthread1entry(void*parameter){while(1){/*采用永久等待方式获取信号量*/if(rtsemtake(&sem,RTWAITINGFOREVER)==RTEOK){/*获取到信号量*/rtkprintf("thread1:takensemaphore\n");rtthreaddelay(50);rtsemrelease(&sem);}}}/*thread2线程入口*/voidthread2entry(void*parameter){66Chapter7.
线程间同步与通信RT-ThreadProgrammingGuide,Release0.
3.
0while(1){/*采用永久等待方式获取信号量*/if(rtsemtake(&sem,RTWAITINGFOREVER)==RTEOK){rtkprintf("thread2:takensemaphore\n");rtthreaddelay(10);rtsemrelease(&sem);}/*以非等待方式试图获取信号量*/if(rtsemtake(&sem,RTWAITINGNO)==RTEOK){rtkprintf("thread2:takensemaphoreokwithnowaiting\n");rtthreaddelay(10);rtsemrelease(&sem);}else{rtkprintf("thread2:takesemaphorefailedwithnowaiting\n");}}}/*用户应用程序入口*/intrtapplicationinit(){rtthreadtthread;/*初始化sem对象*/rtseminit(sem,"semt",RTIPCFLAGFIFO);/*创建线程,入口是thread1entry*/thread=rtthreadcreate("t1",thread1entry,RTNULL,512,250,10);if(thread!
=RTNULL)rtthreadstartup();/*创建线程,入口是thread2entry*/thread=rtthreadcreate("t2",thread2entry,RTNULL,512,200,10);if(thread!
=RTNULL)rtthreadstartup();return0;7.
4.
信号量67RT-ThreadProgrammingGuide,Release0.
3.
0}7.
5消息队列消息队列是另一种常用的线程间通讯方式,它使得线程之间接收和发送消息不必同时进行,它临时保存线程从队列的一端发送来的消息,直到有接收者读取它们.

消息队列用于给线程发消息.
如上图所示,通过内核提供的服务,线程或中断服务子程序可以将一条消息放入消息队列.
同样,一个或多个线程可以通过内核服务从消息队列中得到消息.
通常,先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则(FIFO).
消息队列由多个元素组成,内核用它们来管理队列.
当消息队列被创建时,它就被分配了消息队列控制块,队列名,内存缓冲区,消息大小以及队列长度.
内核负责给消息队列分配消息队列控制块,它同时也接收用户线程传入的参数,像消息队列名以及消息大小,队列长度,由这些来确定消息队列所需内存大小,当获得了这些信息后,内核就可以从内存堆或者线程私有内存空间中为消息队列分配内存.
一个消息队列中本身包含着多个消息框,每个消息框可以存放一条消息,消息队列中的第一个和最后一个消息框被分别称为队首和队尾,对应了消息队列控制块中的msgqueuehead和msgqueuetail,有些消息框中可能是空的,所有消息队列中的消息框总数就是消息队列的长度.
用户线程可以在创建消息队列时指定这个长度.

7.
5.
1消息队列控制块structrtmessagequeue{68Chapter7.
线程间同步与通信RT-ThreadProgrammingGuide,Release0.
3.
0structrtipcobjectparent;void*msgpool;/*存放消息的消息池开始地址*/rtsizetmsgsize;/*每个消息的长度*/rtsizetmaxmsgs;/*最大运行的消息数*/void*msgqueuehead;/*消息链表头*/void*msgqueuetail;/*消息链表尾*/void*msgqueuefree;/*空闲消息链表*/rtubasetentry;/*队列中已经存放的消息数*/};rtmessagequeue对象从rtipcobject中派生,由IPC容器所管理.
7.
5.
2消息队列相关接口创建消息队列创建消息队列时先创建一个消息队列对象控制块,然后给消息队列分配一块内存空间组织成空闲消息链表,这块内存大小等于消息大小与消息队列容量的乘积.
然后再初始化消息队列,此时消息队列为空,如图所示.
创建消息队列接口如下:rtmqtrtmqcreate(constchar*name,rtsizetmsgsize,rtsizetmaxmsgs,rtuint8flag)创建消息队列时给消息队列指定一个名字,作为消息队列的标识,然后根据用户需求指定消息的大小以及消息队列的容量.
如上图所示,消息队列被创建时所有消息都挂在空闲消息列表上,消息队列为空.
删除消息队列当消息队列不再被使用时,应该删除它以释放系统资源,一旦操作完成,消息队列将被永久性的删除.
删除消息队列接口如下:rterrtrtmqdelete(rtmqtmq)删除消息队列时,如果有线程被挂起在该消息队列等待队列上,则先唤醒挂起在该消息等待队列上的所有线程(返回值是-RTERROR),然后再释放消息队列使用的内存,最后删7.
5.
消息队列69RT-ThreadProgrammingGuide,Release0.
3.
0除消息队列对象.
脱离消息队列脱离消息队列将使消息队列对象被从内核对象管理器中删除.
脱离消息队列使用以下接口.
rterrtrtmqdetach(rtmqtmq)使用该接口后,内核先唤醒所有挂在该消息等待队列对象上的线程(返回值是-RTERROR),然后将该消息队列对象从内核对象管理器中删除.
初始化消息队列初始化消息队列跟创建消息队列类似,只是初始化消息队列用于静态内存管理模式,消息队列控制块来源于用户线程在系统中申请的静态对象.
还与创建消息队列不同的是,此处消息队列对象所使用的内存空间是由用户线程提供的一个缓冲区空间,其余的初始化工作与创建消息队列时相同.
初始化消息队列接口如下:rterrtrtmqinit(rtmqtmq,constchar*name,void*msgpool,rtsizetmsgsize,rtsizetpoo初始化消息队列时,该接口需要获得用户已经申请获得的消息队列控制块以及缓冲区指针参数,以及线程指定的消息队列名,消息大小以及消息队列容量.
如上图所示,消息队列初始化后所有消息都挂在空闲消息列表上,消息队列为空.
发送消息70Chapter7.
线程间同步与通信RT-ThreadProgrammingGuide,Release0.
3.
0线程或者中断服务程序都可以给消息队列发送消息,当发送消息时,内核先从空闲消息链表上取下一个空闲消息块,把线程或者中断服务程序发送的消息内容复制到消息块上,然后把该消息块挂到消息队列的尾部.
发送者成功发送消息当且仅当空闲消息链表上有可用的空闲消息块;当自由消息链表上无可用消息块,说明消息队列中的消息已满,此时,发送消息的的线程或者中断程序会收到一个错误码.
发送消息接口如下:rterrtrtmqsend(rtmqtmq,void*buffer,rtsizetsize)发送消息时,发送者需指定发送到哪个消息队列,并且指定发送的消息内容以及消息大小.
如图3-16所示,在发送一个普通消息之后,空闲消息链表上的队首消息被转移到了消息队列尾.
发送紧急消息发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,从空闲消息链表上取下来的消息块不是挂到消息队列的队尾,而是挂到队首,这样,接收者能够优先接收到紧急消息,从而及时进行消息处理.
发送紧急消息的接口如下:rterrtrtmqurgent(rtmqtmq,void*buffer,rtsizetsize)如上图所示,在发送一个紧急消息之后,空闲消息链表上的队首消息被转移到了消息队列首.
接收消息注:只有线程能够接收消息队列中的消息.
只有当消息队列中有消息时,接收者才能接收消息,否则接收者会根据超时时间设置或挂起在消息队列的等待线程队列上,或直接返回.
接收消息接口如下:rterrtrtmqrecv(rtmqtmq,void*buffer,rtsizetsize,rtint32timeout)接收消息时,接收者需指定存储消息的消息队列名,并且指定一个内存缓冲区,接收到的消息内容将被复制到该缓冲区,此外,还需指定未能及时取到邮件时的超时时间.
如上图所示,接收一个消息后消息队列上的队首消息被转移到了空闲消息链表的尾部.

7.
5.
消息队列71RT-ThreadProgrammingGuide,Release0.
3.
0使用消息队列的例子#include/*消息队列对象*/structrtmessagequeuemq;/*用于放置消息的内存池*/charmsgpool[2048];/*t1线程入口*/voidthread1entry(void*parameter){charbuf[128];while(1){rtmemset(&buf[0],0,sizeof(buf));/*采用永久等待的方式从消息队列中读取消息*/if(rtmqrecv(&mq,&buf[0],sizeof(buf),RTWAITINGFOREVER)==RTEOK){rtkprintf("thread1:recvmsgfrommessagequeue,thecontent:%s\n",buf);}rtthreaddelay(100);}}/*t2线程入口*/voidthread2entry(void*parameter){inti,result;charbuf[]="thisismessageNo.
x";while(1){for(i=0;i/*邮箱对象*/structrtmailboxmb;/*用于存放邮件的内存池*/charmbpool[128];charmbstr1[]="I'mamail!
";charmbstr2[]="thisisanothermail!
";/*t1线程入口*/voidthread1entry(void*parameter){unsignedchar*str;while(1){rtkprintf("thread1:trytorecvamail\n");/*从邮箱中收取信件,如果邮箱中为空,则直到等到收到邮件为止*/7.
6.
邮箱77RT-ThreadProgrammingGuide,Release0.
3.
0if(rtmbrecv(&mb,(rtuint32t*)&str,RTWAITINGFOREVER)==RTEOK){rtkprintf("thread1:getamailfrommailbox,thecontent:%s\n",str);rtthreaddelay(100);}}}/*t2线程入口*/voidthread2entry(void*parameter){rtuint8tcount;count=0;while(1){count++;if(count&0x1){/*为奇数,发送mbstr1的首地址到邮箱中*/rtmbsend(&mb,(rtuint32t)&mbstr1[0]);}else{/*为偶数,发送mbstr2的首地址到邮箱中*/rtmbsend(&mb,(rtuint32t)&mbstr2[0]);}rtthreaddelay(200);}}/*用户应用程序入口*/intrtapplicationinit(){rtthreadtthread;/*初始化邮箱对象,大小是128/4封邮件(每封邮件大小为4字节)*/rtmbinit(&mb,"mbt",&mbpool[0],128/4,RTIPCFLAGFIFO);/*创建t1线程*/thread=rtthreadcreate("t1",thread1entry,RTNULL,512,250,10);if(thread!
=RTNULL)rtthreadstartup();78Chapter7.
线程间同步与通信RT-ThreadProgrammingGuide,Release0.
3.
0/*创建t2线程*/thread=rtthreadcreate("t2",thread2entry,RTNULL,512,200,10);if(thread!
=RTNULL)rtthreadstartup();return0;}7.
7事件事件主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步.
可以是一个线程同步多个事件,也可以是多个线程同步多个事件.
多个事件的集合用一个无符号整型变量来表示,变量中的一位代表一个事件,线程通过"逻辑与"或"逻辑或"与一个或多个事件建立关联.
往往内核会定义若干个事件,称为事件集.
事件在用于同步时有两种类型,一种是独立型同步,是指线程与任何事件之一发生同步(逻辑或关系),另一种是关联型同步,是指线程与若干事件都发生同步(逻辑与关系).
RT-Thread定义的事件有以下特点:1.
事件只与线程相关,事件间相互独立,RT-Thread定义的每个线程拥有32个事件标志,用一个32-bit无符号整形数记录,每一个bit代表一个事件.
若干个事件构成一个事件集.
2.
事件仅用于同步,不提供数据传输功能3.
事件无队列,即多次向线程发送同一事件,其效果等同于只发送一次.
在RT-Thread中,每个线程还拥有一个事件信息标记,它有三个属性,分别是RTEVENTFLAGAND(逻辑与),RTEVENTFLAGOR(逻辑或)以及RTEVENTFLAGCLEAR(清除标记).
当线程等待事件同步时,就可以通过32个事件标志和一个事件信息标记来判断当前接收的事件是否满足同步条件.

如上图所示,线程1的事件标志中第三位和第十位被置位,如果事件信息标记位设为逻辑与,则表示线程1只有在事件3和事件10都发生以后才会被触发唤醒,如果事件信息标记位7.
7.
事件79RT-ThreadProgrammingGuide,Release0.
3.
0设为逻辑或,则事件3或事件10中的任意一个发生都会触发唤醒线程1.
如果信息标记同时设置了清除标记位,则发生的事件会导致线程1的相应事件标志位被重新置位为零.

7.
7.
1事件控制块structrtevent{structrtipcobjectparent;rtuint32tset;/*事件集合*/};rtevent对象从rtipcobject中派生,由IPC容器所管理.
7.
7.
2事件相关接口创建事件当创建一个事件时,内核首先创建一个事件控制块,然后对该事件控制块进行基本的初始化,创建事件使用以下接口:rteventtrteventcreate(constchar*name,rtuint8flag)使用该接口时,需为事件指定名称以及事件标志.
删除事件系统不再使用事件对象时,通过删除事件对象控制块以释放系统资源.
删除事件使用以下接口:rterrtrteventdelete(rteventtevent)删除一个事件,必须确保该事件不再被使用,同时唤醒所有挂起在该事件上的线程.

脱离事件脱离信号量将使事件对象从内核对象管理器中删除.
脱离事件使用以下接口.

rterrtrteventdetach(rteventtevent)使用该接口后,内核首先唤醒所有挂在该事件上的线程,然后将该事件从内核对象管理器中删除.
80Chapter7.
线程间同步与通信RT-ThreadProgrammingGuide,Release0.
3.
0初始化事件选择静态内存管理方式时,在编译时创建将会使用的各种内核对象,事件对象也会在此时被创建,此时使用事件就不再需要使用rteventcreate接口来创建它,而只需直接对内核在编译时创建的事件控制块进行初始化.
初始化事件使用以下接口:rterrtrteventinit(rteventtevent,constchar*name,rtuint8flag)使用该接口时,需指定内核分配的静态事件对象,并指定事件名称和事件标志.

接收事件内核使用32位的无符号整型数来标识事件,它的每一位代表一个事件,因此一个事件对象可同时等待接收32个事件,内核可以通过指定选择参数"逻辑与"或"逻辑或"来选择如何激活线程,使用"逻辑与"参数表示只有当所有等待的事件都发生时激活线程,而使用"逻辑或"参数则表示只要有一个等待的事件发生就激活线程.
接收事件使用以下接口:rterrtrteventrecv(rteventtevent,rtuint32set,rtuint8option,rtint32timeout,rtuint用户线程首先根据选择参数和事件对象标志来判断它要接收的事件是否发生,如果已经发生,则根据线程要求选择是否重置事件对象的相应标志位,然后返回,如果没有发生,则把等待的事件标志位和选择参数填入线程本身的结构中,然后把线程挂起在此事件对象上,直到其等待的事件满足条件或等待时间超过指定超时时间.
如果超时时间设置为零,则表示当线程要接受的事件没有满足其要求时不等待而直接返回.
发送事件通过发送事件服务,可以发送一个或多个事件.
发送事件使用以下接口:rterrtrteventsend(rteventtevent,rtuint32set)使用该接口时,通过set参数指定的事件标志重新设定event对象的事件标志值,然后遍历等待在event事件上的线程链表,判断是否有线程的事件激活要求与当前event对象事件标志值匹配,如果有,则激活该线程.
使用事件的例子#include/*事件对象*/structrteventevent;/*t1线程入口*/voidthread1entry(void*param){7.
7.
事件81RT-ThreadProgrammingGuide,Release0.
3.
0rtuint32te;while(1){/*收取第一个事件,采用"与"及收到并清除的方式*/if(rteventrecv(&event,((1/*快速事件对象*/structrtfasteventfevent;/*t1线程入口*/voidthread1entry(void*param){while(1){rtkprintf("thread1:trytorecvfastevent\n");/*接收3bit位事件,当收到时,时间集合自动清除*/if(rtfasteventrecv(&fevent,3,RTEVENTFLAGCLEAR,RTWAITINGFOREVER)==RTEOK){rtkprintf("thread1:recvfaseevent\n");}rtkprintf("thread1:delay1s\n");rtthreaddelay(100);}}/*t2线程入口*/voidthread2entry(void*param){while(1){rtkprintf("thread2:sendfastevent\n");86Chapter7.
线程间同步与通信RT-ThreadProgrammingGuide,Release0.
3.
0/*发送3bit位事件*/rtfasteventsend(&fevent,3);rtthreaddelay(100);}}#endif/*用户应用程序*/intrtapplicationinit(){rtthreadtthread;/*初始化快速事件对象*/rtfasteventinit(&fevent,"fevent",RTIPCFLAGFIFO);/*创建t1线程*/thread=rtthreadcreate("t1",thread1entry,RTNULL,512,250,10);if(thread!
=RTNULL)rtthreadstartup();/*创建t2线程*/thread=rtthreadcreate("t2",thread2entry,RTNULL,512,200,10);if(thread!
=RTNULL)rtthreadstartup();return0;}7.
8.
快速事件87RT-ThreadProgrammingGuide,Release0.
3.
088Chapter7.
线程间同步与通信CHAPTEREIGHT内存管理在计算系统中,变量、中间数据一般存放在系统存储空间中,当实际使用时才从存储空间调入到中央处理器内部进行运算.
存储空间,按照存储方式来分类可以分为两种,内部存储空间和外部存储空间.
内部存储空间通常访问速度比较快,能够随机访问(按照变量地址).
这一章主要讨论内部存储空间的管理.
实时系统中由于它对时间要求的严格性,其中的内存分配往往要比通用操作系统苛刻得多:首先,分配内存的时间必须是确定性的.
一般内存管理算法是搜索一个适当范围去寻找适合长度的空闲内存块.
这个适当,造成了搜索时间的不确定性,这对于实时系统是不可接受的,因为实时系统必须要保证内存块的分配过程在可预测的确定时间内完成,否则实时任务在对外部事响应也将变得时间不可确定性,例如一个处理数据的例子.
当一个外部数据达到时(通过传感器或网络数据包),为了把它提交给上层的任务进行处理,它可能会先申请一块内存,把数据块的地址附加上,还可能有,数据长度以及一些其他信息附加在一起(放在一个结构体中),然后提交给上层任务.
内存申请是当中的一个组成环节,如果因为使用的内存占用比较零乱,从而操作系统需要搜索一个不确定性长度的队列寻找适合的内存,那么申请时间将变得不可确定(可能搜索了1次,也可能搜索了若干次才能找到匹配的空间),进而对整个响应时间产生不可确定性.
如果此时是一次导弹袭击,估计很可能会灰飞烟灭了!
其次,随着使用的内存分块被释放,整个内存区域会产生越来越多的碎片,从总体上来说,系统中还有足够的空闲内存,但因为它们非连续性,不能组成一块连续的完整内存块,从而造成程序不能申请到内存.
对于通用系统而言,这种不恰当的内存分配算法可以通过重新启动系统来解决(每个月或者数个月进行一次),但是这个对于可能需要数年工作于野外的嵌入式系统来说是不可接受的,他们通常需要连续不断地运行下去.
最后,嵌入式系统的资源环境也不是都相同,有些系统中资源比较紧张,只有数十KB的内存可供分配,而有些系统则存在数MB的内存.
RT-Thread操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性的提供了数种内存分配管理算法:静态分区内存管理及动态内存管理.
动态内存管理又更加可用内89RT-ThreadProgrammingGuide,Release0.
3.
0存多少划分为两种情况,一种是针对小内存块的分配管理,一种是针对大内存块的分配管理.
8.
1静态内存池管理8.
1.
1静态内存池工作模式上图是内存池管理结构示意图.
内存池(MemoryPool)是一种用于分配大量大小相同的小对象的技术.
它可以极大加快内存分配/释放过程.
内存池在创建时向系统申请一大块内存,然后分成同样大小的多个小内存块,形成链表连接起来(此链表也称为空闲链表).
每次分配的时候,从空闲链表中取出头上一块,提供给申请者.
如上图所示,物理内存中可以有多个大小不同的内存池,一个内存池由多个空闲内存块组成,内核用它们来进行内存管理.
当一个内存池对象被创建时,内存池对象就被分配了内存池控制块:内存池名,内存缓冲区,内存块大小,块数以及一个等待线程队列.
内核负责给内存池分配内存池对象控制块,它同时也接收用户线程传入的参数,像内存块大小以及块数,由这些来确定内存池对象所需内存大小,当获得了这些信息后,内核就可以从内存堆或者线程私有内存空间中为内存池分配内存.
内存池一旦初始化完成,内部的内存块大小将不能再做调整.
8.
1.
2静态内存池控制块structrtmempool{structrtobjectparent;90Chapter8.
内存管理RT-ThreadProgrammingGuide,Release0.
3.
0void*startaddress;/*内存池数据区域开始地址*/rtsizetsize;/*内存池数据区域大小*/rtsizetblocksize;/*内存块大小*/rtuint8t*blocklist;/*内存块列表*/rtsizetblocktotalcount;/*内存池数据区域中能够容纳的最大内存块数*/rtsizetblockfreecount;/*内存池中空闲的内存块数*/rtlisttsuspendthread;/*因为内存块不可用而挂起的线程列表*/rtsizetsuspendthreadcount;/*因为内存块不可用而挂起的线程数*/};8.
1.
3静态内存池接口创建内存池创建内存池操作将会创建一个内存池对象并且从堆上分配一个内存池.
创建内存池是分配,释放内存块的基础,创建该内存池后,线程便可以从内存池中完成申请,释放操作,创建内存池使用如下接口,接口返回一个已创建的内存池对象.
rtmptrtmpcreate(constchar*name,rtsizetblockcount,rtsizetblocksize);使用该接口可以创建与需求相匹配的内存块大小和数目的内存池,前提是在系统资源允许的情况下.
创建内存池时,需要给内存池指定一个名称.
根据需要,内核从系统中申请一个内存池对象,然后从内存堆中分配一块由块数目和块大小计算得来的内存大小,接着初始化内存池对象结构,并将申请成功的内存缓冲区组织成可用于分配的空闲块链表.

删除内存池删除内存池将删除内存池对象并释放申请的内存.
使用如下接口:rterrtrtmpdelete(rtmptmp)删除内存池时,必须首先唤醒等待在该内存池对象上的所有线程,然后再释放已从内存堆上分配的内存,然后删除内存池对象.
初始化内存池初始化内存池跟创建内存池类似,只是初始化邮箱用于静态内存管理模式,内存池控制块来源于用户线程在系统中申请的静态对象.
还与创建内存池不同的是,此处内存池对象所使用的内存空间是由用户线程指定的一个缓冲区空间,用户把缓冲区的指针传递给内存池对象控制块,其余的初始化工作与创建内存池相同.
接口如下:8.
1.
静态内存池管理91RT-ThreadProgrammingGuide,Release0.
3.
0rterrtrtmpinit(structrtmempool*mp,constchar*name,void*start,rtsizetsize,rtsize初始化内存池时,把需要进行初始化的内存池对象传递给内核,同时需要传递的还有内存池用到的内存空间,以及内存池管理的内存块数目和块大小,并且给内存池指定一个名称.
这样,内核就可以对该内存池进行初始化,将内存池用到的内存空间组织成可用于分配的空闲块链表.
脱离内存池脱离内存池将使内存池对象被从内核对象管理器中删除.
脱离内存池使用以下接口.

rterrtrtmpdetach(structrtmempool*mp)使用该接口后,内核先唤醒所有挂在该内存池对象上的线程,然后将内存池对象从内核对象管理器中删除.
分配内存块从指定的内存池中分配一个内存块,使用如下接口:void*rtmpalloc(rtmptmp,rtint32time)如果内存池中有可用的内存块,则从内存池的空闲块链表上取下一个内存块,减少空闲块数目并返回这个内存块,如果内存池中已经没有空闲内存块,则判断超时时间设置,若超时时间设置为零,则立刻返回空内存块,若等待大于零,则把当前线程挂起在该内存池对象上,直到内存池中有可用的自由内存块,或等待时间到达.
释放内存块任何内存块使用完后都必须被释放,否则会造成内存泄露,释放内存块使用如下接口:voidrtmpfree(void*block)使用以上接口时,首先通过需要被释放的内存块指针计算出该内存块所在的内存池对象,然后增加内存池对象的可用内存块数目,并把该被释放的内存块加入空闲内存块链表上.

接着判断该内存池对象上是否有挂起的线程,如果有,则唤醒挂起线程链表上的首线程.

内存池的使用例子如下#include/*两个线程用到的TCB和栈*/structrtthreadthread1;structrtthreadthread2;92Chapter8.
内存管理RT-ThreadProgrammingGuide,Release0.
3.
0charthread1stack[512];charthread2stack[512];/*内存池数据存放区域*/charmempool[4096];/*内存池TCB*/structrtmempoolmp;/*测试用指针分配头*/char*ptr[48];/*测试线程1入口*/voidthread1entry(void*parameter){inti;char*block;while(1){/*分配48个内存块*/for(i=0;i/*定时器timer1的触发函数*/voidtimeout1(void*parameter){rtkprintf("periodictimeristimeout\n");}/*定时器timer2的触发函数*/voidtimeout2(void*parameter){rtkprintf("oneshottimeristimeout\n");}/*用户应用程序入口*/intrtapplicationinit(){rttimerttimer1;rttimerttimer2;/*创建周期性定时器timer1*/timer1=rttimercreate("timer1",timeout1,RTNULL,200,RTTIMERFLAGPERIODIC);/*创建单次定时器timer2*/timer2=rttimercreate("timer2",timeout2,RTNULL,200,RTTIMERFLAGONESHOT);return0;}10.
3.
3删除定时器系统不再使用特定定时器时,通过删除该定时器以释放系统资源.
删除定时器使用以下接口:rterrtrttimerdelete(rttimerttimer)删除定时器的例子如下代码所示.
/*用户应用程序入口*/intrtapplicationinit(){10.
3.
定时器管理接口109RT-ThreadProgrammingGuide,Release0.
3.
0rttimerttimer1;rttimerttimer2;/*创建周期性定时器timer1*/timer1=rttimercreate("timer1",timeout1,RTNULL,200,RTTIMERFLAGPERIODIC);/*创建单次定时器timer2*/timer2=rttimercreate("timer2",timeout2,RTNULL,200,RTTIMERFLAGONESHOT);/*删除定时器timer1*/rttimerdelete(&timer1);/*删除定时器timer2*/rttimerdelete(&timer2);return0;}10.
3.
4初始化定时器当选择静态创建定时器时,可利用rttimerinit接口来初始化该定时器,接口如下:voidrttimerinit(rttimerttimer,constchar*name,void(*timeout)(void*parameter),void*parameter,rttickttime,rtuint8tflag)使用该接口时,需指定定时器对象,定时器名称,提供定时器回调函数及参数,定时时间,并指定是单次定时还是周期定时.
初始化定时器的例子如下代码所示.

#includestructrttimertimer1;structrttimertimer2;/*定时器timer1的触发函数*/voidtimeout1(void*parameter){rtkprintf("periodictimeristimeout\n");}/*定时器timer2的触发函数*/voidtimeout2(void*parameter){rtkprintf("oneshottimeristimeout\n");}110Chapter10.
定时器与系统时钟RT-ThreadProgrammingGuide,Release0.
3.
0/*用户应用程序入口*/intrtapplicationinit(){/*初始化timer1为周期性定时器*/rttimerinit(&timer1,"timer1",timeout1,RTNULL,200,RTTIMERFLAGPERIODIC);/*初始化timer2为单次定时器*/rttimerinit(&timer2,"timer2",timeout2,RTNULL,200,RTTIMERFLAGONESHOT);/*启动定时器timer1*/rttimerstart(&timer1);/*启动定时器timer2*/rttimerstart(&timer2);return0;}10.
3.
5脱离定时器脱离定时器使定时器对象被从系统容器的链表中脱离出来,但定时器对象所占有的内存不会被释放,脱离信号量使用以下接口.
rterrtrttimerdetach(rttimerttimer)脱离定时器的例子如下代码所示.
/*用户应用程序入口*/intrtapplicationinit(){/*初始化timer1为周期性定时器*/rttimerinit(&timer1,"timer1",timeout1,RTNULL,200,RTTIMERFLAGPERIODIC);/*初始化timer2为单次定时器*/rttimerinit(&timer2,"timer2",timeout2,RTNULL,200,RTTIMERFLAGONESHOT);/*脱离定时器timer1*/rttimerdetach(&timer1);/*脱离定时器timer2*/rttimerstart(&timer2);return0;}10.
3.
定时器管理接口111RT-ThreadProgrammingGuide,Release0.
3.
010.
3.
6启动定时器当定时器被创建或者初始化以后,不会被立即启动,必须在调用启动定时器接口后,才开始工作,启动定时器接口如下:rterrtrttimerstart(rttimerttimer)启动定时器的例子如下代码所示.
#include/*用户应用程序入口*/intrtapplicationinit(){rttimerttimer1;rttimerttimer2;/*创建周期性定时器timer1*/timer1=rttimerinit("timer1",timeout1,RTNULL,200,RTTIMERFLAGPERIODIC);/*创建单次定时器timer2*/timer2=rttimerinit("timer2",timeout2,RTNULL,200,RTTIMERFLAGONESHOT);/*启动定时器timer1*/rttimerstart(&timer1);/*启动定时器timer2*/rttimerstart(&timer2);return0;}10.
3.
7停止定时器启动定时器以后,若想使它停止,可以使用该接口:rterrtrttimerstop(rttimerttimer)停止定时器的例子如下代码所示.
#include/*用户应用程序入口*/intrtapplicationinit(){rttimerttimer1;rttimerttimer2;112Chapter10.
定时器与系统时钟RT-ThreadProgrammingGuide,Release0.
3.
0/*创建周期性定时器timer1*/timer1=rttimerinit("timer1",timeout1,RTNULL,200,RTTIMERFLAGPERIODIC);/*创建单次定时器timer2*/timer2=rttimerinit("timer2",timeout2,RTNULL,200,RTTIMERFLAGONESHOT);/*停止定时器timer1*/rttimerstop(&timer1);/*停止定时器timer2*/rttimerstop(&timer2);return0;}10.
3.
8控制定时器控制定时器接口可以用来查看或改变定时器的设置,它提供四个命令接口,分别是设置定时时间,查看定时时间,设置单次触发,设置周期触发.
命令如下:#defineRTTIMERCTRLSETTIME0x0/*settimer.
*/#defineRTTIMERCTRLGETTIME0x1/*gettimer.
*/#defineRTTIMERCTRLSETONESHOT0x2/*changetimertooneshot.
*/#defineRTTIMERCTRLSETPERIODIC0x3/*changetimertoperiodic.
*/控制定时器接口如下:rterrtrttimercontrol(rttimerttimer,rtuint8tcmd,void*arg)使用该接口时,需指定定时器对象,控制命令及相应参数.
控制定时器的例子如下代码所示.
#include/*用户应用程序入口*/intrtapplicationinit(){rttimerttimer1;rttimerttimer2;/*创建周期性定时器timer1*/timer1=rttimerinit("timer1",timeout1,RTNULL,200,RTTIMERFLAGPERIODIC);/*创建单次定时器timer2*/timer2=rttimerinit("timer2",timeout2,RTNULL,200,RTTIMERFLAGONESHOT);10.
3.
定时器管理接口113RT-ThreadProgrammingGuide,Release0.
3.
0/*控制定时器timer1,重新设置timer1定时时间*/rttimercontrol(timer1,RTTIMERCTRLSETTIME,100);/*控制定时器timer2,重新设置timer2为单次触发类型,重新设置定时时间*/rttimercontrol(timer2,RTTIMERCTRLSETONESHOT,100);return0;}10.
3.
9检查定时器检查定时器接口是一个系统接口,当每一次系统时钟中断到来时,该接口就会被调用,它会检查该时刻是否有定时器到期,接口如下:voidrttimercheck()此函数为系统内部接口,不对外开放114Chapter10.
定时器与系统时钟CHAPTERELEVENI/O设备管理I/O管理模块为应用提供了一个对设备进行访问的通用接口,并通过定义的数据结构对设备驱动程序和设备信息进行管理.
从位置来说I/O管理模块相当于设备驱动程序和上层应用之间的一个中间层.
I/O管理模块实现了对设备驱动程序的封装.
设备驱动程序的实现与I/O管理模块独立,提高了模块的可移植性.
应用程序通过I/O管理模块提供的标准接口访问底层设备,设备驱动程序的升级不会对上层应用产生影响.
这种方式使得与设备的硬件操作相关的代码与应用相隔离,双方只需各自关注自己的功能,这降低了代码的复杂性,提高了系统的可靠性.
在第四章中已经介绍过RT-Thread的内核对象管理器.
读者若对这部分还不太了解,可以回顾一下这章.
在RT-Thread中,设备也被认为是一类对象,被纳入对象管理器范畴.
每个设备对象都是由基对象派生而来,每个具体设备都可以继承其父类对象的属性,并派生出其私有属性.
下图即为设备对象的继承和派生关系示意图.
Figure11.
1:设备对象的继承和派生关系示意图11.
1I/O设备管理控制块structrtdevice{115RT-ThreadProgrammingGuide,Release0.
3.
0structrtobjectparent;/*设备类型*/enumrtdeviceclasstypetype;/*设备参数及打开时参数*/rtuint16tflag,openflag;/*设备回调接口*/rterrt(*rxindicate)(rtdevicetdev,rtsizetsize);rterrt(*txcomplete)(rtdevicetdev,void*buffer);/*设备公共接口*/rterrt(*init)(rtdevicetdev);rterrt(*open)(rtdevicetdev,rtuint16toflag);rterrt(*close)(rtdevicetdev);rtsizet(*read)(rtdevicetdev,rtofftpos,void*buffer,rtsizetsize);rtsizet(*write)(rtdevicetdev,rtofftpos,constvoid*buffer,rtsizetsize);rterrt(*control)(rtdevicetdev,rtuint8tcmd,void*args);#ifdefRTUSINGDEVICESUSPENDrterrt(*suspend)(rtdevicetdev);rterrt(*resumed)(rtdevicetdev);#endif/*设备私有数据*/void*private;};当前RT-Thread支持的设备类型包括:enumrtdeviceclasstype{RTDeviceClassChar=0,/*字符设备*/RTDeviceClassBlock,/*块设备*/RTDeviceClassNetIf,/*网络接口设备*/RTDeviceClassMTD,/*内存设备*/RTDeviceClassCAN,/*CAN设备*/RTDeviceClassUnknown/*未知设备*/};Note:suspend、resume回调函数只会在''RTUSINGDEVICESUSPEND''宏使能的情况下才会有效.
116Chapter11.
I/O设备管理RT-ThreadProgrammingGuide,Release0.
3.
011.
2I/O设备管理接口11.
2.
1注册设备在一个设备能够被上层应用访问前,需要先把这个设备注册到系统中,并添加一些相应的属性.
这些注册的设备均可以采用"查找设备接口"通过设备名来查找设备,获得该设备控制块.
注册设备的原始如下:rterrtrtdeviceregister(rtdevicetdev,constchar*name,rtuint8tflags)其中调用的ags参数支持如下列表中的参数(可以采用或的方式支持多种参数):#defineRTDEVICEFLAGDEACTIVATE0x000/*未初始化设备*/#defineRTDEVICEFLAGRDONLY0x001/*只读设备*/#defineRTDEVICEFLAGWRONLY0x002/*只写设备*/#defineRTDEVICEFLAGRDWR0x003/*读写设备*/#defineRTDEVICEFLAGREMOVABLE0x004/*可移除设备*/#defineRTDEVICEFLAGSTANDALONE0x008/*独立设备*/#defineRTDEVICEFLAGACTIVATED0x010/*已激活设备*/#defineRTDEVICEFLAGSUSPENDED0x020/*挂起设备*/#defineRTDEVICEFLAGSTREAM0x040/*设备处于流模式*/#defineRTDEVICEFLAGINTRX0x100/*设备处于中断接收模式*/#defineRTDEVICEFLAGDMARX0x200/*设备处于DMA接收模式*/#defineRTDEVICEFLAGINTTX0x400/*设备处于中断发送模式*/#defineRTDEVICEFLAGDMATX0x800/*设备处于DMA发送模式*/RTDEVICEFLAGSTREAM参数用于向串口终端输出字符串,当输出的字符是"n"时,自动在前面补一个"r"做分行.
11.
2.
2卸载设备将设备从设备系统中卸载,被卸载的设备将不能通过"查找设备接口"找到该设备,可以通过如下接口完成:rterrtrtdeviceunregister(rtdevicetdev)Note:卸载设备并不会释放设备控制块所占用的内存.
11.
2.
3初始化所有设备初始化所有注册到设备对象管理器中的未初始化的设备,可以通过如下接口完成:11.
2.
I/O设备管理接口117RT-ThreadProgrammingGuide,Release0.
3.
0rterrtrtdeviceinitall(void)Note:如果设备的ags域已经是RTDEVICEFLAGACTIVATED,调用这个接口将不再重复做初始化,一个设备初始化完成后它的ags域RTDEVICEFLAGACTIVATED应该被置位.
11.
2.
4查找设备根据指定的设备名称来查找设备,可以通过如下接口完成:rtdevicetrtdevicefind(constchar*name)使用以上接口时,在设备对象类型所对应的对象容器中遍历寻找设备对象,然后返回该设备,如果没有找到相应的设备对象,则返回RTNULL.
11.
2.
5打开设备根据设备控制块来打开设备,可以通过如下接口完成:rterrtrtdeviceopen(rtdevicetdev,rtuint16toflags)其中oags支持以下列表中的参数:#defineRTDEVICEOFLAGRDONLY0x001/*只读模式访问*/#defineRTDEVICEOFLAGWRONLY0x002/*只写模式访问*/#defineRTDEVICEOFLAGRDWR0x003/*读写模式访问*/Note:如果设备ags域包含RTDEVICEFLAGSTANDALONE参数,将不允许重复打开.
11.
2.
6关闭设备根据设备控制块来关闭设备,可以通过如下接口完成:rterrtrtdeviceclose(rtdevicetdev)11.
2.
7读设备根据设备控制块来读取设备,可以通过如下接口完成:118Chapter11.
I/O设备管理RT-ThreadProgrammingGuide,Release0.
3.
0rtsizetrtdeviceread(rtdevicetdev,rtofftpos,void*buffer,rtsizetsize)根据底层驱动的实现,通常这个接口并不会阻塞上层应用线程.
返回值是读到数据的大小(以字节为单位),如果返回值是0,需要读取当前线程的errno来判断错误状态.

11.
2.
8写设备根据设备控制块来写入设备,可以通过如下接口完成:rtsizetrtdevicewrite(rtdevicetdev,rtofftpos,constvoid*buffer,rtsizetsize)根据底层驱动的实现,通常这个接口并不会阻塞上层应用线程.
返回值是写入数据的大小(以字节为单位),如果返回值是0,需要读取当前线程的errno来判断错误状态.

11.
2.
9控制设备根据设备控制块来控制设备,可以通过如下接口完成:rterrtrtdevicecontrol(rtdevicetdev,rtuint8tcmd,void*arg)cmd命令参数通常是和设备驱动程序相关的.
11.
2.
10设置数据接收指示设置一个回调函数,当硬件设备收到数据时回调给应用程序以通知有数据达到.
可以通过如下接口完成设置接收指示:rterrtrtdevicesetrxindicate(rtdevicetdev,rterrt(*rxind)(rtdevicetdev,rtsizets回调函数rxind由调用者提供,当硬件设备接收到数据时,会回调这个函数并把收到的数据长度放在size参数中传递给上层应用.
上层应用线程应在收到指示时,立刻从设备中读取数据.
11.
2.
11设置发送完成指示在上层应用调用rtdevicewrite写入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数.
这个回调函数会在底层硬件给出发送完成时(例如DMA传送完成或FIFO已经写入完毕产生完成中断时)被调用.
可以通过如下接口完成设备发送完成指示:rterrtrtdevicesettxcomplete(rtdevicetdev,rterrt(*txdone)(rtdevicetdev,void*buff11.
2.
I/O设备管理接口119RT-ThreadProgrammingGuide,Release0.
3.
0回调函数txdone由调用者提供,当硬件设备发送完数据时,由驱动程序回调这个函数并把发送完成的数据块地址buer做为参数传递给上层应用.
上层应用(线程)在收到指示时应根据发送buer的情况,释放buer内存块或为下一个写数据做缓存.
11.
3设备驱动上一节说到了如何使用RT-Thread的设备接口,但对于开发人员来说,如何编写一个驱动设备可能会更加重要.
11.
3.
1设备驱动必须实现的接口我们先来看看/解析下RT-Thread的设备控制块:structrtdevice{structrtobjectparent;/*设备类型*/enumrtdeviceclasstypetype;/*设备参数及打开时的参数*/rtuint16tflag,openflag;/*设备回调函数*/rterrt(*rxindicate)(rtdevicetdev,rtsizetsize);rterrt(*txcomplete)(rtdevicetdev,void*buffer);/*公共的设备接口*/rterrt(*init)(rtdevicetdev);rterrt(*open)(rtdevicetdev,rtuint16toflag);rterrt(*close)(rtdevicetdev);rtsizet(*read)(rtdevicetdev,rtofftpos,void*buffer,rtsizetsize);rtsizet(*write)(rtdevicetdev,rtofftpos,constvoid*buffer,rtsizetsize);rterrt(*control)(rtdevicetdev,rtuint8tcmd,void*args);/*当使用了设备挂起功能时的接口*/#ifdefRTUSINGDEVICESUSPENDrterrt(*suspend)(rtdevicetdev);rterrt(*resumed)(rtdevicetdev);#endif/*deviceprivatedata*/void*private;};120Chapter11.
I/O设备管理RT-ThreadProgrammingGuide,Release0.
3.
0其中包含了一个套公共的设备接口(类似上节说的设备访问接口,但面向的层次已经不一样了):/*公共的设备接口*/rterrt(*init)(rtdevicetdev);rterrt(*open)(rtdevicetdev,rtuint16toflag);rterrt(*close)(rtdevicetdev);rtsizet(*read)(rtdevicetdev,rtofftpos,void*buffer,rtsizetsize);rtsizet(*write)(rtdevicetdev,rtofftpos,constvoid*buffer,rtsizetsize);rterrt(*control)(rtdevicetdev,rtuint8tcmd,void*args);/*当使用了设备挂起功能时的接口*/#ifdefRTUSINGDEVICESUSPENDrterrt(*suspend)(rtdevicetdev);rterrt(*resumed)(rtdevicetdev);#endif这些接口也是上层应用通过RT-Thread设备接口进行访问的实际底层接口,在满足一定的条件下,都会调用到这套接口.
其中suspend和resume接口是应用于RT-Thread的电源管理部分,目前的0.
3.
0版本并不支持,预留给以后使用.
其他的六个接口,可以看成是底层设备驱动必须提供的接口.
NameDescriptioninit设备的初始化.
设备初始化完成后,设备控制块的ag会被置成已激活状态(RTDEVICEFLAGACTIVATED).
如果设备控制块的ag不是已激活状态,那么在设备框架调用rtdeviceinitall接口时调用此设备驱动的init接口进行设备初始化.
open打开设备.
有些设备并不是系统一启动就已经打开开始运行的,或者设备需要进行数据接收,但如果上层应用还未准备好,设备也不应默认已经使能开始接收数据.
所以建议底层驱动程序,在调用open接口时进行设备的使能.
close关闭设备.
在打开设备时,设备框架中会自动进行打开计数(设备控制块中的ocount数据域),只有当打开计数为零的时候,底层设备驱动的close接口才会被调用.
read从设备中读取数据.
参数pos指出读取数据的偏移量,但是有些设备并不一定需要制定偏移量,例如串口设备,那么忽略这个参数即可.
这个接口返回的类型是rtsizet即读到的字节数,如果返回零建议检查errno值.
如果errno值并不是RTEOK,那么或者已经读取了所有数据,或者有错误发生.
write往设备中写入数据.
同样pos参数在一些情况下是不必要的,略过即可.
con-trol根据不同的cmd命令控制设备.
命令往往是由底层设备驱动自定义实现的.

11.
3.
2设备驱动实现的步骤上节中比较详细介绍了RT-Thread的设备控制块,那么实现一个设备驱动的步骤是如何的:11.
3.
设备驱动121RT-ThreadProgrammingGuide,Release0.
3.
01.
实现RT-Thread中定义的设备公共接口,开始可以是空函数(返回类型是rterrt的可默认返回RTEOK).
2.
根据自己的设备类型定义自己的私有数据域.
特别是可以有多个相同设备的情况下,设备接口可以用同一套,不同的只是各自的数据域(例如寄存器基地址).

3.
按照RT-Thread的对象模型,扩展一个对象有两种方式:(a)定义自己的私有数据结构,然后赋值到RT-Thread设备控制块的private指针上.
(b)从structrtdevice结构中进行派生.
4.
根据设备的类型,注册到RT-Thread设备框架中.
Note:异步设备,通俗的说就是,对设备进行读写并不是采用轮询的方式的,而是采用中断方式.
例如接收中断产生代表接收到数据,发送中断产生代表数据已经真实的发送完毕.
11.
3.
3AT91SAM7S64串口驱动做为一个例子,这里仔细分析了AT91SAM7S64的串口驱动,也包括上层应该如何使用这个设备的代码.
AT91SAM7S64串口驱动代码,详细的中文注释已经放在其中了.
#include#include#include"AT91SAM7X.
h"#include"serial.
h"/*串口寄存器结构*/typedefvolatilertuint32tREG32;structrtat91serialhw{REG32USCR;//ControlRegisterREG32USMR;//ModeRegisterREG32USIER;//InterruptEnableRegisterREG32USIDR;//InterruptDisableRegisterREG32USIMR;//InterruptMaskRegisterREG32USCSR;//ChannelStatusRegisterREG32USRHR;//ReceiverHoldingRegisterREG32USTHR;//TransmitterHoldingRegisterREG32USBRGR;//BaudRateGeneratorRegisterREG32USRTOR;//ReceiverTime-outRegisterREG32USTTGR;//TransmitterTime-guardRegisterREG32Reserved0[5];//122Chapter11.
I/O设备管理RT-ThreadProgrammingGuide,Release0.
3.
0REG32USFIDI;//FIDIRatioRegisterREG32USNER;//NbErrorsRegisterREG32Reserved1[1];//REG32USIF;//IRDAFILTERRegisterREG32Reserved2[44];//REG32USRPR;//ReceivePointerRegisterREG32USRCR;//ReceiveCounterRegisterREG32USTPR;//TransmitPointerRegisterREG32USTCR;//TransmitCounterRegisterREG32USRNPR;//ReceiveNextPointerRegisterREG32USRNCR;//ReceiveNextCounterRegisterREG32USTNPR;//TransmitNextPointerRegisterREG32USTNCR;//TransmitNextCounterRegisterREG32USPTCR;//PDCTransferControlRegisterREG32USPTSR;//PDCTransferStatusRegister};/*AT91串口设备*/structrtat91serial{/*采用从设备基类中继承*/structrtdeviceparent;/*串口设备的私有数据*/structrtat91serialhw*hwbase;/*寄存器基地址*/rtuint16tperipheralid;/*外设ID*/rtuint32tbaudrate;/*波特率*//*用于接收的域*/rtuint16tsaveindex,readindex;rtuint8trxbuffer[RTUARTRXBUFFERSIZE];};/*串口类的实例化,serial1/2*/#ifdefRTUSINGUART1structrtat91serialserial1;#endif#ifdefRTUSINGUART2structrtat91serialserial2;#endif/*串口外设的中断服务例程*/staticvoidrthwserialisr(intirqno){rtbasetlevel;structrtdevice*device;structrtat91serial*serial=RTNULL;11.
3.
设备驱动123RT-ThreadProgrammingGuide,Release0.
3.
0/*确定对应的外设对象*/#ifdefRTUSINGUART1if(irqno==AT91CIDUS0){/*serial1*/serial=&serial1;}#endif#ifdefRTUSINGUART2if(irqno==AT91CIDUS1){/*serial2*/serial=&serial2;}#endifRTASSERT(serial!
=RTNULL);/*获得设备基类对象指针*/device=(rtdevicet)serial;/*关闭中断以更新接收缓冲*/level=rthwinterruptdisable();/*读取一个字符*/serial->rxbuffer[serial->saveindex]=serial->hwbase->USRHR;/*把存放索引移到下一个位置*/serial->saveindex++;if(serial->saveindex>=RTUARTRXBUFFERSIZE)serial->saveindex=0;/*如果存放索引指向的位置已经到了读索引位置,则丢掉一个数据*/if(serial->saveindex==serial->readindex){serial->readindex++;if(serial->readindex>=RTUARTRXBUFFERSIZE)serial->readindex=0;}/*使能中断*/rthwinterruptenable(level);/*调用回调函数指示给上层收到了数据*/if(device->rxindicate!
=RTNULL)device->rxindicate(device,1);}124Chapter11.
I/O设备管理RT-ThreadProgrammingGuide,Release0.
3.
0/*以下的是设备基类的公共接口(虚拟函数)*/staticrterrtrtserialinit(rtdevicetdev){rtuint32tbd;structrtat91serial*serial=(structrtat91serial*)dev;RTASSERT(serial!
=RTNULL);/*确认外设标识必需为US0或US1*/RTASSERT((serial->peripheralid!
=AT91CIDUS0)&&(serial->peripheralid!
=AT91CIDUS1));/*使能时钟*/AT91CPMCPCER=1peripheralid;/*设置pinmux以使能Rx/Tx数据引脚*/if(serial->peripheralid==AT91CIDUS0){AT91CPIOPDR=(1peripheralid==AT91CIDUS1){AT91CPIOPDR=(1hwbase->USCR=AT91CUSRSTRX|/*ResetReceiver*/AT91CUSRSTTX|/*ResetTransmitter*/AT91CUSRXDIS|/*ReceiverDisable*/AT91CUSTXDIS;/*TransmitterDisable*//*默认都设置为8-N-1*/serial->hwbase->USMR=AT91CUSUSMODENORMAL|/*NormalMode*/AT91CUSCLKSCLOCK|/*Clock=MCK*/AT91CUSCHRL8BITS|/*8-bitData*/AT91CUSPARNONE|/*NoParity*/AT91CUSNBSTOP1BIT;/*1StopBit*//*设置波特率,注:主时钟(MCK)在board.
h中定义*/bd=((MCK*10)/(serial->baudrate*16));if((bd%10)>=5)bd=(bd/10)+1;elsebd/=10;serial->hwbase->USBRGR=bd;serial->hwbase->USCR=AT91CUSRXEN|/*使能接收*/AT91CUSTXEN;/*使能发送*//*重置读写索引*/11.
3.
设备驱动125RT-ThreadProgrammingGuide,Release0.
3.
0serial->saveindex=0;serial->readindex=0;/*重置接收缓冲*/rtmemset(serial->rxbuffer,0,RTUARTRXBUFFERSIZE);returnRTEOK;}staticrterrtrtserialopen(rtdevicetdev,rtuint16toflag){structrtat91serial*serial=(structrtat91serial*)dev;RTASSERT(serial!
=RTNULL);/*如果是中断方式接收,打开中断并装载中断*/if(dev->flag&RTDEVICEFLAGINTRX){/*enableUARTrxinterrupt*/serial->hwbase->USIER=1hwbase->USIMR|=1peripheralid,rthwserialisr,RTNULL);AT91CAICSMR(serial->peripheralid)=5|(0x01peripheralid);}returnRTEOK;}staticrterrtrtserialclose(rtdevicetdev){structrtat91serial*serial=(structrtat91serial*)dev;RTASSERT(serial!
=RTNULL);/*如果是中断方式接收,关闭中断*/if(dev->flag&RTDEVICEFLAGINTRX){/*disableinterrupt*/serial->hwbase->USIDR=1hwbase->USIMR&=(1hwbase->USCR=AT91CUSRSTRX|/*ResetReceiver*/AT91CUSRSTTX|/*ResetTransmitter*/AT91CUSRXDIS|/*ReceiverDisable*/126Chapter11.
I/O设备管理RT-ThreadProgrammingGuide,Release0.
3.
0AT91CUSTXDIS;/*TransmitterDisable*/returnRTEOK;}staticrtsizetrtserialread(rtdevicetdev,rtofftpos,void*buffer,rtsizetsize){rtuint8t*ptr;structrtat91serial*serial=(structrtat91serial*)dev;RTASSERT(serial!
=RTNULL);/*ptr指向读取的缓冲*/ptr=(rtuint8t*)buffer;if(dev->flag&RTDEVICEFLAGINTRX){/*中断模式接收*/while(size){rtbasetlevel;/*serail->rxbuffer是和ISR共享的,需要关闭中断保护*/level=rthwinterruptdisable();if(serial->readindex!
=serial->saveindex){*ptr=serial->rxbuffer[serial->readindex];serial->readindex++;if(serial->readindex>=RTUARTRXBUFFERSIZE)serial->readindex=0;}else{/*rxbuffer中无数据*//*使能中断*/rthwinterruptenable(level);break;}/*使能中断*/rthwinterruptenable(level);ptr++;size--;}11.
3.
设备驱动127RT-ThreadProgrammingGuide,Release0.
3.
0return(rtuint32t)ptr-(rtuint32t)buffer;}elseif(dev->flag&RTDEVICEFLAGDMARX){/*DMA模式接收,目前不支持*/RTASSERT(0);}else{/*轮询模式*/while(size){/*等待数据达到*/while(!
(serial->hwbase->USCSR&AT91CUSRXRDY));/*读取一个数据*/*ptr=serial->hwbase->USRHR;ptr++;size--;}return(rtsizet)ptr-(rtsizet)buffer;}return0;}staticrtsizetrtserialwrite(rtdevicetdev,rtofftpos,constvoid*buffer,rtsizetsiz{rtuint8t*ptr;structrtat91serial*serial=(structrtat91serial*)dev;RTASSERT(serial!
=RTNULL);ptr=(rtuint8t*)buffer;if(dev->openflag&RTDEVICEOFLAGWRONLY){if(dev->flag&RTDEVICEFLAGSTREAM){/*STREAM模式发送*/while(size){/*遇到'\n'进入STREAM模式,在前面添加一个'\r'*/if(*ptr=='\n'){while(!
(serial->hwbase->USCSR&AT91CUSTXRDY));serial->hwbase->USTHR='\r';}128Chapter11.
I/O设备管理RT-ThreadProgrammingGuide,Release0.
3.
0/*等待发送就绪*/while(!
(serial->hwbase->USCSR&AT91CUSTXRDY));/*发送单个字符*/serial->hwbase->USTHR=*ptr;ptr++;size--;}}else{while(size){/*等待发送就绪*/while(!
(serial->hwbase->USCSR&AT91CUSTXRDY));/*发送单个字符*/serial->hwbase->USTHR=*ptr;ptr++;size--;}}}return(rtsizet)ptr-(rtsizet)buffer;}staticrterrtrtserialcontrol(rtdevicetdev,rtuint8tcmd,void*args){returnRTEOK;}/*串口设备硬件初始化,它会根据配置情况进行串口设备注册*/rterrtrthwserialinit(){rtdevicetdevice;#ifdefRTUSINGUART1device=(rtdevicet)&serial1;/*初始化AT91串口设备私有数据*/serial1.
hwbase=(structrtat91serialhw*)AT91CBASEUS0;serial1.
peripheralid=AT91CIDUS0;serial1.
baudrate=115200;/*设置设备基类的虚拟函数接口*/11.
3.
设备驱动129RT-ThreadProgrammingGuide,Release0.
3.
0device->init=rtserialinit;device->open=rtserialopen;device->close=rtserialclose;device->read=rtserialread;device->write=rtserialwrite;device->control=rtserialcontrol;/*在设备子系统中注册uart1设备*/rtdeviceregister(device,"uart1",RTDEVICEFLAGRDWR|RTDEVICEFLAGINTRX);#endif#ifdefRTUSINGUART2/*初始化AT91串口设备私有数据*/device=(rtdevicet)&serial2;serial2.
hwbase=(structrtat91serialhw*)AT91CBASEUS1;serial2.
peripheralid=AT91CIDUS1;serial2.
baudrate=115200;/*设置设备基类的虚拟函数接口*/device->init=rtserialinit;device->open=rtserialopen;device->close=rtserialclose;device->read=rtserialread;device->write=rtserialwrite;device->control=rtserialcontrol;/*在设备子系统中注册uart2设备*/rtdeviceregister(device,"uart2",RTDEVICEFLAGRDWR|RTDEVICEFLAGINTRX);#endifreturnRTEOK;}这个驱动程序中是包含中断发送、接收的情况,所以针对这些,下面给出了具体的使用代码.
在这个例子中,线程将在两个设备上(UART1,UART2)读取数据,然后再写到其中的一个设备中.
#include/*UART接收消息结构*/structrxmsg{rtdevicetdev;rtsizetsize;};130Chapter11.
I/O设备管理RT-ThreadProgrammingGuide,Release0.
3.
0/*用于接收消息的消息队列*/staticrtmqtrxmq;/*接收线程的接收缓冲区*/staticcharuartrxbuffer[64];/*数据达到回调函数*/rterrtuartinput(rtdevicetdev,rtsizetsize){structrxmsgmsg;msg.
dev=dev;msg.
size=size;/*发送消息到消息队列中*/rtmqsend(rxmq,&msg,sizeof(structrxmsg));returnRTEOK;}voiddevicethreadentry(void*parameter){structrxmsgmsg;intcount=0;rtdevicetdevice,writedevice;rterrtresult=RTEOK;device=rtdevicefind("uart1");if(device!
=RTNULL){/*设置回调函数及打开设备*/rtdevicesetrxindicate(device,uartinput);rtdeviceopen(device,RTDEVICEOFLAGRDWR);}/*设置写设备*/writedevice=device;device=rtdevicefind("uart2");if(device!
=RTNULL){/*设置回调函数及打开设备*/rtdevicesetrxindicate(device,uartinput);rtdeviceopen(device,RTDEVICEOFLAGRDWR);}while(1){11.
3.
设备驱动131RT-ThreadProgrammingGuide,Release0.
3.
0/*从消息队列中读取消息*/result=rtmqrecv(rxmq,&msg,sizeof(structrxmsg),50);if(result==-RTETIMEOUT){/*接收超时*/rtkprintf("timeoutcount:%d\n",++count);}/*成功收到消息*/if(result==RTEOK){rtuint32trxlength;rxlength=(sizeof(uartrxbuffer)-1)>msg.
sizemsg.
size:sizeof(uartrxbuffer)-1;/*读取消息*/rxlength=rtdeviceread(msg.
dev,0,&uartrxbuffer[0],rxlength);uartrxbuffer[rxlength]='\0';/*写到写设备中*/if(writedevice!
=RTNULL)rtdevicewrite(writedevice,0,&uartrxbuffer[0],rxlength);}}}intrtapplicationinit(){/*创建devt线程*/rtthreadtthread=rtthreadcreate("devt",devicethreadentry,RTNULL,1024,25,7);/*创建成功则启动线程*/if(thread!
=RTNULL)rtthreadstartup(&thread);}线程devt启动后,将先查找是否有存在uart1,uart2两个设备,如果存在则设置数据接收到回调函数.
在数据接收到的回调函数中,将把对应的设备句柄,接收到的数据长度填充到一个消息结构(structrxmsg)上,然后发送到消息队列中.
devt线程在打开完设备后,将在消息队列中等待消息的到来.
如果消息队列是空的,devt线程将被阻塞,直到它接收到消息被唤醒,或在0.
5秒(50OSTick)内都没收到消息而唤醒.
两者唤醒时,从rtmqrecv函数的返回值中是不相同的.
当devt线程因为接收到消息而唤醒时(rtmqrecv函数的返回值是RTEOK),它将主动调用rtdeviceread去读取消息,然后写132Chapter11.
I/O设备管理RT-ThreadProgrammingGuide,Release0.
3.
0入到writedevice设备中.
11.
3.
设备驱动133RT-ThreadProgrammingGuide,Release0.
3.
0134Chapter11.
I/O设备管理CHAPTERTWELVEFINSHSHELL系统RT-Thread的shell系统——nsh,提供了一套供用户在命令行操作的接口,主要用于调试、查看系统信息.
nsh被设计成一个不同于传统命令行的解释器,由于很多嵌入式系统都是采用C语言来编写,所以nsh的输入对象也类似于C语言表达式的风格:它能够解析执行大部分C语言的表达式,也能够使用类似于C语言的函数调用方式访问系统中的函数及全局变量,此外它也能够通过命令行方式创建变量.
12.
1基本数据类型nsh支持基本的C语言数据类型,包括:DataTypeDescriptionvoid空数据格式,只用于创建指针变量char,unsignedchar(带符号)字符型数据short,unsignedint(带符号)整数型数据long,unsignedlong(带符号)长整型数据此外,nsh也支持指针类型(void*或int*等声明方式),如果指针做为函数指针类型调用,将自动按照函数方式执行.
nsh中内建了一些命令函数,可以在命令行中调用:list()显示系统中存在的命令及变量,在AT91SAM7S64平台上执行结果如下:--FunctionList:helloversionlistlistthreadlistsemlistmutexlisteventlistmb135RT-ThreadProgrammingGuide,Release0.
3.
0listmqlistmemplisttimer--VariableList:–FunctionList表示的是函数列表;–VariableList表示的是变量列表.
12.
2RT-Thread内置命令针对RT-ThreadRTOS,nsh也提供了一些基本的函数命令:12.
2.
1listthread()列表显示当前系统中线程状态,lumit4510显示结果如下:threadpristatusspstacksizemaxusedlefttickerrortidle0xffready0x000000740x000001000x000000740x0000003b000tshell0x14ready0x0000024c0x000008000x000004180x00000064000thread字段表示线程的名称pri字段表示线程的优先级status字段表示线程当前的状态sp字段表示线程当前的栈位置stacksize字段表示线程的栈大小maxused字段表示线程历史上使用的最大栈位置lefttick字段表示线程剩余的运行节拍数error字段表示线程的错误号12.
2.
2listsem()列表显示系统中信号量状态,lumit4510显示结果如下:semaphorevsuspendthreaduart0000semaphore字段表示信号量的名称v字段表示信号量的当前值suspendthread字段表示等在这个信号量上的线程数目136Chapter12.
FinSHShell系统RT-ThreadProgrammingGuide,Release0.
3.
012.
2.
3listmb()列表显示系统中信箱状态,lumit4510显示结果如下:mailboxentrysizesuspendthreadmailbox字段表示信箱的名称entry字段表示信箱中包含的信件数目size字段表示信箱能够容纳的最大信件数目suspendthread字段表示等在这个信箱上的线程数目12.
2.
4listmq()列表显示系统中消息队列状态,lumit4510显示结果如下:msgqueueentrysuspendthreadsemaphore字段表示消息队列的名称entry字段表示消息队列中当前包含的消息数目size字段表示消息队列能够容纳的最大消息数目suspendthread字段表示等在这个消息队列上的线程数目12.
2.
5listevent()列表显示系统中事件状态,lumit4510显示结果如下:eventsetsuspendthreadevent字段表示事件的名称set字段表示事件的值suspendthread字段表示等在这个事件上的线程数目12.
2.
6listtimer()列表显示系统中定时器状态,lumit4510显示结果如下:timerperiodictimeoutflagtidle0x000000000x00000000deactivatedtshell0x000000000x00000000deactivatedcurrenttick:0x00000d7etimer字段表示定时器的名称12.
2.
RT-Thread内置命令137RT-ThreadProgrammingGuide,Release0.
3.
0periodic字段表示定时器是否是周期性的timeout字段表示定时器超时时的节拍数flag字段表示定时器的状态,activated表示活动的,deactivated表示不活动的currenttick表示当前系统的节拍12.
3应用程序接口nsh的应用程序接口提供了上层注册函数或变量的接口,使用时应包含如下头文件:#include注:另外一种添加函数及变量的方式,请参看本章选项一节.
12.
3.
1添加函数voidfinshsyscallappend(constchar*name,syscallfuncfunc)在nsh中添加一个函数.
name–函数在nshshell中访问的名称func–函数的地址12.
3.
2添加变量voidfinshsysvarappend(constchar*name,uchartype,void*addr)这个接口用于在nsh中添加一个变量:name–变量在finshshell中访问的名称type–数据类型,由枚举类型finshtype给出.
当前finsh支持的数据类型:enumfinshtype{finshtypeunknown=0,finshtypevoid,/**void*/finshtypevoidp,/**voidpointer*/finshtypechar,/**char*/finshtypeuchar,/**unsignedchar*/finshtypecharp,/**charpointer*/finshtypeshort,/**short*/finshtypeushort,/**unsignedshort*/finshtypeshortp,/**shortpointer*/finshtypeint,/**int*/finshtypeuint,/**unsignedint*/138Chapter12.
FinSHShell系统RT-ThreadProgrammingGuide,Release0.
3.
0finshtypeintp,/**intpointer*/finshtypelong,/**long*/finshtypeulong,/**unsignedlong*/finshtypelongp/**longpointer*/};addr–变量的地址12.
4移植由于nsh完全采用ANSIC编写,具备极好的移植性,同时在内存占用上也非常小,如果不使用上述提到的API函数,整个nsh将不会动态申请内存.
nshshell线程:每次的命令执行都是在nshshell线程的上下文中完成的,nshshell线程在函数nshsysteminit()中创建,它将一直等待uartsem信号量的释放.
nsh的输出:nsh的输出依赖于系统的输出,在RT-Thread中依赖的是rtkprintf输出.
nsh的输入:nshshell线程在获得了uartsem信号量后调用rtserialgetc()函数从串口中获得一个字符然后处理.
所以nsh的移植需要rtserialgetc()函数的实现.
而uartsem信号量的释放通过调用nshnotify()函数以完成对nshshell线程的输入通知.
通常的过程是,当串口接收中断发生时(即串口中有输入),接收中断服务例程调用nshnotify()函数通知nshshell线程有输入;而后nshshell线程获取串口输入最后做相应的命令处理.
12.
5选项要开启nsh的支持,在RT-Thread的配置中必须定义RTUSINGFINSH宏.
/*SECTION:FinSHshelloptions*//*UsingFinSHasShell*/#defineRTUSINGFINSH/*Usingsymboltable*/#defineFINSHUSINGSYMTAB#defineFINSHUSINGDESCRIPTION12.
4.
移植139RT-ThreadProgrammingGuide,Release0.
3.
0采用FINSHUSINGSYMTAB将可以支持FINSHFUNCTIONEXPORT和FINSHVAREXPORT的方式输出函数或变量到shell中调用.
例如:longhello(){rtkprintf("HelloRT-Thread!
\n");return0;}FINSHFUNCTIONEXPORT(hello,sayhelloworld)staticintdummy=0;FINSHVAREXPORT(dummy,finshtypeint,dummyvariableforfinsh)hello函数、counter变量将自动输出到shell中,既可以在shell中调用、访问hello函数、counter变量:采用FINSHUSINGDESCRIPTION将可以在list()列出函数、变量列表时显示相应的描述.
140Chapter12.
FinSHShell系统CHAPTERTHIRTEEN文件系统RT-Thread的文件系统采用了三层的结构,如下图所示:Figure13.
1:文件系统结构最顶层的是一套面向嵌入式系统专门优化过的虚拟文件系统(接口),通过它能够适配下层不同的文件系统格式,例如个人电脑上常使用的FAT文件系统,或者是嵌入式设备中常用的ash文件系统.
接下来的是各种文件系统的实现,例如支持FAT文件系统的DFS-FAT、DFS-EFSL,支持NandFlash的YAFFS2也即将移植进这套虚拟文件系统框架中.
最底层的是各类存储驱动,例如SD卡驱动,IDE硬盘驱动等.
RT-Thread的文件系统对上层提供的接口主要以POSIX标准接口为主,这样这部分代码也容易调试通过.
141RT-ThreadProgrammingGuide,Release0.
3.
013.
1文件系统接口13.
1.
1打开文件打开或创建一个文件可以调用如下接口intopen(constchar*pathname,intoflag)pathname是要打开或创建的文件名,oag指出打开文件的选项,当前可以支持:NameDescriptionORDONLYRD只读方式打开OWRONLYWR只写方式打开ORDWRRD-WR读写方式打开OCREAT如果要打开的文件不存在,则建立该文件OAPPEND当读写文件时会从文件尾开始移动,也就是所定入的数据会以附加的方式加入到文件后面ODIRECTORY如果参数pathname所指的文件并非为一个目录,则会令打开文件失败打开成功时返回打开文件的描述符序号,使用open的例子如下/*假设文件操作是在一个线程中完成*/voidfilethread(){intfd,size;chars[]="RT-ThreadProgrammer!
\n",buffer[80];/*打开/text.
txt作写入,如果该文件不存在则建立该文件*/fd=open("/text.
txt",OWRONLY|OCREAT);if(fd>=0){write(fd,s,sizeof(s));close(fd);}/*打开/text.
txt准备作读取动作*/fd=open("/text.
txt",ORDONLY);if(fd>=0){size=read(fd,buffer,sizeof(buffer));close(fd);}printf("%s",buffer);}142Chapter13.
文件系统RT-ThreadProgrammingGuide,Release0.
3.
013.
1.
2关闭文件intclose(intfd)当使用完文件后若已不再需要则可使用close()关闭该文件,而close()会让数据写回磁盘,并释放该文件所占用的资源.
参数fd为先前由open()或creat()所返回的文件描述词.

13.
1.
3读取数据ssizetread(intfd,void*buf,sizetcount)read()函数会把参数fd所指的文件传送count个字节到buf指针所指的内存中.

若参数count为0,则read()不会有作用并返回0.
返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置随读取到的字节移动.

13.
1.
4写入数据sizetwrite(intfd,constvoid*buf,sizetcount)write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内.
当然,文件读写位置也会随之移动.
如果顺利write()会返回实际写入的字节数.
当有错误发生时则返回-1,错误代码存入当前线程的errno中.
13.
1.
5更改名称intrename(constchar*oldpath,constchar*newpath)rename()会将参数oldpath所指定的文件名称改为参数newpath所指的文件名称.

或newpath所指定的文件已经存在,则会被删除.
例子代码如下:voidfilethread(void*parameter){rtkprintf("%s=>%s","/text1.
txt","/text2.
txt");if(rename("/text1.
txt","/text2.
txt")dname);}}}13.
2.
4取得目录流的读取位置rtoffttelldir(DIR*d)telldir()函数用来取得当前目录流的读取位置.
13.
2.
5设置下次读取目录的位置voidseekdir(DIR*d,rtofftoffset)seekdir()函数用来设置下回目录读取的位置.
例子如下:voiddiroperation(void*parameter){DIR*dirp;intsave3=0;intcur;inti=0;structdirent*dp;dirp=opendir(".
");for(dp=readdir(dirp);dp!
=RTNULL;dp=readdir(dirp)){/*保存第三个目录项的目录指针*/if(i++==3)save3=telldir(dirp);rtkprintf("%s\n",dp->dname);146Chapter13.
文件系统RT-ThreadProgrammingGuide,Release0.
3.
0}/*回到刚才保存的第三个目录项的目录指针*/seekdir(dirp,save3);/*检查当前目录指针是否等于保存过的第三个目录项的指针.
*/cur=telldir(dirp);if(cur!
=save3){rtkprintf("seekdir(d,%ld);telldir(d)==%ld\n",save3,cur);}/*从第三个目录项开始打印*/for(dp=readdir(dirp);dp!
=NULL;dp=readdir(dirp))rtkprintf("%s\n",dp->dname);/*关闭目录*/closedir(dirp);}13.
2.
6重设读取目录的位置为开头位置voidrewinddir(DIR*d)rewinddir()函数用来设置读取的目录位置为开头位置.
13.
2.
7关闭目录intclosedir(DIR*d)closedir()函数用来关闭一个目录,如果关闭目录成功返回0,否则返回-1,该函数必须和opendir()函数成对出现.
13.
2.
8删除目录intrmdir(constchar*pathname)rmdir()函数用来删除一个目录,如果删除目录成功返回0,否则返回-1.
13.
2.
目录操作接口147RT-ThreadProgrammingGuide,Release0.
3.
013.
3下层驱动接口TODO148Chapter13.
文件系统CHAPTERFOURTEENTCP/IP协议栈LwIP是瑞士计算机科学院(SwedishInstituteofComputerScience)的AdamDunkels等开发的一套用于嵌入式系统的开放源代码TCP/IP协议栈,它在包含完整的TCP协议实现基础上实现了小型的资源占用,因此它十分适合于使用到嵌入式设备中,占用的体积大概在几十kBRAM和40KBROM代码左右.
由于LwIP出色的小巧实现,而功能也相对完善,用户群比较广泛,RT-Thread采用LwIP做为默认的TCP/IP协议栈,同时根据小型设备的特点对其进行再优化,体积相对进一步减小,**RAM占用缩小到5kB附近**(依据上层应用使用情况会有浮动).
本章主要讲述了LwIP在RT-Thread中的使用.
14.
1协议初始化在使用LwIP协议栈之前,需要初始化协议栈.
协议栈本身会启动一个TCP的线程,和协议相关的处理都会放在这个线程中完成.
#include#ifdefRTUSINGLWIP#include#endif/*初始化线程入口*/voidrtinitthreadentry(void*parameter){/*LwIP初始化*/#ifdefRTUSINGLWIP{externvoidlwipsysinit(void);/*初始化LwIP系统*/lwipsysinit();rtkprintf("TCP/IPinitialized!
\n");}149RT-ThreadProgrammingGuide,Release0.
3.
0#endif}intrtapplicationinit(){rtthreadtinitthread;/*创建初始化线程*/initthread=rtthreadcreate("init",rtinitthreadentry,RTNULL,2048,10,5);/*启动线程*/if(initthread!
=RTNULL)rtthreadstartup(initthread);return0;}另外,在RT-Thread中为了使用LwIP协议栈需要在rtcong.
h头文件中定义使用LwIP的宏/*使用lighweightTCP/IP协议栈*/#defineRTUSINGLWIPLwIP协议栈的主线程TCP的参数(优先级,信箱大小,栈空间大小)也可以在rtcong.
h头文件中定义/*tcp线程选项*/#defineRTLWIPTCPTHREADPRIORITY120#defineRTLWIPTCPTHREADMBOXSIZE4#defineRTLWIPTCPTHREADSTACKSIZE1024默认的IP地址,网关地址,子网掩码也可以在rtcong.
h头文件中定义(如果要使用DHCP方式分配,则需要定义RTUSINGDHCP宏)/*目标板IP地址*/#defineRTLWIPIPADDR0192#defineRTLWIPIPADDR1168#defineRTLWIPIPADDR21#defineRTLWIPIPADDR330/*网关地址*/#defineRTLWIPGWADDR0192#defineRTLWIPGWADDR1168#defineRTLWIPGWADDR21#defineRTLWIPGWADDR31150Chapter14.
TCP/IP协议栈RT-ThreadProgrammingGuide,Release0.
3.
0/*子网掩码*/#defineRTLWIPMSKADDR0255#defineRTLWIPMSKADDR1255#defineRTLWIPMSKADDR2255#defineRTLWIPMSKADDR3014.
2缓冲区函数14.
2.
1netbufnew()原型声明structnetbuf*netbufnew(void)分配一个netbuf结构,该函数并不会分配实际的缓冲区空间,只创建顶层的结构.
netbuf用完后,必须使用netbufdelete()回收.
14.
2.
2netbufdelete()原型声明voidnetbufdelete(structnetbuf*)回收先前通过调用netbufnew()函数创建的netbuf结构,任何通过netbufalloc()函数分配给netbuf的缓冲区内存同样也会被回收.
例子:这个例子显示了使用netbufs的基本代码结构voidlwthread(void*parameter){structnetbuf*buf;buf=netbufnew();/*建立一个新的netbuf*/netbufalloc(buf,100);/*为这个buf分配100bytes*//*对netbuf数据做一些处理*/netbufdelete(buf);/*删除buf*/}14.
2.
3netbufalloc()原型声明void*netbufalloc(structnetbuf*buf,intsize)14.
2.
缓冲区函数151RT-ThreadProgrammingGuide,Release0.
3.
0为netbufbuf分配指定字节(bytes)大小的缓冲区内存.
这个函数返回一个指针指向已分配的内存,任何先前已分配给netbufbuf的内存会被回收.
刚分配的内存可以在以后使用netbuffree()函数回收.
因为协议头应该要先于数据被发送,所以这个函数即为协议头也为实际的数据分配内存.
14.
2.
4netbuffree()原型声明intnetbuffree(structnetbuf*buf)回收与netbufbuf相关联的缓冲区.
如果还没有为netbuf分配缓冲区,这个函数不做任何事情.
14.
2.
5netbufref()原型声明intnetbufref(structnetbuf*buf,void*data,intsize)使数据指针指向的外部存储区与netbufbuf关联起来.
外部存储区大小由size参数给出.
任何先前已分配给netbuf的存储区会被回收.
使用netbufalloc()函数为netbuf分配存储区与先分配存储区——比如使用malloc()函数——然后再使用netbufref()函数引用这块存储区相比,不同的是前者还要为协议头分配空间这样会使处理和发送缓冲区速度更快.

下面这个例子显示了netbufref()函数的简单用法voidlwthread(void*parameter){structnetbuf*buf;charstring[]="Astring";buf=netbufnew();netbufref(buf,string,sizeof(string));/*引用这个字符串*/netbufdelete(buf);}14.
2.
6netbuflen()原型声明intnetbuflen(structnetbuf*buf)返回netbufbuf中的数据长度,即使netbuf被分割为数据片断.
对数据片断状的netbuf来说,通过调用这个函数取得的长度值并不等于netbuf中的第一个数据片断的长度,而152Chapter14.
TCP/IP协议栈RT-ThreadProgrammingGuide,Release0.
3.
0是所有分片的长度.
14.
2.
7netbufdata()原型声明intnetbufdata(structnetbuf*buf,void**data,int*len)这个函数用于获取一个指向netbufbuf中的数据的指针,同时还取得数据块的长度.
参数data和len为结果参数,参数data用于接收指向数据的指针值,len指针接收数据块长度.
如果netbuf中的数据被分割为片断,则函数给出的指针指向netbuf中的第一个数据片断.
应用程序必须使用片断处理函数netbufrst()和netbufnext()来取得netbuf中的完整数据.
关于如何使用netbufdata()函数请参阅下面netbufnext()函数说明中给出的例子.
14.
2.
8netbufnext()原型声明intnetbufnext(structnetbuf*buf)函数修改netbuf中数据片断的指针以便指向netbuf中的下一个数据片断.
返回值为0表明netbuf中还有数据片断存在,大于0表明指针现在正指向最后一个数据片断,小于0表明已经到了最后一个数据片断的后面的位置,netbuf中已经没有数据片断了.
例子:这个例子显示了如何使用netbufnext()函数.
我们假定这是一个函数的中间部分,并且其中的buf就是一个netbuf类型的变量.
voidlwthread(void*parameter){do{char*data;intlen;netbufdata(buf,&data,&len);/*获取一个指针指向数据片段中的数据*/dosomething(data,len);/*对这些数据进行一些处理*/}while(netbufnext(buf)>=0);}14.
2.
缓冲区函数153RT-ThreadProgrammingGuide,Release0.
3.
014.
2.
9netbufrst()原型声明intnetbuffirst(structnetbuf*buf)复位netbufbuf中的数据片断指针,使其指向netbuf中的第一个数据片断.
14.
2.
10netbufcopy()原型声明voidnetbufcopy(structnetbuf*buf,void*data,intlen)将netbufbuf中的所有数据复制到data指针指向的存储区,即使netbufbuf中的数据被分割为片断.
len参数指定要复制数据的最大值.
下面的例子显示了netbufcopy()函数的简单用法.
这里,协议栈分配200个字节的存储区用以保存数据,即使netbufbuf中的数据大于200个字节,也只会复制200个字节的数据.
voidexamplefunction(structnetbuf*buf){chardata[200];netbufcopy(buf,data,200);/*对这些数据进行一些处理*/}14.
2.
11netbufchain()原型声明voidnetbufchain(structnetbuf*head,structnetbuf*tail)将两个netbufs的首尾链接在一起,以使首部netbuf的最后一个数据片断成为尾部netbuf的第一个数据片断.
函数被调用后,尾部netbuf会被回收,不能再使用.
14.
2.
12netbuffromaddr()原型声明structipaddr*netbuffromaddr(structnetbuf*buf)返回接收到的netbufbuf的主机IP地址.
如果指定的netbuf还没有从网络收到,函数返回一个未定义值.
netbuffromport()函数用于取得远程主机的端口号.
154Chapter14.
TCP/IP协议栈RT-ThreadProgrammingGuide,Release0.
3.
014.
2.
13netbuffromport()原型声明unsignedshortnetbuffromport(structnetbuf*buf)返回接收到的netbufbuf的主机端口号.
如果指定的netbuf还没有从网络收到,函数返回一个不确定值.
netbuffromaddr()函数用于取得远程主机的IP地址.
14.
3网络连接函数14.
3.
1netconnnew()原型声明structnetconn*netconnnew(enumnetconntypetype)建立一个新的连接数据结构,根据是要建立TCP还是UDP连接来选择参数值是NETCONNTCP还是NETCONNUCP.
调用这个函数并不会建立连接并且没有数据被发送到网络中.
14.
3.
2netconndelete()原型声明voidnetconndelete(structnetconn*conn)删除连接数据结构conn,如果连接已经打开,调用这个函数将会关闭这个连接.

14.
3.
3netconntype()原型声明enumnetconntypenetconntype(structnetconn*conn)返回指定的连接conn的连接类型.
返回的类型值就是前面netconnnew()函数说明中提到的NETCONNTCP或者NETCONNUDP.
14.
3.
4netconnpeer()原型声明intnetconnpeer(structnetconn*conn,structipaddr*addr,unsignedshort*port)这个函数用于获取连接的远程终端的IP地址和端口号.
addr和port为结果参数,它们的值由函数设置.
如果指定的连接conn并没有连接任何远程主机,则获得的结果值并不确定.

14.
3.
网络连接函数155RT-ThreadProgrammingGuide,Release0.
3.
014.
3.
5netconnaddr()原型声明intnetconnaddr(structnetconn*conn,structipaddr**addr,unsignedshort*port)这个函数用于获取由conn指定的连接的本地IP地址和端口号.
14.
3.
6netconnbind()原型声明intnetconnbind(structnetconn*conn,structipaddr*addr,unsignedshortport)为参数conn指定的连接绑定本地IP地址和TCP或UDP端口号.
如果addr参数为NULL则本地IP地址由网络系统确定.
14.
3.
7netconnconnect()原型声明intnetconnconnect(structnetconn*conn,structipaddr*addr,unsignedshortport)对UDP连接,该函数通过addr和port参数设定发送的UDP消息要到达的远程主机的IP地址和端口号.
对TCP,netconnconnect()函数打开与指定远程主机的连接.
14.
3.
8netconnlisten()原型声明intnetconnlisten(structnetconn*conn)使参数conn指定的连接进入TCP监听(TCPLISTEN)状态.
14.
3.
9netconnaccept()原型声明structnetconn*netconnaccept(structnetconn*conn)阻塞进程直至从远程主机发出的连接请求到达参数conn指定的连接.
这个连接必须处于监听(LISTEN)状态,因此在调用netconnaccept()函数之前必须调用netconnlisten()函数.
与远程主机的连接建立后,函数返回新连接的结构.
例子:这个例子显示了如何在2000端口上打开一个TCP服务器.
voidlwthread(void*parameter){structnetconn*conn,*newconn;/*建立一个连接结构*/156Chapter14.
TCP/IP协议栈RT-ThreadProgrammingGuide,Release0.
3.
0conn=netconnnew(NETCONNTCP);/*将连接绑定到一个本地任意IP地址的2000端口上*/netconnbind(conn,NULL,2000);/*告诉这个连接监听进入的连接请求*/netconnlisten(conn);/*阻塞直至得到一个进入的连接*/newconn=netconnaccept(conn);/*处理这个连接*/processconnection(newconn);/*删除连接*/netconndelete(newconn);netconndelete(conn);}14.
3.
10netconnrecv()原型声明structnetbuf*netconnrecv(structnetconn*conn)阻塞进程,等待数据到达参数conn指定的连接.
如果连接已经被远程主机关闭,则返回NULL,其它情况,函数返回一个包含着接收到的数据的netbuf.
例子:这是一个小例子,显示了netconnrecv()函数的假定用法.
我们假定在调用这个例子函数examplefunction()之前连接已经建立.
voidexamplefunction(structnetconn*conn){structnetbuf*buf;/*接收数据直到其它主机关闭连接*/while((buf=netconnrecv(conn))!
=NULL){/*对这些数据进行一些处理*/dosomething(buf);}/*连接现在已经被其它终端关闭,因此也关闭我们自己的连接*/netconnclose(conn);}14.
3.
网络连接函数157RT-ThreadProgrammingGuide,Release0.
3.
014.
3.
11netconnwrite()原型声明intnetconnwrite(structnetconn*conn,void*data,intlen,unsignedintflags)这个函数只用于TCP连接.
它把data指针指向的数据放在属于conn连接的输出队列.

Len参数指定数据的长度,这里对数据长度没有任何限制.
这个函数不需要应用程序明确的分配缓冲区(buers),因为这由协议栈来负责.
ags参数有两种可能的状态,如下所示:#defineNETCONNNOCOPY0x00#defineNETCONNCOPY0x01当ags值为NETCONNCOPY时,data指针指向的数据被复制到为这些数据分配的内部缓冲区.
这就允许这些数据在函数调用后可以直接修改,但是这会在执行时间和内存使用率方面降低效率.
如果ags值为NETCONNNOCOPY,数据不会被复制而是直接使用data指针来引用.
这些数据在函数调用后不能被修改,因为这些数据可能会被放在当前指定连接的重发队列,并且会在里面逗留一段不确定的时间.
当要发送的数据在ROM中因而数据不可变时这很有用.
如果需要更多的控制数据的修改,则可以联合使用复制和不复制数据,如下面的例子所示.
这个例子显示了netconnwrite()函数的基本用法.
这里假定程序里的data变量在后面编辑修改,因此它被复制到内部缓冲区,方法是前文所讲的在调用netconnwrite()函数时将ags参数值设为NETCONNCOPY.
text变量包含了一个不能被编辑修改的字符串,因此它采用指针引用的方式以代替复制.
voidlwthread(void*parameter){structnetconn*conn;chardata[10];chartext[]="Statictext";inti;/*设置连接conn*//*构造一些测试数据*/for(i=0;i\Atestpage\\Thisisasmalltestpage.
\\";conststaticcharhttphtmlhdr[]="Content-type:text/html\r\n\r\n";/*这个函数处理进入的连接*/staticvoidprocessconnection(structnetconn*conn){structnetbuf*inbuf;char*rq;intlen;/*从这个连接读取数据到inbuf,我们假定在这个netbuf中包含完整的请求*/inbuf=netconnrecv(conn);/*获取指向netbuf中第一个数据片断的指针,在这个数据片段里我们希望包含这个请求*/netbufdata(inbuf,&rq,&len);/*检查这个请求是不是HTTP"GET/\r\n"*/if(rq[0]=='G'&&rq[1]=='E'&&rq[2]=='T'&&rq[3]rq[4]rq[5]=='\r'&&rq[6]=='\n'){/*发送头部数据*/netconnwrite(conn,httphtmlhdr,sizeof(httphtmlhdr),NETCONNNOCOPY);/*发送实际的web页面*/netconnwrite(conn,indexdata,sizeof(indexdata),NETCONNNOCOPY);/*关闭连接*/netconnclose(conn);}14.
3.
网络连接函数161RT-ThreadProgrammingGuide,Release0.
3.
0}/*线程入口*/voidlwthread(void*paramter){structnetconn*conn,*newconn;/*建立一个新的TCP连接句柄*/conn=netconnnew(NETCONNTCP);/*将连接绑定在任意的本地IP地址的80端口上*/netconnbind(conn,NULL,80);/*连接进入监听状态*/netconnlisten(conn);/*循环处理*/while(1){/*接受新的连接请求*/newconn=netconnaccept(conn);/*处理进入的连接*/processconnection(newconn);/*删除连接句柄*/netconndelete(newconn);}return0;}14.
4BSDSocket库BSDSocket是在Unix下进行网络通信编程的API接口之一,也是网络编程的事实标准.

LwIP提供了一个轻型BSDSocketAPI的实现,为大量已有的网络应用程序提供了兼容的接口.
LwIP的socket接口实现都在函数名前加有lwip前缀,同时在头文件中把它采用宏定义的方式定义成标准的BSDSocketAPI接口.
14.
4.
1分配一个socketintlwipsocket(intdomain,inttype,intprotocol);intsocket(intdomain,inttype,intprotocol);162Chapter14.
TCP/IP协议栈RT-ThreadProgrammingGuide,Release0.
3.
0应用程序在使用socket前,首先必须拥有一个socket,调用socket()函数可以为应用程序分配一个socket.
socket()函数的参数用于指定所需要的socket的类型.
domain参数可以支持如下类型:#defineAFUNSPEC0#defineAFINET2#definePFINETAFINET#definePFUNSPECAFUNSPECtype参数可以支持如下类型:/*Socketprotocoltypes(TCP/UDP/RAW)*/#defineSOCKSTREAM1#defineSOCKDGRAM2#defineSOCKRAW3protocol参数可以支持如下类型:#defineIPPROTOIP0#defineIPPROTOTCP6#defineIPPROTOUDP17#defineIPPROTOUDPLITE13614.
4.
2绑定socket到地址intlwipbind(ints,structsockaddr*name,socklentnamelen);intbind(ints,structsockaddr*name,socklentnamelen);bind()调用为socket绑定一个本地地址.
本地地址由name指定,其长度由namelen指定.

sockaddr结构定义如下:structsockaddr{u8tsalen;u8tsafamily;charsadata[14];};14.
4.
3建立到远端socket的连接intlwipconnect(ints,conststructsockaddr*name,socklentnamelen);intconnect(ints,conststructsockaddr*name,socklentnamelen);14.
4.
BSDSocket库163RT-ThreadProgrammingGuide,Release0.
3.
0调用connect()函数连接socket到一个远端地址.
调用时需要指定一个远端连接的地址.

14.
4.
4接收一个连接intlwipaccept(ints,structsockaddr*addr,socklent*addrlen);intaccept(ints,structsockaddr*addr,socklent*addrlen);accept()函数等待一个连接请求到达指定的TCPsocket,而这个socket先前已经通过调用listen()函数进入监听状态.
对accept()函数的调用会根据socket选项情况阻塞线程直至与远程主机建立连接或非阻塞方式返回.
这个addr参数是一个结果参数,它的值由accept()函数设置,这个值其实就是远程主机的地址.
当新的连接已经建立,LwIP将把远程主机的IP地址和端口号保存到addr参数后,分配一个新的socket标识符,然后accept函数返回这个标识符.

14.
4.
5监听连接intlwiplisten(ints,intbacklog);intlisten(ints,intbacklog);调用listen()函数相当于调用LwIPAPI函数netconnlisten(),且只能用于TCP连接.
与BSDSocketAPI中listen函数不同的是,BSDSocket允许应用程序指定等待连接队列的大小(backlog参数指定).
LwIP协议栈只支持范围在0-255内值的backlog参数.
14.
4.
6发送数据intlwipsend(ints,constvoid*dataptr,intsize,unsignedintflags);intlwipsendto(ints,constvoid*dataptr,intsize,unsignedintflags,structsockaddr*to,socklenttolen);intlwipwrite(ints,constvoid*dataptr,intsize);intsend(ints,constvoid*dataptr,intsize,unsignedintflags);intsendto(ints,constvoid*dataptr,intsize,unsignedintflags,structsockaddr*to,socklenttolen);intwrite(ints,constvoid*dataptr,intsize);send()调用用于在参数s指定的已连接的数据报或流socket上发送输出数据,其中参数s为已连接的socket描述符;dataptr指向存有发送数据的缓冲区的指针,其长度由size指定.
ags支持的参数包括:164Chapter14.
TCP/IP协议栈RT-ThreadProgrammingGuide,Release0.
3.
0/*Flagswecanusewithsendandrecv.
*/#defineMSGPEEK0x01/*查看当前数据*/#defineMSGWAITALL0x02/*等到所有的信息到达时才返回,不支持*/#defineMSGOOB0x04/*带外数据,不支持*/#defineMSGDONTWAIT0x08/*非阻塞模式*/#defineMSGMORE0x10/*发送更多*/sendto()调用用于将数据由指定的socket传给远方主机.
参数to用来指定要传送到的网络地址,结构sockaddr请参考bind().
参数tolen为sockaddr的结果长度.
write()调用用于往参数s指定的已连接的socket中写入数据,其中参数s为已连接的本地socket描述符;dataptr指向存有发送数据的缓冲区的指针,其长度由size指定.
14.
4.
7接收数据recv()调用用于在参数s指定的已连接的数据报或流socket上接收输入数据.
其中参数s为已连接的socket描述符;mem指向用于保存接收数据的的缓冲区指针,其能够存放的最大长度由len指定.
ags支持的参数包括:/*Flagswecanusewithsendandrecv.
*/#defineMSGPEEK0x01/*查看当前数据*/#defineMSGWAITALL0x02/*等到所有的信息到达时才返回,不支持*/#defineMSGOOB0x04/*带外数据,不支持*/#defineMSGDONTWAIT0x08/*非阻塞模式*/#defineMSGMORE0x10/*发送更多*/recvfrom()调用用于在指定的socket上接收远方主机传递过来的数据.
参数from用来指定欲传送的网络地址,结构sockaddr请参考bind()函数.
参数fromlen为sockaddr的结构长度.

read()调用用于在参数s指定的已连接的socket中读取数据,其中参数s为已连接的本地socket描述符;mem指向用于保存接收数据的的缓冲区指针,其能够存放的最大长度由len指定.
14.
4.
8输入/输出多路复用intlwipselect(intmaxfdp1,fdset*readset,fdset*writeset,fdset*exceptset,structtimeval*timeout);intselect(intmaxfdp1,fdset*readset,fdset*writeset,fdset*exceptset,structtimeval*timeout);select()调用用来检测一个或多个socket的状态.
对每一个socket来说,这个调用可以请求读、写或错误状态方面的信息.
请求给定状态的socket集合由一个fdset结构指示.
在返回时,此结构被更新,以反映那些满足特定条件的socket的子集,同时select()调用返回满足条件的socket的数目.
14.
4.
BSDSocket库165RT-ThreadProgrammingGuide,Release0.
3.
0Note:select函数在RT-Thread中不能用于文件描述符操作.
14.
4.
9关闭socketintlwipclose(ints);intclose(ints);close()关闭sockets,并释放分配给该socket的资源;如果s涉及一个打开的TCP连接,则该连接被释放.
14.
4.
10TCP服务器例子#include#include#defineMAXSERV5/*最大chargen服务数目*/#defineCHARGENTHREADNAME"chargen"/*chargen线程名称*/#defineCHARGENPRIORITY200/*线程优先级*/#defineCHARGENTHREADSTACKSIZE1024/*一个chargen客户端控制块结构*/structcharcb{structcharcb*next;intsocket;structsockaddrincliaddr;socklentclilen;charnextchar;};/*放置chargen客户端的链表*/staticstructcharcb*charcblist=0;staticintdoread(structcharcb*pcharcb);staticvoidclosechargen(structcharcb*pcharcb);/*chargen线程入口*/staticvoidchargenthreadentry(void*arg){intlistenfd;structsockaddrinchargensaddr;fdsetreadset;fdsetwriteset;inti,maxfdp1;structcharcb*pcharcb;166Chapter14.
TCP/IP协议栈RT-ThreadProgrammingGuide,Release0.
3.
0/*创建一个TCP的套接字*/listenfd=lwipsocket(AFINET,SOCKSTREAM,IPPROTOTCP);LWIPASSERT("chargenthread():Socketcreatefailed.
",listenfd>=0);/*对服务器地址做初始化,端口19*/memset(&chargensaddr,0,sizeof(chargensaddr));chargensaddr.
sinfamily=AFINET;chargensaddr.
sinaddr.
saddr=htonl(INADDRANY);chargensaddr.
sinport=htons(19);/*绑定listenfd套接字到服务器地址*/if(lwipbind(listenfd,(structsockaddr*)&chargensaddr,sizeof(chargensaddr))==-1)LWIPASSERT("chargenthread():Socketbindfailed.
",0);/*让listenfd进入监听模式*/if(lwiplisten(listenfd,MAXSERV)==-1)LWIPASSERT("chargenthread():Listenfailed.
",0);/*用于处理数据的死循环*/for(;;){maxfdp1=listenfd+1;/*Determinewhatsocketsneedtobeinreadset*/FDZERO(&readset);FDZERO(&writeset);FDSET(listenfd,&readset);for(pcharcb=charcblist;pcharcb;pcharcb=pcharcb->next){if(maxfdp1socket+1)maxfdp1=pcharcb->socket+1;FDSET(pcharcb->socket,&readset);FDSET(pcharcb->socket,&writeset);}/*等待数据传送或一个新的连接*/i=lwipselect(maxfdp1,&readset,&writeset,0,0);/*未有新的就绪,继续循环*/if(i==0)continue;/*否则至少有个一个就绪*/if(FDISSET(listenfd,&readset)){/*有新的连接请求,创建一个控制块*/14.
4.
BSDSocket库167RT-ThreadProgrammingGuide,Release0.
3.
0pcharcb=(structcharcb*)rtcalloc(1,sizeof(structcharcb));if(pcharcb){/*接受新的连接,此处应是马上返回并会阻塞*/pcharcb->socket=lwipaccept(listenfd,(structsockaddr*)&pcharcb->cliaddr,&pcharcb->clilen);if(pcharcb->socketnext=charcblist;charcblist=pcharcb;pcharcb->nextchar=0x21;}}else{/*分配控制块失败,立马关闭连接*/intsock;structsockaddrcliaddr;socklentclilen;sock=lwipaccept(listenfd,&cliaddr,&clilen);if(sock>=0)lwipclose(sock);}}/*遍历链表并处理数据*/for(pcharcb=charcblist;pcharcb;pcharcb=pcharcb->next){if(FDISSET(pcharcb->socket,&readset)){/*读就绪,执行数据读取*/if(doread(pcharcb)socket,&writeset)){/*写就绪*/charline[80];charsetchar=pcharcb->nextchar;/*构造发送数据*/for(i=0;isocket,line,strlen(line))nextchar==0x7f)pcharcb->nextchar=0x21;}}}}/*关闭一个chargen连接*/staticvoidclosechargen(structcharcb*pcharcb){structcharcb*psearchcharcb;/*关闭套接字*/lwipclose(pcharcb->socket);/*从charcb列表中把pcharcb删除*/if(charcblist==pcharcb)charcblist=pcharcb->next;/*如果是头节点,直接删除*/else/*查找链表以找到它的前一个*/for(psearchcharcb=charcblist;psearchcharcb;psearchcharcb=psearchcharcb->n{if(psearchcharcb->next==pcharcb){/*把节点从链表中删除*/psearchcharcb->next=pcharcb->next;break;}}/*释放pcharcb占用的资源*/rtfree(pcharcb);14.
4.
BSDSocket库169RT-ThreadProgrammingGuide,Release0.
3.
0}/*读操作*/staticintdoread(structcharcb*pcharcb){charbuffer[80];intreadcount;/*从socket中读取最大80个字节的数据*/readcount=lwipread(pcharcb->socket,&buffer,80);if(readcountR14其中,mode为以下几种模式之一:usr、q、irq、svc、abt、und.
由于处理器的每种运行模式均有自己独立的物理寄存器R13,在用户应用程序的初始化部分都需要初始化每种模式下的R13,使其指向该运行模式的栈空间,这样,当程序的运行进入异常模式时,可以将需要保护的寄存器放入R13所指向的堆栈,而当程序从异常模式返回时,则从对应的堆栈中恢复,采用这种方式可以保证异常发生后程序的正常执行.

R14也称作子程序连接寄存器(SubroutineLinkRegister)或连接寄存器LR.
当执行**BL子程序调用指令**时,R14中得到R15(程序计数器PC)的备份.
其他情况下,R14用作通用寄存器.
与之类似,当发生中断或异常时,对应的分组寄存器R14svc、R14irq、R14q、R14abt和R14und用来保存R15的返回值.
寄存器R14常用在如下的情况:在每一种运行模式下,都可用R14保存子程序的返回地址,当用BL或BLX指令调用子程序时,将PC的当前值拷贝给R14.
执行完子程序后,又将R14的值拷贝回PC,即可完成子程序的调用返回.
程序计数器PC(R15)用作程序计数器(PC).
在ARM状态下,位[1:0]为0,位[31:2]用于保存PC;在Thumb状态下,位[0]为0,位[31:1]用于保存PC;在ARM状态下,PC的0和1位是0,在Thumb状态下,PC的0位是0.

CPSR(CurrentProgramStatusRegister,当前程序状态寄存器),CPSR可在任何运行模式178Chapter16.
ARM基本知识RT-ThreadProgrammingGuide,Release0.
3.
0下被访问,它包括条件标志位、中断禁止位、当前处理器模式标志位,以及其他一些相关的控制和状态位.
每一种运行模式下又都有一个专用的物理状态寄存器,称为SPSR(SavedProgramStatusRegister,备份的程序状态寄存器),当异常发生时,SPSR用于保存CPSR的当前值,从异常退出时则可由SPSR来恢复CPSR.
16.
4ARM的异常当正常的程序执行流程发生暂时的停止时,称之为异常,例如处理一个外部的中断请求.

在处理异常之前,当前处理器的状态必须保留,这样当异常处理完成之后,当前程序可以继续执行.
处理器允许多个异常同时发生,它们将会按固定的优先级进行处理.

当一个异常出现以后,ARM微处理器会执行以下几步操作:将下一条指令的地址存入相应连接寄存器LR,以便程序在处理异常返回时能从正确的位置重新开始执行.
若异常是从ARM状态进入,LR寄存器中保存的是下一条指16.
4.
ARM的异常179RT-ThreadProgrammingGuide,Release0.
3.
0令的地址(当前PC+4或PC+8,与异常的类型有关);若异常是从Thumb状态进入,则在LR寄存器中保存当前PC的偏移量,这样,异常处理程序就不需要确定异常是从何种状态进入的.
例如:在软件中断异常SWI,指令MOVPC,R14svc总是返回到下一条指令,不管SWI是在ARM状态执行,还是在Thumb状态执行.
将CPSR复制到相应的SPSR中.
根据异常类型,强制设置CPSR的运行模式位.
强制PC从相关的异常向量地址取下一条指令执行,从而跳转到相应的异常处理程序处.
还可以设置中断禁止位,以禁止中断发生.
如果异常发生时,处理器处于Thumb状态,则当异常向量地址加载入PC时,处理器自动切换到ARM状态.
ARM微处理器对异常的响应过程用伪码可以描述为:R14=ReturnLinkSPSR=CPSRCPSR[4:0]=ExceptionModeNumberCPSR[5]=0;当运行于ARM工作状态时If==ResetorFIQthen;当响应FIQ异常时,禁止新的FIQ异常CPSR[6]=1CPSR[7]=1PC=ExceptionVectorAddress异常处理完毕之后,ARM微处理器会执行以下几步操作从异常返回:将连接寄存器LR的值减去相应的偏移量后送到PC中.
将SPSR复制回CPSR中.
若在进入异常处理时设置了中断禁止位,要在此清除.
可以认为应用程序总是从复位异常处理程序开始执行的,因此复位异常处理程序不需要返回.
当系统运行时,异常可能会随时发生,为保证在ARM处理器发生异常时不至于处于未知状态,在应用程序的设计中,首先要进行异常处理,采用的方式是在异常向量表中的特定位置放置一条跳转指令,跳转到异常处理程序,当ARM处理器发生异常时,程序计数器PC会被强制设置为对应的异常向量,从而跳转到异常处理程序,当异常处理完成以后,返回到主程序继续执行.
180Chapter16.
ARM基本知识RT-ThreadProgrammingGuide,Release0.
3.
016.
5ARM的IRQ中断处理RT-Thread的ARM体系结构移植只涉及到IRQ中断,所以本节只讨论IRQ中断模式,主要包括ARM微处理器在硬件上是如何响应中断及如何从中断中返回的.
当中断产生时,ARM7TDMI将执行的操作1.
把当前CPSR寄存器的内容拷贝到相应的SPSR寄存器.
这样当前的工作模式、中断屏蔽位和状态标志被保存下来.
2.
转入相应的模式,并关闭IRQ中断.
3.
把PC值减4后,存入相应的LR寄存器.
4.
将PC寄存器指向IRQ中断向量位置.
由中断返回时,ARM7TDMI将完成的操作1.
将备份程序状态寄存器的内容拷贝到当前程序状态寄存器,恢复中断前的状态.

2.
清除相应禁止中断位(如果已设置的话).
3.
把连接寄存器的值拷贝到程序计数器,继续运行原程序.
16.
6AT91SAM7S64概述AT91SAM7S64是ATMEL32位ARMRISC处理器小引脚数Flash微处理器家族的一员.
它包含一个ARM7TDMI高性能RISC核心,64KB片上高速ash(512页,每页包含128字节),16KB片内高速静态RAM,2个同步串口(USART),USB2.
0全速Device设备,3个16bit定时器/计数器通道等.
16.
5.
ARM的IRQ中断处理181RT-ThreadProgrammingGuide,Release0.
3.
0182Chapter16.
ARM基本知识CHAPTERSEVENTEENGNUGCC移植GNUGCC是GNU的多平台编译器,也是开源项目中的首选编译环境,支持ARM各个版本的指令集,MIPS,x86等多个体系结构,也为一些知名操作系统作为官方编译器(例如主流的几种BSD操作系统,Linux操作系统,vxWorks实时操作系统等),所以作为开源项目的RT-Thread首选编译器是GNUGCC,甚至在一些地方会对GCC做专门的优化.
以下就以AT91SAM7S核心板为例,描述如何进行RT-Thread的移植.
Figure17.
1:AT91SAM7S64核心板(由icdev.
net提供)17.
1CPU相关移植和通用平台中的GCC不同,编译操作系统会生成单独的目标文件,一些基本的算术操作例如除法,必须在链接的时候选择使用gcc库(libgcc.
a),还是自身的实现.
RT-Thread推荐选择后者:自己实现一些基本的算术操作,因为这样能够让生成的目标文件体积更小一些.
这些基本的算术操作统一放在各自体系结构目录下的common目录.
另外ARM体183RT-ThreadProgrammingGuide,Release0.
3.
0系结构中ARM模式下的一些过程调用也是标准的,所以也放置了一些栈回溯的代码例程(在Thumb模式下这部分代码将不可用).
kernel/libcpu/arm/common目录下的文件目前common目录下这些文件都已经存在,其他的ARM芯片移植基本上不需要重新实现或修改.
17.
1.
1上下文切换代码任务切换代码是移植一个操作系统首先要考虑的,因为它关系到线程间的正常运行,是核心中的核心.
在目录中添加contextgcc.
S代码,代码如下/**voidrthwcontextswitch(rtuint32from,rtuint32to)*上下文切换函数,*r0寄存器指向保存当前线程栈位置*r1寄存器指向切换到线程的栈位置*/.
globlrthwcontextswitchrthwcontextswitch:stmfdsp!
,{lr}/*把LR寄存器压入栈,也就是从这个函数返回后的下一执行处*/stmfdsp!
,{r0-r12,lr}/*把R0–R12以及LR压入栈*/mrsr4,cpsr/*读取CPSR寄存器到R4寄存器*/stmfdsp!
,{r4}/*把R4寄存器压栈(即上一指令取出的CPSR寄存器)*/mrsr4,spsr/*读取SPSR寄存器到R4寄存器*/stmfdsp!
,{r4}/*把R4寄存器压栈(即SPSR寄存器)*/strsp,[r0]/*把栈指针更新到TCB的sp,是由R0传入此函数*//*到这里换出线程的上下文都保存在栈中*/ldrsp,[r1]/*载入切换到线程的TCB的sp,即此线程换出时保存的sp寄存器*//*从切换到线程的栈中恢复上下文,次序和保存的时候刚好相反*/ldmfdsp!
,{r4}/*出栈到R4寄存器(保存了SPSR寄存器)*/msrspsrcxsf,r4/*恢复SPSR寄存器*/ldmfdsp!
,{r4}/*出栈到R4寄存器(保存了CPSR寄存器)*/msrcpsrcxsf,r4/*恢复CPSR寄存器*/ldmfdsp!
,{r0-r12,lr,pc}/*对R0–R12及LR、PC进行恢复*//**voidrthwcontextswitchto(rtuint32to)/**此函数只在系统进行第一次发生任务切换时使用,因为是从没有线程的状态进行切换184Chapter17.
GNUGCC移植RT-ThreadProgrammingGuide,Release0.
3.
0*实现上,刚好是rthwcontextswitch的下半截*/.
globlrthwcontextswitchtorthwcontextswitchto:ldrsp,[r0]/*获得切换到线程的SP指针*/ldmfdsp!
,{r4}/*出栈R4寄存器(保存了SPSR寄存器值)*/msrspsrcxsf,r4/*恢复SPSR寄存器*/ldmfdsp!
,{r4}/*出栈R4寄存器(保存了CPSR寄存器值)*/msrcpsrcxsf,r4/*恢复CPSR寄存器*/ldmfdsp!
,{r0-r12,lr,pc}/*恢复R0–R12,LR及PC寄存器*/在RT-Thread中,如果中断服务例程触发了上下文切换(即执行了一次rtschedule函数试图进行一次调度),它会设置标志rtthreadswitchinterruptag为真.
而后在所有中断服务例程都处理完毕向线程返回的时候,如果rtthreadswitchinterruptag为真,那么中断结束后就必须进行线程的上下文切换.
这部分上下文切换代码和上面会有些不同,这部分在下一个文件中叙述,但设置rtthreadswitchinterruptag标志的代码以及保存切换出和切换到线程的函数在这里给出,如下代码所示.
/**voidrthwcontextswitchinterrupt(rtuint32from,rtuint32to)/**此函数会在调度器中调用,在调度器做上下文切换前会判断是否处于中断服务模式中,如果*是则调用rthwcontextswitchinterrupt函数(设置中断中任务切换标志)*否则调用rthwcontextswitch函数(进行真正的线程上线文切换)*/rthwcontextswitchinterrupt:ldrr2,=rtthreadswitchinterrputflagldrr3,[r2];载入中断中切换标致地址cmpr3,#1;等于1beqreswitch;如果等于1,跳转到reswitchmovr3,#1;设置中断中切换标志位1strr3,[r2];保存到标志变量中ldrr2,=rtinterruptfromthreadstrr0,[r2];保存切换出线程栈指针reswitch:ldrr2,=rtinterrupttothreadstrr1,[r2];保存切换到线程栈指针bxlr上面的代码等价于C代码:;/*;*voidrthwcontextswitchinterrupt(rtuint32from,rtuint32to);;*/rthwcontextswitchinterrupt(rtuint32from,rtuint32to)17.
1.
CPU相关移植185RT-ThreadProgrammingGuide,Release0.
3.
0{if(rtthreadswitchinterrputflag==1)rtinterruptfromthread=from;elsertthreadswitchinterrputflag=1;rtinterrupttothread=to;}除了上下文切换外,也在这个文件中实现了中断(IRQ&FIQ)的关闭和恢复(操作CPSR寄存器屏蔽/使能所有中断).
/**rtbasetrthwinterruptdisable()*关闭IRQ和FIQ中断,返回关闭中断前的状态*/.
globlrthwinterruptdisablerthwinterruptdisable:mrsr0,cpsr/*保存CPSR寄存器的值到R0寄存器*/orrr1,r0,#0xc0/*R0寄存器的值或上0xc0(2、3位置1),结果放到r1中*/msrcpsrc,r1/*把R1的值存放到CPSR寄存器中*/movpc,lr/*返回调用rthwinterruptdisable函数处,返回值在R0中*//**voidrthwinterruptenable(rtbasetlevel)*恢复中断状态,中断状态在R0寄存器中*/.
globlrthwinterruptenablerthwinterruptenable:msrcpsr,r0/*把R0的值保存到CPSR中*/movpc,lr/*函数返回*/17.
1.
2系统启动代码接下来是系统启动的代码.
因为ARM体系结构异常的触发总是置于0地址的(或者说异常向量表总是置于0地址),所以操作系统要捕获异常(最重要的是中断异常)就必须放置上自己的向量表.
此外,由于系统刚上电可能一些地方也需要进行初始化(RT-Thread推荐和板子相关的初始化最好放在bsp目录中),对ARM体系结构,另一个最重要的地方就是(各种模式下)栈的设置.
下面的代码(文件startgcc.
S)列出了系统启动部分的汇编代码:/*AT91SAM7S64内部Memory基地址*/.
equFLASHBASE,0x00100000.
equRAMBASE,0x00200000186Chapter17.
GNUGCC移植RT-ThreadProgrammingGuide,Release0.
3.
0/*栈顶及各个栈大小的配置*/.
equTOPSTACK,0x00204000.
equUNDSTACKSIZE,0x00000000.
equSVCSTACKSIZE,0x00000400.
equABTSTACKSIZE,0x00000000.
equFIQSTACKSIZE,0x00000100.
equIRQSTACKSIZE,0x00000100.
equUSRSTACKSIZE,0x00000004/*ARM模式的定义*/.
equMODEUSR,0x10.
equMODEFIQ,0x11.
equMODEIRQ,0x12.
equMODESVC,0x13.
equMODEABT,0x17.
equMODEUND,0x1B.
equMODESYS,0x1F.
equIBIT,0x80/*IRQ位*/.
equFBIT,0x40/*FIQ位*/.
section.
init,"ax".
code32.
align0.
globlstartstart:bresetldrpc,vectorundefldrpc,vectorswildrpc,vectorpabtldrpc,vectordabtnop/*保留的异常项*/ldrpc,vectorirqldrpc,vectorfiqvectorundef:.
wordvectorundefvectorswi:.
wordvectorswivectorpabt:.
wordvectorpabtvectordabt:.
wordvectordabtvectorresv:.
wordvectorresvvectorirq:.
wordvectorirqvectorfiq:.
wordvectorfiq/**RT-ThreadBSS段起始、结束位置,这个在链接脚本中定义*/17.
1.
CPU相关移植187RT-ThreadProgrammingGuide,Release0.
3.
0.
globlbssstartbssstart:.
wordbssstart.
globlbssendbssend:.
wordbssend/*系统入口*/reset:/*关闭看门狗*/ldrr0,=0xFFFFFD40ldrr1,=0x00008000strr1,[r0,#0x04]/*使能主晶振*/ldrr0,=0xFFFFFC00ldrr1,=0x00000601strr1,[r0,#0x20]/*等待晶振稳定*/moscsloop:ldrr2,[r0,#0x68]andsr2,r2,#1beqmoscsloop/*设置PLL*/ldrr1,=0x00191C05strr1,[r0,#0x2C]/*等待PLL上锁*/pllloop:ldrr2,[r0,#0x68]andsr2,r2,#0x04beqpllloop/*选择clock*/ldrr1,=0x00000007strr1,[r0,#0x30]/*设置各个模式下的栈*/ldrr0,=TOPSTACK/*设置栈*//*undefined模式*/msrcpsrc,#MODEUND|IBIT|FBITmovsp,r0subr0,r0,#UNDSTACKSIZE/*abort模式*/188Chapter17.
GNUGCC移植RT-ThreadProgrammingGuide,Release0.
3.
0msrcpsrc,#MODEABT|IBIT|FBITmovsp,r0subr0,r0,#ABTSTACKSIZE/*FIQ模式*/msrcpsrc,#MODEFIQ|IBIT|FBITmovsp,r0subr0,r0,#FIQSTACKSIZE/*IRQ模式*/msrcpsrc,#MODEIRQ|IBIT|FBITmovsp,r0subr0,r0,#IRQSTACKSIZE/*系统模式*/msrcpsrc,#MODESVCmovsp,r0#ifdefFLASHBUILD/*如果是FLASH模式build,从ROM中复制数据段到RAM中*/ldrr1,=etextldrr2,=dataldrr3,=edatadataloop:cmpr2,r3ldrlor0,[r1],#4strlor0,[r2],#4blodataloop#else/*重映射SRAM到零地址*/ldrr0,=0xFFFFFF00movr1,#0x01strr1,[r0]#endif/*屏蔽所有IRQ中断*/ldrr1,=0xFFFFF124ldrr0,=0XFFFFFFFFstrr0,[r1];对bss段进行清零movr0,#0;置R0为0ldrr1,=bssstart;获得bss段开始位置ldrr2,=bssend;获得bss段结束位置bssloop:cmpr1,r2;确认是否已经到结束位置strlor0,[r1],#4;清零17.
1.
CPU相关移植189RT-ThreadProgrammingGuide,Release0.
3.
0blobssloop;循环直到结束;对C++的全局对象进行构造ldrr0,=ctorsstart;获得ctors开始位置ldrr1,=ctorsend;获得ctors结束位置ctorloop:cmpr0,r1beqctorendldrr2,[r0],#4stmfdsp!
,{r0-r1}movlr,pcbxr2ldmfdsp!
,{r0-r1}bctorloopctorend:/*跳转到RT-ThreadKernel*/ldrpc,rtthreadstartuprtthreadstartup:.
wordrtthreadstartup/*异常处理*/vectorundef:bvectorundefvectorswi:bvectorswivectorpabt:bvectorpabtvectordabt:bvectordabtvectorresv:bvectorresv.
globlrtinterruptenter.
globlrtinterruptleave.
globlrtthreadswitchinterrputflag.
globlrtinterruptfromthread.
globlrtinterrupttothread/**IRQ异常处理*/vectorirq:stmfdsp!
,{r0-r12,lr}/*先把R0–R12,LR寄存器压栈保存*/blrtinterruptenter/*调用rtinterruptenter以确认进入中断处理*/blrthwtrapirq/*调用C函数的中断处理函数进行处理*/blrtinterruptleave/*调用rtinterruptleave表示离开中断处理*//*如果设置了rtthreadswitchinterrputflag,进行中断中的线程上下文处理*/ldrr0,=rtthreadswitchinterrputflagldrr1,[r0]cmpr1,#1/*判断是否设置了中断中线程切换标志*/beqinterruptthreadswitch/*是则跳转到interruptthreadswitch*/190Chapter17.
GNUGCC移植RT-ThreadProgrammingGuide,Release0.
3.
0ldmfdsp!
,{r0-r12,lr}/*R0–R12,LR出栈*/subspc,lr,#4/*中断返回*//**FIQ异常处理*在这里仅仅进行了简单的函数回调,OS并没对FIQ做特别处理.
*如果在FIQ中要用到OS的一些服务,需要做IRQ异常类似处理.
*/vectorfiq:stmfdsp!
,{r0-r7,lr}/*R0–R7,LR寄存器入栈,*FIQ模式下,R0–R7是通用寄存器,*其他的都是分组寄存器*/blrthwtrapfiq/*跳转到rthwtrapfiq进行处理*/ldmfdsp!
,{r0-r7,lr}subspc,lr,#4/*FIQ异常返回*//*进行中断中的线程切换*/interruptthreadswitch:movr1,#0/*清除切换标识*/strr1,[r0]ldmfdsp!
,{r0-r12,lr}/*载入保存的R0–R12及LR寄存器*/stmfdsp!
,{r0-r3}/*先保存R0–R3寄存器*/movr1,sp/*保存一份IRQ模式下的栈指针到R1寄存器*/addsp,sp,#16/*IRQ栈中保持了R0–R4,加16后刚好到栈底*//*后面会直接跳出IRQ模式,相当于恢复IRQ的栈*/subr2,lr,#4/*保存中断前线程的PC到R2寄存器*/mrsr3,spsr/*保存中断前的CPSR到R3寄存器*/orrr0,r3,#NOINT/*关闭中断前线程的中断*/msrspsrc,r0ldrr0,=.
+8/*把当前地址+8载入到R0寄存器中*/movspc,r0/*退出IRQ模式,由于SPSR被设置成关中断模式,*所以从IRQ返回后,中断并没有打开*R0寄存器中的位置实际就是下一条指令,*即PC继续往下走*此时*模式已经换成中断前的SVC模式,*SP寄存器也是SVC模式下的栈寄存器*R1保存IRQ模式下的栈指针17.
1.
CPU相关移植191RT-ThreadProgrammingGuide,Release0.
3.
0*R2保存切换出线程的PC*R3保存切换出线程的CPSR*/stmfdsp!
,{r2}/*对R2寄存器压栈,即前面保存的切换出线程PC*/stmfdsp!
,{r4-r12,lr}/*对LR,R4–R12寄存器进行压栈(切换出线程的)movr4,r1/*R4寄存器为IRQ模式下的栈指针,/*栈中保存了切换出线程的R0–R3*/movr5,r3/*R5中保存了切换出线程的CPSR*/ldmfdr4!
,{r0-r3}/*恢复切换出线程的R0–R3寄存器*/stmfdsp!
,{r0-r3}/*对切换出线程的R0–R3寄存器进行压栈*/stmfdsp!
,{r5}/*对切换出线程的CPSR进行压栈*/mrsr4,spsr/*读取切换出线程的SPSR寄存器*/stmfdsp!
,{r4}/*对切换出线程的SPSR进行压栈*/ldrr4,=rtinterruptfromthreadldrr5,[r4]strsp,[r5]/*更新切换出线程的sp指针(存放在TCB中)*/ldrr6,=rtinterrupttothreadldrr6,[r6]ldrsp,[r6]/*获得切换到线程的栈指针*/ldmfdsp!
,{r4}/*恢复切换到线程的SPSR寄存器*/msrSPSRcxsf,r4ldmfdsp!
,{r4}/*恢复切换到线程的CPSR寄存器*/msrCPSRcxsf,r4ldmfdsp!
,{r0-r12,lr,pc}/*恢复切换到线程的R0–R12,LR及PC寄存器*/17.
1.
3线程初始栈构造在创建一个线程并把它放到就绪队列,调度器选择了这个线程开始运行时,调度器只知道要切换到这个线程中,它并不知道线程应该从什么地方开始运行,也不知道线程入口处应该放置哪些参数.
为了解决这个问题,那么移植就需要"手工地"设置初始栈.
添加一个stack.
c文件以实现线程栈的初始化工作,代码如下.
#include#defineSVCMODE0x13/***Thisfunctionwillinitializethreadstack**@paramtentrytheentryofthread192Chapter17.
GNUGCC移植RT-ThreadProgrammingGuide,Release0.
3.
0*@paramparametertheparameterofentry*@paramstackaddrthebeginningstackaddress*@paramtexitthefunctionwillbecalledwhenthreadexit**@returnstackaddress*/rtuint8t*rthwstackinit(void*tentry,void*parameter,rtuint8t*stackaddr,void*texit){unsignedlong*stk;stk=(unsignedlong*)stackaddr;*(stk)=(unsignedlong)tentry;/*线程入口,等价于线程的PC*/*(--stk)=(unsignedlong)texit;/*lr*/*(--stk)=0;/*r12*/*(--stk)=0;/*r11*/*(--stk)=0;/*r10*/*(--stk)=0;/*r9*/*(--stk)=0;/*r8*/*(--stk)=0;/*r7*/*(--stk)=0;/*r6*/*(--stk)=0;/*r5*/*(--stk)=0;/*r4*/*(--stk)=0;/*r3*/*(--stk)=0;/*r2*/*(--stk)=0;/*r1*/*(--stk)=(unsignedlong)parameter;/*r0:入口函数参数*/*(--stk)=SVCMODE;/*cpsr,采用SVC模式运行*/*(--stk)=SVCMODE;/*spsr*//*returntask'scurrentstackaddress*/return(rtuint8t*)stk;}17.
1.
4中断处理当一个中断触发时,从上面初始化代码中可以看到,它的处理流程是这样的:rthwtrapirq是实际的中断服务例程调用函数,它在trap.
c文件中实现.
#include#include#include"AT91SAM7S.
h"/*实际的中断处理函数*/17.
1.
CPU相关移植193RT-ThreadProgrammingGuide,Release0.
3.
0Figure17.
2:启动文件中的汇编处理194Chapter17.
GNUGCC移植RT-ThreadProgrammingGuide,Release0.
3.
0voidrthwtrapirq(){/*从IVR寄存器中获得当前设定的中断服务例程函数入口*/rtisrhandlerthander=(rtisrhandlert)AT91CAICIVR;/*调用中断服务例程函数,ISR寄存器指示出是第几号中断*/hander(AT91CAICISR);/*写EOICR寄存器以指示出中断服务结束*/AT91CAICEOICR=0;}/*FIQ异常处理函数,目前未使用到*/voidrthwtrapfiq(){rtkprintf("fastinterruptrequest\n");}在rthwtrapirq函数中,它只是负责找到当前产生的中断应该调用哪个中断服务例程,而并没给出如何去设置每个中断所对应的中断服务例程.
中断控制器及中断服务例程的设定在interrupt.
c中实现.
#include#include"AT91SAM7S.
h"/*总共32个中断*/#defineMAXHANDLERS32externrtuint32trtinterruptnest;rtuint32trtinterruptfromthread,rtinterrupttothread;rtuint32trtthreadswitchinterrputflag;/*默认的中断处理*/voidrthwinterrupthandler(intvector){rtkprintf("Unhandledinterrupt%doccured!
!
!
\n",vector);}/*初始化中断控制器*/voidrthwinterruptinit(){rtbasetindex;/*每个中断服务例程都设置到默认的中断处理上*/for(index=0;index=0&&vectorCODE=0.
=ALIGN(4);.
rodata:{*(.
rodata.
rodata.
*)}>CODEetext=.
;PROVIDE(etext=.
);/*.
datasectionwhichisusedforinitializeddata*/.
data:AT(etext){data=.
;*(.
data)SORT(CONSTRUCTORS)}>DATA.
=ALIGN(4);edata=.
;PROVIDE(edata=.
);.
=ALIGN(4);bssstart=.
;.
bss:{*(.
bss)}>DATAbssend=.
;end=.
;}通过这个链接脚本文件,主要生成了几个section:*.
.
text,从0x00000000开始,放置可执行代码部分.
*.
.
rodata,紧接着.
text后面放置,其中包含了只读数据;并在后面插入了etext符合,指向结束地址.
*.
.
data,在映像文件中放置于etext位置,其中包含了可读写的数据,但在运行状态下则从0x00200000地址开始.
*.
.
bss,紧接着.
data放置,并在开始位置及结束位置放置了bssstart和bssend以指向相应的位置.
17.
2.
板级相关移植205RT-ThreadProgrammingGuide,Release0.
3.
0206Chapter17.
GNUGCC移植CHAPTEREIGHTEENREALVIEWMDK移植本节用到的RealViewMDK版本是3.
50评估版,因为生成的代码小于from;r1-->to;进行线程的上下文切换rthwcontextswitchPROCEXPORTrthwcontextswitchSTMFDsp!
,{lr};把LR寄存器压入栈(这个函数返回后的下一个执行处)STMFDsp!
,{r0-r12,lr};把R0–R12以及LR压入栈MRSr4,cpsr;读取CPSR寄存器到R4寄存器STMFDsp!
,{r4};把R4寄存器压栈(即上一指令取出的CPSR寄存器)MRSr4,spsr;读取SPSR寄存器到R4寄存器STMFDsp!
,{r4};把R4寄存器压栈(即SPSR寄存器)STRsp,[r0];把栈指针更新到TCB的sp,是由R0传入此函数;到这里换出线程的上下文都保存在栈中LDRsp,[r1];载入切换到线程的TCB的sp;从切换到线程的栈中恢复上下文,次序和保存的时候刚好相反LDMFDsp!
,{r4};出栈到R4寄存器(保存了SPSR寄存器)MSRspsrcxsf,r4;恢复SPSR寄存器LDMFDsp!
,{r4};出栈到R4寄存器(保存了CPSR寄存器)MSRcpsrcxsf,r4;恢复CPSR寄存器18.
3.
线程上下文切换215RT-ThreadProgrammingGuide,Release0.
3.
0LDMFDsp!
,{r0-r12,lr,pc};对R0–R12及LR、PC进行恢复ENDP;voidrthwcontextswitchto(rtuint32to);;r0-->to;此函数只在系统进行第一次发生任务切换时使用,因为是从没有线程的状态进行切换;实现上,刚好是rthwcontextswitch的下半截rthwcontextswitchtoPROCEXPORTrthwcontextswitchtoLDRsp,[r0];获得切换到线程的SP指针LDMFDsp!
,{r4};出栈R4寄存器(保存了SPSR寄存器值)MSRspsrcxsf,r4;恢复SPSR寄存器LDMFDsp!
,{r4};出栈R4寄存器(保存了CPSR寄存器值)MSRcpsrcxsf,r4;恢复CPSR寄存器LDMFDsp!
,{r0-r12,lr,pc};恢复R0–R12,LR及PC寄存器ENDPIMPORTrtthreadswitchinterrputflagIMPORTrtinterruptfromthreadIMPORTrtinterrupttothread;voidrthwcontextswitchinterrupt(rtuint32from,rtuint32to);;此函数会在调度器中调用,在调度器做上下文切换前会判断是否处于中断服务模式中,如果;是则调用rthwcontextswitchinterrupt函数(设置中断中任务切换标志);否则调用rthwcontextswitch函数(进行真正的线程上线文切换)rthwcontextswitchinterruptPROCEXPORTrthwcontextswitchinterruptLDRr2,=rtthreadswitchinterrputflagLDRr3,[r2];载入中断中切换标致地址CMPr3,#1;等于1BEQreswitch;如果等于1,跳转到reswitchMOVr3,#1;设置中断中切换标志位1STRr3,[r2];保存到标志变量中LDRr2,=rtinterruptfromthreadSTRr0,[r2];保存切换出线程栈指针reswitchLDRr2,=rtinterrupttothreadSTRr1,[r2];保存切换到线程栈指针BXlrENDPEND216Chapter18.
RealViewMDK移植RT-ThreadProgrammingGuide,Release0.
3.
018.
4启动汇编文件启动汇编文件可直接在RealViewMDK新创建的SAM7.
s文件上进行修改得到,把它重命名(为了和RT-Thread的文件命名规则保持一致)为startrvds.
s.
修改主要有几点:默认IRQ中断是由RealView的库自己处理的,RT-Thread需要截获下来进行做操作系统级的调度;自动生成的SAM7.
s默认对WatchDog不做处理,修改成disable状态(否则需要在代码中加入相应代码);在汇编文件最后跳转到RealView的库函数main时,会提前转到ARM的用户模式,RT-Thread需要维持在SVC模式;和GNUGCC的移植类似,需要添加中断结束后的线程上下文切换部分代码.
代码A-8是启动汇编的代码清单,其中加双下划线部分是修改的部分.
代码A-18startrvds.
s;/*SAM7.
S:StartupfileforAtmelAT91SAM7deviceseries*/;/*>>*/;/*ThisfileispartoftheuVision/ARMdevelopmenttools.
*/;/*Copyright(c)2005-2006KeilSoftware.
Allrightsreserved.
*/;/*Thissoftwaremayonlybeusedunderthetermsofavalid,current,*/;/*enduserlicencefromKEILforacompatibleversionofKEILsoftware*/;/*developmenttools.
Nothingelsegivesyoutherighttousethissoftware.
*/;/*;*TheSAM7.
ScodeisexecutedafterCPUReset.
Thisfilemaybe;*translatedwiththefollowingSETsymbols.
InuVisiontheseSET;*symbolsareenteredunderOptions-ASM-Define.
;*;*REMAP:whensetthestartupcoderemapsexceptionvectorsfrom;*on-chipRAMtoaddress0.
;*;*RAMINTVEC:whensetthestartupcodecopiesexceptionvectors;*fromon-chipFlashtoon-chipRAM.
;*/;StandarddefinitionsofModebitsandInterrupt(I&F)flagsinPSRsModeUSREQU0x10ModeFIQEQU0x11ModeIRQEQU0x12ModeSVCEQU0x13ModeABTEQU0x17ModeUNDEQU0x1BModeSYSEQU0x1F18.
4.
启动汇编文件217RT-ThreadProgrammingGuide,Release0.
3.
0IBitEQU0x80;whenIbitisset,IRQisdisabledFBitEQU0x40;whenFbitisset,FIQisdisabled;InternalMemoryBaseAddressesFLASHBASEEQU0x00100000RAMBASEEQU0x00200000;//StackConfiguration(StackSizesinBytes);//UndefinedMode;//SupervisorMode;//AbortMode;//FastInterruptMode;//InterruptMode;//User/SystemMode;//UNDStackSizeEQU0x00000000SVCStackSizeEQU0x00000080ABTStackSizeEQU0x00000000FIQStackSizeEQU0x00000000IRQStackSizeEQU0x00000080USRStackSizeEQU0x00000400ISRStackSizeEQU(UNDStackSize+SVCStackSize+ABTStackSize+\FIQStackSize+IRQStackSize)AREASTACK,NOINIT,READWRITE,ALIGN=3StackMemSPACEUSRStackSizeinitialspSPACEISRStackSizeStackTop;//HeapConfiguration;//HeapSize(inBytes);//HeapSizeEQU0x00000000AREAHEAP,NOINIT,READWRITE,ALIGN=3heapbaseHeapMemSPACEHeapSizeheaplimit218Chapter18.
RealViewMDK移植RT-ThreadProgrammingGuide,Release0.
3.
0;ResetController(RSTC)definitionsRSTCBASEEQU0xFFFFFD00;RSTCBaseAddressRSTCMREQU0x08;RSTCMROffset;/*;//ResetController(RSTC);//URSTEN:UserResetEnable;//EnablesNRSTPintogenerateReset;//ERSTL:ExternalResetLength;//ExternalResetTimein2(ERSTL+1)SlowClockCycles;//;*/RSTCSETUPEQU1RSTCMRValEQU0xA5000401;EmbeddedFlashController(EFC)definitionsEFCBASEEQU0xFFFFFF00;EFCBaseAddressEFC0FMREQU0x60;EFC0FMROffsetEFC1FMREQU0x70;EFC1FMROffset;//EmbeddedFlashController0(EFC0);//FMCN:FlashMicrosecondCycleNumber;//NumberofMasterClockCyclesin1us;//FWS:FlashWaitState;//Read:1cycle/Write:2cycles;//Read:2cycle/Write:3cycles;//Read:3cycle/Write:4cycles;//Read:4cycle/Write:4cycles;//EFC0SETUPEQU1EFC0FMRValEQU0x00320100;//EmbeddedFlashController1(EFC1);//FMCN:FlashMicrosecondCycleNumber;//NumberofMasterClockCyclesin1us;//FWS:FlashWaitState;//Read:1cycle/Write:2cycles;//Read:2cycle/Write:3cycles;//Read:3cycle/Write:4cycles;//Read:4cycle/Write:4cycles;//EFC1SETUPEQU0EFC1FMRValEQU0x0032010018.
4.
启动汇编文件219RT-ThreadProgrammingGuide,Release0.
3.
0;WatchdogTimer(WDT)definitionsWDTBASEEQU0xFFFFFD40;WDTBaseAddressWDTMREQU0x04;WDTMROffset;//WatchdogTimer(WDT);//WDV:WatchdogCounterValue;//WDD:WatchdogDeltaValue;//WDFIEN:WatchdogFaultInterruptEnable;//WDRSTEN:WatchdogResetEnable;//WDRPROC:WatchdogResetProcessor;//WDDBGHLT:WatchdogDebugHalt;//WDIDLEHLT:WatchdogIdleHalt;//WDDIS:WatchdogDisable;//WDTSETUPEQU1WDTMRValEQU0x00008000;PowerMangementController(PMC)definitionsPMCBASEEQU0xFFFFFC00;PMCBaseAddressPMCMOREQU0x20;PMCMOROffsetPMCMCFREQU0x24;PMCMCFROffsetPMCPLLREQU0x2C;PMCPLLROffsetPMCMCKREQU0x30;PMCMCKROffsetPMCSREQU0x68;PMCSROffsetPMCMOSCENEQU(1PowerMangementController(PMC);//MainOscillator;//MOSCEN:MainOscillatorEnable;//OSCBYPASS:OscillatorBypass;//OSCCOUNT:MainOscillatorStartupTime;//;//PhaseLockedLoop(PLL);//DIV:PLLDivider220Chapter18.
RealViewMDK移植RT-ThreadProgrammingGuide,Release0.
3.
0;//MUL:PLLMultiplier;//PLLOutputismultipliedbyMUL+1;//OUT:PLLClockFrequencyRange;//80.
.
160MHzReserved;//150.
.
220MHzReserved;//PLLCOUNT:PLLLockCounter;//USBDIV:USBClockDivider;//None24Reserved;//;//CSS:ClockSourceSelection;//SlowClock;//MainClock;//Reserved;//PLLClock;//PRES:Prescaler;//None;//Clock/2Clock/4;//Clock/8Clock/16;//Clock/32Clock/64;//Reserved;//PMCSETUPEQU1PMCMORValEQU0x00000601PMCPLLRValEQU0x00191C05PMCMCKRValEQU0x00000007PRESERVE8;AreaDefinitionandEntryPoint;StartupCodemustbelinkedfirstatAddressatwhichitexpectstorun.
AREARESET,CODE,READONLYARM;ExceptionVectors;MappedtoAddress0.
;Absoluteaddressingmodemustbeused.
;DummyHandlersareimplementedasinfiniteloopswhichcanbemodified.
VectorsLDRPC,ResetAddrLDRPC,UndefAddrLDRPC,SWIAddrLDRPC,PAbtAddrLDRPC,DAbtAddrNOP;ReservedVectorLDRPC,IRQAddr18.
4.
启动汇编文件221RT-ThreadProgrammingGuide,Release0.
3.
0LDRPC,FIQAddrResetAddrDCDResetHandlerUndefAddrDCDUndefHandlerSWIAddrDCDSWIHandlerPAbtAddrDCDPAbtHandlerDAbtAddrDCDDAbtHandlerDCD0;ReservedAddressIRQAddrDCDIRQHandlerFIQAddrDCDFIQHandlerUndefHandlerBUndefHandlerSWIHandlerBSWIHandlerPAbtHandlerBPAbtHandlerDAbtHandlerBDAbtHandler;IRQ和FIQ的处理由操作系统截获,需要重新实现;IRQHandlerBIRQHandlerFIQHandlerBFIQHandler;ResetHandlerEXPORTResetHandlerResetHandler;SetupRSTCIFRSTCSETUP!
=0LDRR0,=RSTCBASELDRR1,=RSTCMRValSTRR1,[R0,#RSTCMR]ENDIF;SetupEFC0IFEFC0SETUP!
=0LDRR0,=EFCBASELDRR1,=EFC0FMRValSTRR1,[R0,#EFC0FMR]ENDIF;SetupEFC1IFEFC1SETUP!
=0LDRR0,=EFCBASELDRR1,=EFC1FMRValSTRR1,[R0,#EFC1FMR]ENDIF222Chapter18.
RealViewMDK移植RT-ThreadProgrammingGuide,Release0.
3.
0;SetupWDTIFWDTSETUP!
=0LDRR0,=WDTBASELDRR1,=WDTMRValSTRR1,[R0,#WDTMR]ENDIF;SetupPMCIFPMCSETUP!
=0LDRR0,=PMCBASE;SetupMainOscillatorLDRR1,=PMCMORValSTRR1,[R0,#PMCMOR];WaituntilMainOscillatorisstablilizedIF(PMCMORVal:AND:PMCMOSCEN)!
=0MOSCSLoopLDRR2,[R0,#PMCSR]ANDSR2,R2,#PMCMOSCSBEQMOSCSLoopENDIF;SetupthePLLIF(PMCPLLRVal:AND:PMCMUL)!
=0LDRR1,=PMCPLLRValSTRR1,[R0,#PMCPLLR];WaituntilPLLisstabilizedPLLLoopLDRR2,[R0,#PMCSR]ANDSR2,R2,#PMCLOCKBEQPLLLoopENDIF;SelectClockIF(PMCMCKRVal:AND:PMCCSS)==1;MainClockSelectedLDRR1,=PMCMCKRValANDR1,#PMCCSSSTRR1,[R0,#PMCMCKR]WAITRdy1LDRR2,[R0,#PMCSR]ANDSR2,R2,#PMCMCKRDYBEQWAITRdy1LDRR1,=PMCMCKRValSTRR1,[R0,#PMCMCKR]WAITRdy2LDRR2,[R0,#PMCSR]ANDSR2,R2,#PMCMCKRDY18.
4.
启动汇编文件223RT-ThreadProgrammingGuide,Release0.
3.
0BEQWAITRdy2ELIF(PMCMCKRVal:AND:PMCCSS)==3;PLLClockSelectedLDRR1,=PMCMCKRValANDR1,#PMCPRESSTRR1,[R0,#PMCMCKR]WAITRdy1LDRR2,[R0,#PMCSR]ANDSR2,R2,#PMCMCKRDYBEQWAITRdy1LDRR1,=PMCMCKRValSTRR1,[R0,#PMCMCKR]WAITRdy2LDRR2,[R0,#PMCSR]ANDSR2,R2,#PMCMCKRDYBEQWAITRdy2ENDIF;SelectClockENDIF;PMCSETUP;CopyExceptionVectorstoInternalRAMIF:DEF:RAMINTVECADRR8,Vectors;SourceLDRR9,=RAMBASE;DestinationLDMIAR8!
,{R0-R7};LoadVectorsSTMIAR9!
,{R0-R7};StoreVectorsLDMIAR8!
,{R0-R7};LoadHandlerAddressesSTMIAR9!
,{R0-R7};StoreHandlerAddressesENDIF;Remapon-chipRAMtoaddress0MCBASEEQU0xFFFFFF00;MCBaseAddressMCRCREQU0x00;MCRCROffsetIF:DEF:REMAPLDRR0,=MCBASEMOVR1,#1STRR1,[R0,#MCRCR];RemapENDIF;SetupStackforeachmodeLDRR0,=StackTop;EnterUndefinedInstructionModeandsetitsStackPointerMSRCPSRc,#ModeUND:OR:IBit:OR:FBit224Chapter18.
RealViewMDK移植RT-ThreadProgrammingGuide,Release0.
3.
0MOVSP,R0SUBR0,R0,#UNDStackSize;EnterAbortModeandsetitsStackPointerMSRCPSRc,#ModeABT:OR:IBit:OR:FBitMOVSP,R0SUBR0,R0,#ABTStackSize;EnterFIQModeandsetitsStackPointerMSRCPSRc,#ModeFIQ:OR:IBit:OR:FBitMOVSP,R0SUBR0,R0,#FIQStackSize;EnterIRQModeandsetitsStackPointerMSRCPSRc,#ModeIRQ:OR:IBit:OR:FBitMOVSP,R0SUBR0,R0,#IRQStackSize;EnterSupervisorModeandsetitsStackPointerMSRCPSRc,#ModeSVC:OR:IBit:OR:FBitMOVSP,R0SUBR0,R0,#SVCStackSize;EnterUserModeandsetitsStackPointer;在跳转到main函数前,维持在SVC模式;MSRCPSRc,#ModeUSRIF:DEF:MICROLIBEXPORTinitialspELSEMOVSP,R0SUBSL,SP,#USRStackSizeENDIF;EntertheCcodeIMPORTmainLDRR0,=mainBXR0IMPORTrtinterruptenterIMPORTrtinterruptleaveIMPORTrtthreadswitchinterrputflagIMPORTrtinterruptfromthread18.
4.
启动汇编文件225RT-ThreadProgrammingGuide,Release0.
3.
0IMPORTrtinterrupttothreadIMPORTrthwtrapirq;IRQ处理的实现IRQHandlerPROCEXPORTIRQHandlerstmfdsp!
,{r0-r12,lr};对R0–R12,LR寄存器压栈blrtinterruptenter;通知RT-Thread进入中断模式blrthwtrapirq;相应中断服务例程处理blrtinterruptleave;通知RT-Thread要离开中断模式;判断中断中切换是否置位,如果是,进行上下文切换ldrr0,=rtthreadswitchinterrputflagldrr1,[r0]cmpr1,#1beqrthwcontextswitchinterruptdo;中断中切换发生;如果跳转了,将不会回来ldmfdsp!
,{r0-r12,lr};恢复栈subspc,lr,#4;从IRQ中返回ENDP;voidrthwcontextswitchinterruptdo(rtbasetflag);中断结束后的上下文切换rthwcontextswitchinterruptdoPROCEXPORTrthwcontextswitchinterruptdomovr1,#0;清除中断中切换标志strr1,[r0]ldmfdsp!
,{r0-r12,lr};先恢复被中断线程的上下文stmfdsp!
,{r0-r3};对R0–R3压栈,因为后面会用到movr1,sp;把此处的栈值保存到R1addsp,sp,#16;恢复IRQ的栈,后面会跳出IRQ模式subr2,lr,#4;保存切换出线程的PC到R2mrsr3,spsr;获得SPSR寄存器值orrr0,r3,#IBit|FBitmsrspsrc,r0;关闭SPSR中的IRQ/FIQ中断;切换到SVC模式msrcpsrc,#ModeSVCstmfdsp!
,{r2};保存切换出任务的PCstmfdsp!
,{r4-r12,lr};保存R4–R12,LR寄存器movr4,r1;R1保存有压栈R0–R3处的栈位置movr5,r3;R3切换出线程的CPSRldmfdr4!
,{r0-r3};恢复R0–R3226Chapter18.
RealViewMDK移植RT-ThreadProgrammingGuide,Release0.
3.
0stmfdsp!
,{r0-r3};R0–R3压栈到切换出线程stmfdsp!
,{r5};切换出线程CPSR压栈mrsr4,spsrstmfdsp!
,{r4};切换出线程SPSR压栈ldrr4,=rtinterruptfromthreadldrr5,[r4]strsp,[r5];保存切换出线程的SP指针ldrr6,=rtinterrupttothreadldrr6,[r6]ldrsp,[r6];获得切换到线程的栈ldmfdsp!
,{r4};恢复SPSRmsrSPSRcxsf,r4ldmfdsp!
,{r4};恢复CPSRmsrCPSRcxsf,r4ldmfdsp!
,{r0-r12,lr,pc};恢复R0–R12,LR及PC寄存器ENDPIF:DEF:MICROLIBEXPORTheapbaseEXPORTheaplimitELSE;UserInitialStack&HeapAREA|.
text|,CODE,READONLYIMPORTusetworegionmemoryEXPORTuserinitialstackheapuserinitialstackheapLDRR0,=HeapMemLDRR1,=(StackMem+USRStackSize)LDRR2,=(HeapMem+HeapSize)LDRR3,=StackMemBXLRENDIFEND18.
4.
启动汇编文件227RT-ThreadProgrammingGuide,Release0.
3.
018.
5中断处理中断处理部分和GNUGCC中的移植是相同的,详细请参见:ref:AT91SAM7S64-interrupt-c18.
6开发板初始化此部分代码也和GNUGCC中断移植相同,详细请参见:ref:AT91SAM7S64-board-c228Chapter18.
RealViewMDK移植CHAPTERNINETEENRT-THREAD/STM32说明本文是RT-Thread的STM32移植的说明.
STM32是一款ARMCortexM3芯片,本文也对RT-Thread关于ARMCortexM3体系结构移植情况进行详细说明.
19.
1ARMCortexM3概况CortexM3微处理器是ARM公司于2004年推出的基于ARMv7架构的新一代微处理器,它的速度比目前广泛使用的ARM7快三分之一,功耗则低四分之三,并且能实现更小芯片面积,利于将更多功能整合在更小的芯片尺寸中.
Cortex-M3微处理器包含了一个ARMcore,内置了嵌套向量中断控制器、存储器保护等系统外设.
ARMcore内核基于哈佛架构,3级流水线,指令和数据分别使用一条总线,由于指令和数据可以从存储器中同时读取,所以Cortex-M3处理器对多个操作并行执行,加快了应用程序的执行速度.
229RT-ThreadProgrammingGuide,Release0.
3.
0Cortex-M3微处理器是一个32位处理器,包括13个通用寄存器,两个堆栈指针,一个链接寄存器,一个程序计数器和一系列包含编程状态寄存器的特殊寄存器.
Cortex-M3微处理器的指令集则是Thumb-2指令,是16位Thumb指令的扩展集,可使用于多种场合.
BFI和BFC指令为位字段指令,在网络信息包处理等应用中可大派用场;SBFX和UBFX指令改进了从寄存器插入或提取多个位的能力,这一能力在汽车应用中的表现相当出色;RBIT指令的作用是将一个字中的位反转,在DFT等DSP运算法则的应用中非常有用;表分支指令TBB和TBH用于平衡高性能和代码的紧凑性;Thumb-2指令集还引入了一个新的If-Then结构,意味着可以有多达4个后续指令进行条件执行.
Cortex-M3微处理器支持两种工作模式(线程模式(Thread)和处理模式(Handler))和两个等级的访问形式(有特权或无特权),在不牺牲应用程序安全的前提下实现了对复杂的开放式系统的执行.
无特权代码的执行限制或拒绝对某些资源的访问,如某个指令或指定的存储器位置.
Thread是常用的工作模式,它同时支持享有特权的代码以及没有特权的代码.
当异常发生时,进入Handler模式,在该模式中所有代码都享有特权.
这两种模式中分别使用不同的两个堆栈指针寄存器.
Cortex-M3微处理器的异常模型是基于堆栈方式的.
当异常发生时,程序计数器、程序状230Chapter19.
RT-Thread/STM32说明RT-ThreadProgrammingGuide,Release0.
3.
0态寄存器、链接寄存器和R0-R3、R12四个通用寄存器将被压进堆栈.
在数据总线对寄存器压栈的同时,指令总线从向量表中识别出异常向量,并获取异常代码的第一条指令.
一旦压栈和取指完成,中断服务程序或故障处理程序就开始执行.
当处理完毕后,前面压栈的寄存器自动恢复,中断了的程序也因此恢复正常的执行.
由于可以在硬件中处理堆栈操作,Cortex-M3处理器免去了在传统的C语言中断服务程序中为了完成堆栈处理所要编写的汇编代码.
Cortex-M3微处理器内置的中断控制器支持中断嵌套(压栈),允许通过提高中断的优先级对中断进行优先处理.
正在处理的中断会防止被进一步激活,直到中断服务程序完成.

JUSTG(5.99美元/月)最新5折优惠,KVM虚拟虚拟512Mkvm路线

Justg是一家俄罗斯VPS云服务器提供商,主要提供南非地区的VPS服务器产品,CN2高质量线路网络,100Mbps带宽,自带一个IPv4和8个IPv6,线路质量还不错,主要是用户较少,带宽使用率不高,比较空闲,不拥挤,比较适合面向非洲、欧美的用户业务需求,也适合追求速度快又需要冷门的朋友。justg的俄罗斯VPS云服务器位于莫斯科机房,到美国和中国速度都非常不错,到欧洲的平均延迟时间为40毫秒,...

bluehost32元/月,2核2G/20GB空间,独立ip,新一代VPS美国云主机!

bluehost怎么样?bluehost推出新一代VPS美国云主机!前几天,BlueHost也推出了对应的周年庆活动,全场海外虚拟主机月付2.95美元起,年付送免费的域名和SSL证书,通过活动进入BlueHost中文官网,购买虚拟主机、云虚拟主机和独立服务器参与限时促销。今天,云服务器网(yuntue.com)小编给大家介绍的是新一代VPS美国云主机,美国SSD云主机,2核2G/20GB空间,独立...

搬瓦工VPS:高端线路,助力企业运营,10Gbps美国 cn2 gia,1Gbps香港cn2 gia,10Gbps日本软银

搬瓦工vps(bandwagonhost)现在面向中国大陆有3条顶级线路:美国 cn2 gia,香港 cn2 gia,日本软银(softbank)。详细带宽是:美国cn2 gia、日本软银,都是2.5Gbps~10Gbps带宽,香港 cn2 gia为1Gbps带宽,搬瓦工是目前为止,全球所有提供这三种带宽的VPS(云服务器)商家里面带宽最大的,成本最高的,没有第二家了! 官方网站:https...

suspend是什么意思为你推荐
桌面背景图片淡雅高清桌面背景图片怎么搞cf蜗牛外挂我想让cf用什么外挂可以让号被封了要最快那种。最好永久封了最好p图软件哪个好用美图P图软件哪个好,你们用哪个集成显卡和独立显卡哪个好集成显卡和独立显卡什么区别?免费阅读小说app哪个好有什么好用的看小说的app手机浏览器哪个好目前手机浏览器哪个最好闪迪和金士顿哪个好闪迪和金士顿哪个好少儿英语哪个好少儿英语教材哪个好?等额本息等额本金哪个好等额本息和等额本金哪个好?雅思和托福哪个好考托福好考还是雅思好考?
成都主机租用 荷兰vps 西安电信测速 burstnet wavecom mach5 外国域名 哈喽图床 光棍节日志 七夕促销 空间技术网 新睿云 空间租赁 阿里云免费邮箱 万网空间 空间服务器 黑科云 apnic 789电视剧网 谷歌搜索打不开 更多