RTThread龙芯移植技术文档分析.docx
- 文档编号:29895025
- 上传时间:2023-08-03
- 格式:DOCX
- 页数:234
- 大小:835.86KB
RTThread龙芯移植技术文档分析.docx
《RTThread龙芯移植技术文档分析.docx》由会员分享,可在线阅读,更多相关《RTThread龙芯移植技术文档分析.docx(234页珍藏版)》请在冰豆网上搜索。
RTThread龙芯移植技术文档分析
第一章RT-THREAD简介
RT-Thread是一款来自中国的开放源代码实时操作系统,并且是一款商业许可证非常宽松的实时操作系统。
下图是RT-Thread及外围组件的基本框架图:
RT-ThreadKernel内核部分包括了RT-Thread的核心代码,包括对象管理器,线程管理及调度,线程间通信等的微小内核实现(最小能够到达2.5kROM,1kRAM体积占用)。
内核库是为了保证内核能够独立运作的一套小型类似C库实现(这部分根据编译器自带C库的情况会有些不同,使用GCC编译器时,携带更多的标准C库实现)。
CPU及板级支持包包含了RT-Thread支持的各个平台移植代码,通常会包含两个汇编文件,一个是系统启动初始化文件,一个是线程进行上下文切换的文件,其他的都是C源文件。
1.1实时内核
1.1.1任务/线程调度
在RT-Thread中线程是最小的调度单位,线程调度算法是基于优先级的全抢占式多线程调度算法,支持256个线程优先级(也能通过配置文件更改为最大支持32个或8个线程优先级,针对STM32通常配置是32个线程优先级),0优先级代表最高优先级,255优先级留给空闲线程使用;支持创建相同优先级线程,相同优先级的线程采用可设置时间片的轮转调度算法;调度器寻找下一个最高优先级就绪线程的时间是恒定的(时间复杂度是1,即O
(1))。
系统不限制线程数量的多少,只和硬件平台的具体内存相关。
1.1.2任务同步机制
系统支持信号量、互斥锁作为线程间同步机制。
互斥锁采用优先级继存方式以解决优先级翻转问题。
信号量的释放动作可安全用于中断服务例程中。
同步机制支持线程按优先级等待或按先进先出方式获取信号量或互斥锁。
1.1.3任务间通信机制
系统支持事件、邮箱和消息队列等通信机制。
事件支持多事件”或触发”及”与触发”,适合于线程等待多个事件情况。
邮箱中一封邮件的长度固定为4字节,效率较消息队列更为高效。
通信设施中的发送动作可安全用于中断服务例程中。
通信机制支持线程按优先级等待或按先进先出方式获取。
1.1.4时间管理
系统使用时钟节拍来完成同优先级任务的时间片轮转调度;线程对内核对象的时间敏感性是通过系统定时器来实现的;定时器支持软定时器及硬定时器(软定时器的处理在系统线程的上下文中,硬定时器的处理在中断的上下文中);定时器支持一次性超时及周期性超时。
1.1.5内存管理
系统支持静态内存池管理及动态内存堆管理。
从静态内存池中获取内存块时间恒定,当内存池为空时,可把申请内存块的线程阻塞(或立刻返回,或等待一段时间后仍未获得内存块返回。
这取决于内存块申请时设置的等待时间),当其他线程释内存块到内存池时,将把相应阻塞线程唤醒。
动态堆内存管理对于不同的系统资源情况,提供了面向小内存系统的管理算法及大内存系统的SLAB内存管理算法。
1.1.6设备管理
系统实现了按名称访问的设备管理子系统,可按照统一的API界面访问硬件设备。
在设备驱动接口上,根据嵌入式系统的特点,对不同的设备可以挂接相应的事件,当设备事件触发时,通知给上层的应用程序。
1.2虚拟文件系统
RT-Thread提供的文件系统称为设备文件系统,它主要包含了一个非常轻型的虚拟文件系统。
虚拟文件系统的好处就是,不管下层采用了什么文件系统,例如内存虚拟文件系统,FAT32文件系统还是YAFFS2闪存文件系统,对上层应用程序提供的接口都是统一的。
1.3轻型IP协议栈
LwIP是瑞士计算机科学院(SwedishInstituteofComputerScience)的AdamDunkels等开发的一套用于嵌入式系统的开放源代码TCP/IP协议栈,它在包含完整的TCP协议实现基础上实现了小型的资源占用,因此它十分适合于使用到嵌入式设备中,RT-Thread采用LwIP做为默认的TCP/IP协议栈,同时根据小型设备的特点对其进行再优化,体积相对进一步减小,RAM占用缩小到5kB附近(依据上层应用使用情况会有浮动)。
1.4shell系统
RT-Thread的shell系统——FinSH,提供了一套供用户在命令行操作的接口,主要用于调试、查看系统信息。
由于系统程序大多数采用C语言来编写,FinSH命令行的设计被设计成类似C语言表达式的风格:
它能够解析执行大部分C语言的表达式,也能够使用类似于C语言的函数调用方式(或函数指针方式)访问系统中的函数及全局变量。
1.5图形用户界面
RT-Thread/GUI组件是一套完全针对嵌入式系统而进行优化的图形用户界面,它在保留通常意义的多窗口的前提下,提出了面板,工作台,视图的概念,通过一个个视图的渲染展现出图形用户界面绚丽的外观。
它同样也包括了基本控件的支持、中文显示的支持、多线程的支持;针对嵌入式系统计算能力不足的特点,它会自动对界面区域进行可视区域的剪切,该重绘显示的地方进行重绘,被覆盖的地方则不进行重复绘图。
1.6POSIX标准
POSIX是PortableOperatingSystemInterfaceofUnix的缩写,被大量的使用于UNIX类(开源的例如Linux,FreeBSD等)操作系统中,是通用操作系统上的工业标准。
RT-Thread中的POSIX标准组件实现了POSIX标准所要求的大部分API接口,包括其中的动态加载接口和POSIX线程库接口。
1.7用户应用模块
用户应用模块提供给了用户一个独立加载应用程序的接口,能够让用户程序类似通用计算机上进行程序编写、运行。
同时依然能够保持RT-Thread所提倡的实时性能。
它采用把用户程序编译成与位置无关运行的方式,并在内存区域中开辟一块独立的区域,让用户应用程序独立运行。
当用户应用程序退出时,系统将对这块内存区域进行清空。
这样它能够保证即使应用程序运行出错时也不会对系统造成灾难性的影响。
1.8龙芯1SoC3210平台支持情况
SoC3210是一款采用龙芯I32位高性能RISC的SoC。
它具备250MHz的系统主频,兼容MIPS2指令集,并采用5级动态流水线。
在龙芯1SoC3210平台上,RT-Thread已经实现了下列特性的支持:
✧实时内核;
✧shell;
✧虚拟文件系统;
✧图形用户界面;
✧POSIX标准组件;
✧用户应用模块。
第二章内核对象模型
RT-Thread的内核对象模型是一种非常有趣的面向对象实现方式。
由于C语言更为面向系统底层,操作系统核心通常都是采用C语言和汇编语言混合编写而成。
C语言作为一门高级计算机编程语言,一般被认为是一种面向过程的编程语言:
程序员按照特定的方式把要处理事物的过程一级级分解成一个个子过程。
面向对象源于人类对世界的认知多偏向于类别模式,根据世界中不同物品的特性分门别类的组织在一起抽象并归纳,形成各个类别的自有属性。
在计算机领域一般采用一门新的,具备面向对象特征的编程语言实现面向对象的设计,例如常见的编程语言C++,Java,Python等。
那么RT-Thread既然有意引入对象系统,为什么不直接采用C++来实现?
这个需要从C++的实现说起,用过C++的开发人员都知道,C++的对象系统中会引入很多未知的东西,例如虚拟重载表,命名粉碎,模板展开等。
对于一个需要精确控制的系统,这不是一个很好的方式,假于它人之手不如握入己手!
面向对象有它非常优越的地方,取其精华(即面向对象思想,面向对象设计),也就是RT-Thread内核对象模型的来源。
RT-Thread实时操作系统中包含一个小型的,非常紧凑的对象系统,这个对象系统完全采用C语言实现。
在了解RT-Thread内部或采用RT-Thread编程时有必要先熟悉它,它是RT-Thread实现的基础。
2.1C语言的对象化模型
面向对象的特征主要包括:
✧封装,隐藏内部实现
✧继承,复用现有代码
✧多态,改写对象行为
采用C语言实现的关键是如何运用C语言本身的特性来实现上述面向对象的特征。
2.1.1封装
封装是一种信息隐蔽技术,它体现于类的说明,是对象的重要特性。
封装使数据和加工该数据的方法(函数)封装为一个整体,以实现独立性很强的模块,使得用户只能见到对象的外特性(对象能接受哪些消息,具有那些处理能力),而对象的内特性(保存内部状态的私有数据和实现加工能力的算法)对用户是隐蔽的。
封装的目的在于把对象的设计者和对象者的使用分开,使用者不必知晓行为实现的细节,只须用设计者提供的消息来访问该对象。
在C语言中,大多数函数的命名方式是动词+名词的形式,例如要获取一个semaphore,会命名成takesemaphore,重点在take这个动作上。
在RT-Thread系统的面向对象编程中刚好相反,命名为rt_sem_take,即名词+动词的形式,重点在名词上,体现了一个对象的方法。
另外对于某些方法,仅局限在对象内部使用,它们将采用static修辞把作用范围局限在一个文件的内部。
通过这样的方式,把一些不想让用户知道的信息屏蔽在封装里,用户只看到了外层的接口,从而形成了面向对象中的最基本的对象封装实现。
一般属于某个类的对象会有一个统一的创建,析构过程。
在RT-Thread中这些分为两类(以semaphore对象为例):
✧对象内存数据块已经存在,需要对它进行初始化–rt_sem_init;
✧对象内存数据块还未分配,需要创建并初始化–rt_sem_create。
可以这么认为,对象的创建(create)是以对象的初始化(init)为基础的,创建动作相比较而言多了个内存分配的动作。
相对应的两类析构方式:
✧由rt_sem_init初始化的semaphore对象–rt_sem_detach;
✧由rt_sem_create创建的semaphore对象–rt_sem_delete.
2.1.2继承
继承性是子类自动共享父类之间数据和方法的机制。
它由类的派生功能体现。
一个类直接继承其它类的全部描述,同时可修改和扩充。
继承具有传递性。
继承分为单继承(一个子类只有一父类)和多重继承(一个类有多个父类,当前RT-Thread的对象系统不能支持)。
类的对象是各自封闭的,如果没继承性机制,则类对象中数据、方法就会出现大量重复。
继承不仅支持系统的可重用性,而且还促进系统的可扩充性。
类似的实现代码如下程序清单:
/*父类*/
structparent_class
{
inta,b;
char*str;
};
/*继承于父类的子类*/
structchild_class
{
structparent_classp;
inta,b;
};
/*操作示例函数*/
voidfunc()
{
structchild_classobj,*obj_ptr;/*子类对象及指针*/
structparent_class*parent_ptr;/*父类指针*/
obj_ptr=&obj;
/*取父指针*/
parent_ptr=(structparent_class*)&obj;
/*可通过转换过类型的父类指针访问相应的属性*/
parent_ptr->a=1;
parent_ptr->b=5;
/*子类属性的操作*/
obj_ptr->a=10;
obj_ptr->b=100;
}
在上面代码中,注意child_class结构中第一个成员p,这种声明方式代表child_class类型的数据中开始的位置包含一个parent_class类型的变量。
在函数func中obj是一个child_class对象,正像这个结构类型指示的,它前面的数据应该包含一个parent_class类型的数据。
在第21行的强制类型赋值中parent_ptr指向了obj变量的首地址,也就是obj变量中的p对象。
好了,现在parent_ptr指向的是一个真真实实的parent类型的结构,那么可以按照parent的方式访问其中的成员,当然也包括可以使用和parent结构相关的函数来处理内部数据,因为一个正常的,正确的代码,它是不会越界访问parent结构体以外的数据。
经过这基本的结构体层层相套包含,对象简单的继存关系就体现出来了:
父对象放于数据块的最前方,代码中可以通过强制类型转换获得父对象指针。
2.1.3多态
对象根据所接收的消息而做出动作。
同一消息为不同的对象接受时可产生完全不同的行动,这种现象称为多态性。
利用多态性用户可发送一个通用的信息,而将所有的实现细节都留给接受消息的对象自行决定,如是,同一消息即可调用不同的方法。
例如:
RT-Thread系统中的设备:
抽象设备具备接口统一的读写接口。
串口是设备的一种,也应支持设备的读写。
但串口的读写操作是串口所特有的,不应和其他设备操作完全相同,例如操作串口的操作不应应用于SD卡设备中。
多态性的实现受到继承性的支持,利用类继承的层次关系,把具有通用功能的协议存放在类层次中尽可能高的地方,而将实现这一功能的不同方法置于较低层次,这样,在这些低层次上生成的对象就能给通用消息以不同的响应。
RT-Thread对象模型采用结构封装中使用指针的形式达到面向对象中多态的效果,例如:
/*抽象父类*/
structparent_class
{
inta;
/*反映不同类别属性的方法*/
void(*vfunc)(inta);
}
/*抽象类的方法调用*/
voidparent_class_vfunc(structparent_class*self,inta)
{
assert(self!
=NULL);
assert(slef->vfunc!
=NULL);
/*调用对象本身的虚拟函数*/
self->vfunc(a);
}
/*继承自parent_class的子类*/
structchild_class
{
structparent_classparent;
intb;
};
/*子类的构造函数*/
voidchild_class_init(structchild_class*self)
{
structparent_class*parent;
/*强制类型转换获得父类指针*/
parent=(structparent_class*)self;
assert(parent!
=NULL);
/*设置子类的虚拟函数*/
parent->vfunc=child_class_vfunc;
}
/*子类的虚拟函数实现*/
staticvoid_child_class_vfunc(structchild_class*self,inta)
{
self->b=a+10;
}
2.2内核对象模型
2.2.1静态对象和动态对象
RT-Thread的内核映像文件在编译时会形成如下图所示的结构(以STM32、KeilMDK为例):
其中主要包括了这么几段:
段名称
描述
ER_IROM1
代码正文段,以及只读数据。
GCC中一般是.text和.rodata段
RW_IRAM1
数据段,通常又分为
存放带初始值的RW_IRAM1$$RW
和
存放无初始值的RW_IRAM1$$ZI
相对应的,在GCC中一般称为.data段和.bss段。
如下图所示,在STM32上,分成了RW_IRAM和ER_IROM
当系统运行时,这些段也会相应的映射到内存中。
在RT-Thread系统初始化时,通常ZI或.bss段会清零,而堆(Heap)则是RW_IRAM上除了RW和ZI以外可用的内存空间(具体的地址空间在系统启动时由链接时的参数指定),系统运行时动态分配的内存块就在堆的空间中分配出来的,如下代码:
rt_uint8_t*msg_ptr;
msg_ptr=(rt_uint8_t*)rt_malloc(128);
rt_memset(msg_ptr,0,128);
msg_ptr指向的128字节内存空间位于堆空间中。
而一些全局变量则是存放于RW_IRAM1$$RW,RW_IRAM1$$ZI中,RW_IRAM1$$RW存放的是具有初始值的全局变量(而常量形式的全局变量则放置在ER_IROM1段中,是只读属性的),如下代码:
#include
conststaticrt_uint32_tsensor_enable=0x000000FE;
rt_uint32_tsensor_value;
rt_bool_tsensor_inited=RT_FALSE;
voidsensor_init()
{
/*...*/
}
sensor_value存放在RW_IRAM1$$ZI段中,系统启动后会自动初始化成零。
sensor_inited变量则存放在RW_IRAM1$$RW段中,而sensor_enable存放在RW_IROM1段中。
在RT-Thread内核对象中分为两类:
静态内核对象和动态内核对象。
静态内核对象通常放在RW_IRAM1$$RW、RW_IRAM1$$ZI段中,在系统启动后在程序中初始化;动态内核对象则是从堆中创建的,而后手工做初始化。
RT-Thread中操作系统级的设施都是一种内核对象,例如线程,信号量,互斥量,定时器等。
以下的代码所示的即为静态线程和动态线程的例子:
/*线程1的对象和运行时用到的栈*/
staticstructrt_threadthread1;
staticrt_uint8_tthread1_stack[512];
/*线程1入口*/
voidthread1_entry(void*parameter)
{
inti;
while
(1)
{
for(i=0;i<10;i++)
{
rt_kprintf("%d\n",i);
/*延时100个OSTick*/
rt_thread_delay(100);
}
}
}
/*线程2入口*/
voidthread2_entry(void*parameter)
{
intcount=0;
while
(1)
{
rt_kprintf("Thread2count:
%d\n",++count);
/*延时50个OSTick*/
rt_thread_delay(50);
}
}
/*用户应用程序入口*/
intrt_application_init()
{
rt_thread_tthread2_ptr;
rt_err_tresult;
/*初始化线程1*/
/*线程的入口是thread1_entry,参数是RT_NULL
*线程栈是thread1_stack
*优先级是200,时间片是10个OSTick
*/
result=rt_thread_init(&thread1,
"thread1",
thread1_entry,RT_NULL,
&thread1_stack[0],sizeof(thread1_stack),
200,10);
/*启动线程*/
if(result==RT_EOK)rt_thread_startup(&thread1);
/*创建线程2*/
/*线程的入口是thread2_entry,参数是RT_NULL
*栈空间是512,优先级是250,时间片是25个OSTick
*/
thread2_ptr=rt_thread_create("thread2",
thread2_entry,RT_NULL,
512,250,25);
/*启动线程*/
if(thread2_ptr!
=RT_NULL)rt_thread_startup(thread2_ptr);
return0;
}
例子中,thread1是一个静态线程对象,而thread2是一个动态线程对象。
thread1对象的内存空间,包括线程控制块thread1,栈空间thread1_stack都是编译时决定的,因为代码中都不存在初始值,都统一放在RW_IRAM1$$ZI段中。
thread2运行中用到的空间都是动态分配的,包括线程控制块(thread2_ptr指向的内容)和栈空间。
2.2.2内核对象管理工作模式
RT-Thread采用内核对象管理系统来访问/管理所有内核对象。
内核对象包含了内核中绝大部分设施,而这些内核对象可以是静态分配的静态对象,也可以是从系统内存堆中分配的动态对象。
通过内核对象系统,RT-Thread做到了不依赖于具体的内存分配方式,系统的灵活性得到极大的提高。
RT-Thread内核对象包括:
线程,信号量,互斥锁,事件,邮箱,消息队列和定时器,内存池,设备驱动等。
对象容器中包含了每类内核对象的信息,包括对象类型,大小等。
对象容器给每类内核对象分配了一个链表,所有的内核对象都被链接到该链表上。
下图显示了RT-Thread中各类内核对象的派生和继承关系。
对于每一种具体内核对象和对象控制块,除了基本结构外,还有自己的扩展属性(私有属性),例如,对于线程控制块,在基类对象基础上进行扩展,增加了线程状态、优先级等属性。
这些属性在基类对象的操作中不会用到,只有在与具体线程相关的操作中才会使用。
因此从面向对象的观点,可以认为每一种具体对象是抽象对象的派生,继承了基本对象的属性并在此基础上扩展了与自己相关的属性。
在对象管理模块中,定义了通用的数据结构,用来保存各种对象的共同属性,各种具体对象只需要在此基础上加上自己的某些特别的属性,就可以清楚的表示自己的特征。
这种设计方法的优点:
1.提高了系统的可重用性和扩展性,增加新的对象类别很容易,只需要继承通用对象的属性再加少量扩展即可。
2.提供统一的对象操作方式,简化了各种具体对象的操作,提高了系统的可靠性。
2.2.3对象控制块
structrt_object
{
/*内核对象名称*/
charname[RT_NAME_MAX];
/*内核对象类型*/
rt_uint8_ttype;
/*内核对象的参数*/
rt_uint8_tflag;
/*内核对象管理链表*/
rt_list_tlist;
};
目前内核对象支持的类型如下:
enumrt_object_class_type
{
RT_Object_Class_Thread=0,/*对象为线程类型*/
#ifdefRT_USING_SEMAPHORE
RT_Object_Class_Semaphore,/*对象为信号量类型*/
#endif
#ifdefRT_USING_MUTEX
RT_Object_Class_Mutex,/*对象为互斥锁类型*/
#endif
#ifdefRT_USING_EVENT
RT_Object_Class_Event,/*对象为事件类型*/
#endif
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- RTThread 移植 技术 文档 分析