第4章RT-Thread内存管理器在计算系统中,一般需要一定的存储空间来存放变量或中间数据,按照存储方式来分类可以分为两种,内部存储空间和外部存储空间.
内部存储空间通常访问速度比较快,能够随机访问(按照变量地址).
这一章主要讨论内部存储空间的管理.
实时系统中由于它对时间要求的严格性,其中的内存分配往往要比通用操作系统苛刻得多:首先,分配内存的时间必须是确定性的.
一般内存管理算法是搜索一个适当范围的空间去寻找适合长度的空闲内存块,然而这对于实时系统是不可接受的,因为实时系统必须要保证内存块的分配必须在确定的时间内完成,否则实时任务在对外部事响应也将变得时间不可确定,例如一个处理外部数据的例子:当一个外部数据达到时(通过传感器,或者一些网络数据包),为了把它提交给上层的任务进行处理,它可能会先申请一块内存,把数据块的地址附加上,还可能有,数据长度,以及一些其他信息附加在一起(放在一个结构体中),然后提交给上层任务.
内存申请是当中的一个组成环节,如果因为使用的内存占用比较零乱,从而操作系统需要搜索一个不确定性长度的队列寻找适合的内存,那么申请时间将变得不可确定,进而对整个响应时间产生不可确定性.
如果此时是一次导弹袭击,估计很可能会灰飞烟灭了!
其次,随着使用的内存分块被释放,整个内存区域会产生越来越多的碎片,从总体上来说,分配内存,处理数据,调整位置刚好分配到内存,逃过一劫分配内存,处理数据还差点点分配到内存,天啊!
!
第一次:第N次:系统中还有足够的空闲内存,但因为它们不在一起,不能组成一块连续的内存块,从而造成程序不能申请到内存.
对于通用系统而言,这种不恰当的内存分配算法可以通过重新启动系统来解决(每个月或者数个月进行一次),但是这个对于嵌入式系统来说是不可接受的,他们通常需要连续不断地运行数年.
最后,嵌入式系统的资源环境也不是都相同,有些系统中资源比较紧张,只有数百KB的内存可供分配,而有些系统则存在数MB的内存.
RT-Thread操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性的了数种内存分配算法:静态分区内存管理及动态内存管理.
动态内存管理又划分为两种情况,一种是针对小内存块的分配管理,一种是针对大内存块的分配管理.
4.
1RT-Thread的的的的内核对象内核对象内核对象内核对象管理管理管理管理在讨论RT-Thread内存管理器前,有必要详细描述下RT-Thread的内核对象系统.
RT-Thread的内核映像文件在编译时会形成如下图所示的结构(以lumit4510为例):其中主要包括了这么几段:表4-1运行目标段段描述.
text代码正文段.
data数据段,用于放置带初始值的全局变量.
rodata只读数据段,用于放置只读的全局变量(常量).
bssbss段,用于放置未初始化的全局变量图4-1lumit4510运行时内存映像图当系统运行时,这些段也会相应的映射到内存中.
在RT-Thread系统初始化时,通常bss段会清零,而堆(Heap)则是除了以上这些段以外可用的内存空间(具体的地址空间在系统启动时由参数指定),系统运行时动态分配的内存块就在堆的空间中分配出来的,如程序4-1:程序4-1rt_uint8_t*msg;msg=(rt_uint8_t*)rt_malloc(128);rt_memset(msg,0,128);.
text.
rodata.
data.
bss0x000080000x0001ABE40x0001B5480x0001B7E80x0001EB70HeapBeginmsg_ptr指向的内存空间就是处于堆空间中的.
而一些全局变量则是存放于.
data和.
bss段中,.
data存放的是具有初始值的全局变量(.
rodata可以看成一个特殊的data段,是只读的),如程序4-2:程序4-2#includeconststaticrt_uint32_tsensor_enable=0x000000FE;rt_uint32_tsensor_value;rt_bool_tsensor_inited=RT_FALSE;voidsensor_init(){…}…sensor_value存放在.
bss段中,系统启动后会自动初始化成零.
sensor_inited变量则存放在.
data段中,而sensor_enable存放在.
rodata端中.
在RT-Thread内核对象中分为两类:静态内核对象和动态内核对象.
静态内核对象通常放在.
data或.
bss段中,在系统启动后在程序中初始化;动态内核对象则是从堆中创建的,而后手工做初始化.
内核对象API命名规则rt_xxx_init初始化系统静态内核对象,它只会对对象所在的内存块进行初始化,并添加到系统容器的链表中进行管理.
rt_xxx_detach脱离内核对象容器管理,它会将对象从系统容器的链表中脱离出来,但不会去释放内存块.
rt_xxx_create创建一个动态内核对象,它会先申请出一块空间,然后进行初始化并添加到系统容器的链表中进行管理.
rt_xxx_delete它会将对象从系统容器的链表中删除出来,并是否所占用的内存块.
RT-Thread中操作系统级的设施都是一种内核对象,例如线程,信号量,互斥量,定时器等.
如程序4-3中所示的静态线程和动态线程的例子:程序4-3静态内核对象与动态内核对象staticrt_uint8_tthread1_stack[512];staticstructrt_threadthread1;/*线程1入口*/voidthread1_entry(void*parameter){inti;while(1){for(i=0;i<10;i++){rt_kprintf("%d\n",i);rt_thread_delay(100);}}}/*线程2入口*/voidthread2_entry(void*parameter){intcount=0;while(1){rt_kprintf("Thread2count:%d\n",++count);rt_thread_delay(50);}}/*用户应用程序入口*/intrt_application_init(){rt_thread_tthread2_ptr;rt_thread_init(&thread1,"thread1",thread1_entry,RT_NULL,&thread1_stack[0],sizeof(thread1_stack),200,10);thread2_ptr=rt_thread_create("thread2",thread2_entry,RT_NULL,512,250,25);rt_thread_startup(&thread1);rt_thread_startup(thread2_ptr);return0;}例子中,thread1是一个静态线程对象,而thread2是一个动态线程对象.
thread1对象的内存空间,包括threadtcb:thread1,栈空间thread1_stack都是编译时刻决定的,因为都不存在初始值,都统一放在.
bss段中.
thread2运行中用到的空间都是动态分配的,包括threadtcb(thread2_ptr指向的内容)和栈空间.
4.
1.
1内核对象内核对象内核对象内核对象管理工作模式管理工作模式管理工作模式管理工作模式图4-1内核对象管理器结构图系统采用内核对象管理系统来访问/管理所有内核对象.
内核对象包含了内核中绝大部分设施,而这些内核对象可以是静态分配的静态对象,也可以是从系统内存堆中分配的动态对象.
通过内核对象系统,RT-Thread可以做到不依赖于具体的内存分配方式,伸缩性得到极大的扩展.
RT-ThreadRTOS使用C语言和汇编语言开发,本来不具备面向对象的特征.
但这里,RT-Thread借用了面向对象语言中对象的概念,定义了对象管理模块.
系统中线程同步和通信机制对象都是从object中继承而来,具备object的属性,并由系统的对象容器统一分配并管理.
内核对象管理器结构如图4-1所示,RT-Thread内核对象包括:线程,信号量,互斥锁,事件,邮箱,消息队列和定时器,内存池.
对象容器中包含了每类内核对象的信息,包括对象类型,大小等.
对象容器给每类内核对象分配了一个链表,所有的内核对象都被链接到该链表上.
对于每一种具体内核对象和对象控制块,除了基本结构外,还有自己的扩展属性(私有属性),例如,对于线程控制块,在此基类对象基础上进行扩展,增加了线程状态、优先级对象容器线程信号量互斥量事件邮箱消息队定时器线程对象对象链表信号量对象互斥量对象事件对象邮箱对象消息队列对象定时器对象等属性.
这些属性在基类对象的基本操作中不会用到,只有在与具体线程相关的函数才会使用.
因此用面向对象的观点,我们可以认为每一种具体对象是抽象对象的派生,继承了基本对象的属性并在此基础上增加与自己相关的属性.
图4-2显示了RT-Thread中各类内核对象的派生和继承关系.
图4-2内核对象的继承和派生关系图在对象管理模块中,定义了通用的数据结构,用来保存各种对象的共同属性,各种具体对象只需要在此基础上加上自己的某些特别的属性,就可以清楚的表示自己的特征.
这种设计方法的优点:1)提高了系统的可重用性和扩展性,增加新的对象类别很容易,只需要继承通用对象的属性再加少量扩展即可.
2)提供统一的对象操作方式,简化了各种具体对象的操作,提高了系统的可靠性.
4.
1.
2对象控制块对象控制块对象控制块对象控制块structrt_object{/*nameofkernelobject*/charname[RT_NAME_MAX];/*typeofkernelobject*/rt_uint8_ttype;/*flagofkernelobject1*/rt_uint8_tflag;/*listpointerofkernelobject*/rt_list_tlist;};基对象IPC对象定时器对信号量对象互斥量对象邮箱对象事件对象消息队列对内存池对线程对象4.
1.
3内核对象接口内核对象接口内核对象接口内核对象接口4.
1.
3.
1初始化系统对象初始化系统对象初始化系统对象初始化系统对象在初始化各种内核对象之前,首先需对对象管理系统进行初始化.
在系统中,每类内核对象都有一个静态对象容器,一个静态对象容器放置一类内核对象,初始化对象管理系统的任务就是初始化这些对象容器,使之能够容纳各种内核对象,初始化系统对象使用以下接口:voidrt_system_object_init(void)以下是对象容器的数据结构:structrt_object_information{enumrt_object_class_typetype;/*objectclasstype.
*/rt_list_tobject_list;/*objectlist.
*/rt_size_tobject_size;/*objectsize.
*/};一种类型的对象容器维护了一个对象链表object_list,所有对于内核对象的分配,释放操作均在该链表上进行.
4.
1.
3.
2初始化对象初始化对象初始化对象初始化对象使用对象前须先对其进行初始化.
初始化对象使用以下接口:voidrt_object_init(structrt_object*object,enumrt_object_class_typetype,constchar*name)对象初始化,实际上就是把对象放入到其相应的对象容器中,即将对象插入到对象容器链表中.
4.
1.
3.
3脱离脱离脱离脱离对象对象对象对象从内核对象管理器中脱离一个对象.
脱离对象使用以下接口:voidrt_object_detach(rt_object_tobject)使用该接口后,静态内核对象将从内核对象管理器中脱离,但是对象占用的内存不会被释放.
4.
1.
3.
4分配对象分配对象分配对象分配对象在当前内核中,共定义了十类内核对象,这些内核对象被广泛的用于线程的管理,线程之间的同步,通信等.
因此,系统随时需要新的对象来完成这些操作,分配新对象使用以下接口:rt_object_trt_object_allocate(enumrt_object_class_typetype,constchar*name)使用以上接口,首先根据对象类型来获取对象信息,然后从内存堆中分配对象所需内存空间,然后对该对象进行必要的初始化,最后将其插入到它所在的对象容器链表中.
4.
1.
3.
5删除对象删除对象删除对象删除对象不再使用的对象应该立即被删除,以释放有限的系统资源.
删除对象使用以下接口:voidrt_object_delete(rt_object_tobject)使用以上接口时,首先从对象容器中脱离对象,然后释放对象所占用的内存.
4.
1.
3.
6查找对象查找对象查找对象查找对象通过指定的对象类型和对象名查找对象,查找对象使用以下接口:rt_object_trt_object_find(enumrt_object_class_typetype,constchar*name)使用以上接口时,在对象类型所对应的对象容器中遍历寻找指定对象,然后返回该对象,如果没有找到这样的对象,则返回空.
4.
1.
3.
7辨别对象辨别对象辨别对象辨别对象判断指定对象是否是系统对象(静态内核对象).
辨别对象使用以下接口:rt_err_trt_object_is_systemobject(rt_object_tobject)通常采用rt_object_init方式挂接到内核对象管理器中的对象是系统对象.
4.
2静态分区内存管理静态分区内存管理静态分区内存管理静态分区内存管理4.
2.
1内存池工作模式内存池工作模式内存池工作模式内存池工作模式图4-19内存池管理结构示意图内存池(MemoryPool)是一种用于分配大量大小相同的小对象的技术.
它可以极大加快内存分配/释放过程.
内存池在创建时向系统申请一大块内存,然后分成同样大小的多个小内存块,形成链表连接起来(此链表也称为空闲链表).
每次分配的时候,从空闲链表中取出头上一块,提供给申请者.
如图4-19所示,物理内存中可以有多个大小不同的内存池,一个内存池由多个空闲内存块组成,内核用它们来进行内存管理.
当一个内存池对象被创建时,内存池对象就被分配了内存池控制块:内存池名,内存缓冲区,内存块大小,块数以及一个等待线程队列.
内核负责给内存池分配内存池对象控制块,它同时也接收用户线程传入的参数,像内存块大小以及块数,由这些来确定内存池对象所需内存大小,当获得了这些信息后,内核就可以从内存堆或者线程私有内存空间中为内存池分配内存.
4.
2.
2内存池控制块内存池控制块内存池控制块内存池控制块structrt_mempool{structrt_objectparent;void*start_address;/*memorypoolstart*/rt_size_tsize;/*sizeofmemorypool*/内存块控制块块大小/32k块数/128内存池2内存池1物理内存线程线程线程等待线程队列块大小/16k块数/128rt_size_tblock_size;/*sizeofmemoryblocks*/rt_uint8_t*block_list;/*memoryblockslist*/rt_size_tblock_total_count;/*numbersofmemoryblock*/rt_size_tblock_free_count;/*numbersoffreememoryblock*/rt_list_tsuspend_thread;/*threadspendedonthispool*/rt_size_tsuspend_thread_count;/*numbersofthreadpendedonthispool*/};4.
2.
3内存池接口内存池接口内存池接口内存池接口4.
2.
3.
1创建内存池创建内存池创建内存池创建内存池创建内存池操作将会创建一个内存池对象并且从堆上分配一个内存池.
创建内存池是分配,释放内存块的基础,创建该内存池后,线程便可以从内存池中完成申请,释放操作,创建内存池使用如下接口,接口返回一个已创建的内存池对象.
rt_mp_trt_mp_create(constchar*name,rt_size_tblock_count,rt_size_tblock_size);使用该接口可以创建与需求相匹配的内存块大小和数目的内存池,前提是在系统资源允许的情况下.
创建内存池时,需要给内存池指定一个名称.
根据需要,内核从系统中申请一个内存池对象,然后从内存堆中分配一块由块数目和块大小计算得来的内存大小,接着初始化内存池对象结构,并将申请成功的内存缓冲区组织成可用于分配的空闲块链表.
4.
2.
3.
2删除内存池删除内存池删除内存池删除内存池删除内存池将删除内存池对象并释放申请的内存.
使用如下接口:rt_err_trt_mp_delete(rt_mp_tmp)删除内存池时,必须首先唤醒等待在该内存池对象上的所有线程,然后再释放已从内存堆上分配的内存,然后删除内存池对象.
4.
2.
3.
3初始化内存池初始化内存池初始化内存池初始化内存池初始化内存池跟创建内存池类似,只是初始化邮箱用于静态内存管理模式,内存池控制块来源于用户线程在系统中申请的静态对象.
还与创建内存池不同的是,此处内存池对象所使用的内存空间是由用户线程指定的一个缓冲区空间,用户把缓冲区的指针传递给内存池对象控制块,其余的初始化工作与创建内存池相同.
接口如下:rt_err_trt_mp_init(structrt_mempool*mp,constchar*name,void*start,rt_size_tsize,rt_size_tblock_size)初始化内存池时,把需要进行初始化的内存池对象传递给内核,同时需要传递的还有内存池用到的内存空间,以及内存池管理的内存块数目和块大小,并且给内存池指定一个名称.
这样,内核就可以对该内存池进行初始化,将内存池用到的内存空间组织成可用于分配的空闲块链表.
4.
2.
3.
4脱离内存池脱离内存池脱离内存池脱离内存池脱离内存池将使内存池对象被从内核对象管理器中删除.
脱离内存池使用以下接口.
rt_err_trt_mp_detach(structrt_mempool*mp)使用该接口后,内核先唤醒所有挂在该内存池对象上的线程,然后将该内存池对象从内核对象管理器中删除.
4.
2.
3.
5分配内存块分配内存块分配内存块分配内存块从指定的内存池中分配一个内存块,使用如下接口:void*rt_mp_alloc(rt_mp_tmp,rt_int32time)如果内存池中有可用的内存块,则从内存池的空闲块链表上取下一个内存块,减少空闲块数目并返回这个内存块,如果内存池中已经没有空闲内存块,则判断超时时间设置,若超时时间设置为零,则立刻返回空内存块,若等待大于零,则把当前线程挂起在该内存池对象上,直到内存池中有可用的自由内存块,或等待时间到达.
4.
2.
3.
6释放内存块释放内存块释放内存块释放内存块任何内存块使用完后都必须被释放,否则会造成内存泄露,释放内存块使用如下接口:voidrt_mp_free(void*block)使用以上接口时,首先通过需要被释放的内存块指针计算出该内存块所在的内存池对象,然后增加内存池对象的可用内存块数目,并把该被释放的内存块加入空闲内存块链表上.
接着判断该内存池对象上是否有挂起的线程,如果有,则唤醒挂起线程链表上的首线程.
4.
3动态内存管理动态内存管理动态内存管理动态内存管理动态内存管理是一个真实的堆内存管理模块,可以根据用户的需求(在当前资源满足的情况下)分配任意大小的内存块.
RT-Thread系统中为了满足不同的需求,提供了两套动态内存管理算法,分别是小堆内存管理和SLAB内存管理.
下堆内存管理模块主要针对系统资源比较少,一般小于2M内存空间的系统;而SLAB内存管理模块则主要是在系统资源比较丰富时,提供了一种近似的内存池管理算法.
两种内存管理模块在系统运行时只能选择其中之一(或者完全不使用动态堆内存管理器),两种动态内存管理模块API形式完全相同.
注:不要在中断服务例程中分配或释放动态内存块.
4.
3.
1小内存管理模块小内存管理模块小内存管理模块小内存管理模块小内存管理算法是一个简单的内存分配算法,当有可用内存的时候,会从中分割出一块来作为分配的内存,而余下的则返回到动态内存堆中.
如图4-5所示当用户线程要分配一个64字节的内存块时,空闲链表指针lfree初始指向0x0001EC00内存块,但此内存块只有32字节并不能满足要求,它会继续寻找下一内存块,此内存块大小为128字节满足分配的要求.
分配器将把此内存块进行拆分,余下的内存块(52字节)继续留在lfree链表中.
如图4-8所示在分配的内存块前约12字节会存放内存分配器管理用的私有数据,用户线程不应访问修改它,这个头的大小会根据配置的对齐字节数稍微有些差别.
释放时则是相反的过程,但分配器会查看前后相邻的内存块是否空闲,如果空闲则合并成一个大的空闲内存块.
4.
3.
2SLAB内存管理模块内存管理模块内存管理模块内存管理模块RT-Thread实现的SLAB分配器是在MatthewDillon在DragonFlyBSD中实现的SLAB分配器基础上针对嵌入式系统优化过的内存分配算法.
原始的SLAB算法是JeffBonwick为Solaris操作系统首次引入的一种高效内核内存分配算法.
RT-Thread的SLAB分配器实现主要是去掉了其中的对象构造及析构过程,只保留了纯粹的缓冲型的内存池算法.
SLAB分配器会根据对象的类型(主要是大小)分成多个区(zone),也可以看成每类对象有一个内存池,如图所示:3252lfree0x0001EC000x0001EC200x0002004C0x000200800x010000001264Allocatedmemoryblock32128lfree0x0001EC000x0001EC200x000200000x000200800x01000000一个zone的大小在32k~128k字节之间,分配器会在堆初始化时根据堆的大小自动调整.
系统中最多包括72种对象的zone,最大能够分配16k的内存空间,如果超出了16k那么直接从页分配器中分配.
每个zone上分配的内存块大小是固定的,能够分配相同大小内存块的zone会链接在一个链表中,而72种对象的zone链表则放在一个数组(zone_arry)中统一管理.
动态内存分配器主要的两种操作:内存分配:假设分配一个32字节的内存,SLAB内存分配器会先按照32字节的值,从zone_array链表表头数组中找到相应的zone链表.
如果这个链表是空的,则向页分配器分配一个新的zone,然后从zone中返回第一个空闲内存块.
如果链表非空,则这个zone链表中的第一个zone节点必然有空闲块存在(否则它就不应该放在这个链表中),然后取相应的空闲块.
如果分配完成后,导致一个zone中所有空闲内存块都使用完毕,那么分配器需要把这个zone节点从链表中删除.
内存释放:分配器需要找到内存块所在的zone节点,然后把内存块链接到zone的空闲内存块链表中.
如果此时zone的空闲链表指示出zone的所有内存块都已经释放,即zone是完全空闲的zone.
当中zone链表中,全空闲zone达到一定数目后,会把这个全空闲的zone释放到页面分配器中去.
4.
3.
3动态内存接口动态内存接口动态内存接口动态内存接口4.
3.
3.
1初始化系统堆空间初始化系统堆空间初始化系统堆空间初始化系统堆空间在使用堆内存时,必须要在系统初始化的时候进行堆内存的初始化,可以通过如下接口完成:voidrt_system_heap_init(void*begin_addr,void*end_addr);入口参数分别为堆内存的其实地址和结束地址.
zone1#zone2#…zonen#Objectsize=32zone1##zone1##freeblocklist4.
3.
3.
2分配内存块分配内存块分配内存块分配内存块从内存堆上分配用户线程指定大小的内存块,接口如下:void*rt_malloc(rt_size_tnbytes);用户线程需指定申请的内存空间大小,成功时返回分配的内存块地址,失败时返回RT_NULL.
4.
3.
3.
3重分配内存块重分配内存块重分配内存块重分配内存块在已分配内存块的基础上重新分配内存块的大小(增加或缩小),可以通过如下接口完成:void*rt_realloc(void*rmem,rt_size_tnewsize);在进行重新分配内存块时,原来的内存块数据保持不变(缩小的情况下,后面的数据被自动截断).
4.
3.
3.
4分配多内存块分配多内存块分配多内存块分配多内存块从内存堆中分配连续内存地址的多个内存块,可以通过如下接口完成:void*rt_calloc(rt_size_tcount,rt_size_tsize);返回的指针指向第一个内存块的地址,并且所有分配的内存块都被初始化成零.
2021年6月底,raksmart开发出来的新产品“cloud-云服务器”正式上线对外售卖,当前只有美国硅谷机房(或许以后会有其他数据中心加入)可供选择。或许你会问raksmart云服务器怎么样啊、raksm云服务器好不好、网络速度快不好之类的废话(不实测的话),本着主机测评趟雷、大家受益的原则,先开一个给大家测评一下!官方网站:https://www.raksmart.com云服务器的说明:底层...
imidc怎么样?imidc彩虹数据或彩虹网络现在促销旗下日本多IP站群独立服务器,原价159美元的机器现在只需要88美元,而且给13个独立IPv4,30Mbps直连带宽,不限制月流量!IMIDC又名为彩虹数据,rainbow cloud,香港本土运营商,全线产品都是商家自营的,自有IP网络资源等,提供的产品包括VPS主机、独立服务器、站群独立服务器等,数据中心区域包括香港、日本、台湾、美国和南非...
BuyVM商家算是一家比较老牌的海外主机商,公司设立在加拿大,曾经是低价便宜VPS主机的代表,目前为止有提供纽约、拉斯维加斯、卢森堡机房,以及新增加的美国迈阿密机房。如果我们有需要选择BuyVM商家的机器需要注意的是注册信息的时候一定要规范,否则很容易出现欺诈订单,甚至你开通后都有可能被禁止账户,也是这个原因,曾经被很多人吐槽的。这里我们简单的对于BuyVM商家新增加的迈阿密机房进行简单的测评。如...