内存管理:静态分配和动态分配的完美结合
先看看我们面临的需求:产品A:有一个RS485口,可以挂0~256个节点,但不能带电增加和减少节点。软件上,上电初始化时,要为每个节点分配一个缓冲区。
产品B:一个通信装置,相同的CPU平台上,因为所配的接口数量不一样,衍生出多个型号。软件上,也要要为每个接口分配一个缓冲区。
需求C:在OS下编程,有许多常驻内存的线程,这些线程在整个运行周期内是不会退出运行的,同时运行着许多用户线程,这些线程是动态创建和销毁的。
我们知道,内存分配分为动态分配和静态分配两种,他们各有特点。
静态分配是在rw段分配,由编译器执行,快速,高效,无开销,无碎片,无泄漏,如果内存不足,编译时就能知道。缺点是不灵活,不能释放,不能满足动态需求。
动态分配则是在运行时,从堆中分配,执行速度慢,内存利用率低,可能产生碎片和内存泄漏,优点嘛,恰好就是静态分配的缺点。
可以说,动态分配和静态分配的优缺点是一一对应的。我们从内存分配的角度,分析一下实现上述三种需求的不同方法。
产品A:
方案1,静态分配,按最大可能,分配256块缓冲区,优点自不必说,缺点是内存开销太大。
方案2,动态分配,在上电初始化时,检测连接的节点数,按实际连接的节点数动态分配缓冲区。缺点是要付出动态分配的开销,如果节点数接近256的话,需要的内存可能比静态分配还要多。
理想方案:按照实际连接的节点数,“静态”地分配缓冲区。不行吗?djyos可以这样,且看后面分析。
产品B:
其实和A很类似,但从项目经理以及产品档案管理和维护的角度,A和B是不同的。
方案1和方案2和A一样。
方案3:每个型号按照其实际接口数量,静态分配内存。缺点是每个型号的软件不一样,源代码至少有一个宏不一样,用来控制接口数量,二进制的代码页不一样。每一次的版本升级、bug修正,都需要每个型号单独重新编译。
理想方案:同A,但所有型号保持从源代码和二进制代码一致。
需求C:我们知道,每个线程,都需要分配运行栈,如果使用静态分配方案,就不能动态创建和删除线程,这在OS中是不允许的,如果用动态分配方案,那么常驻内存的线程,用动态分配就太浪费了。因此有一些OS,会提供两套创建线程的函数,分别对应静态线程和动态线程。即使如此,亦有问题,比如一个模块,在产品x中其线程是静态的,在产品y中是动态的,则不能直接移植。两者能兼容吗?djyos是可以的。
在djyos中,提出了准静态分配的概念,在必要的初始化完成前,先执行静态堆初始化,此后调用malloc函数,结果执行的是从堆中静态分配,好像堆的底部成了RW段一样,这时候分配器的内存,可以释放,但要求严格按照“malloc-free”成对出现的方式,否则虽然不会出错,但free变成空操作。初始化完成后,将执行动态堆初始化,此后调用malloc动态分配,就和和其他OS的动态分配一样,此时malloc和free可以按照任意顺序调用。
过程是:
上电启动;
调用静态堆初始化;
提供appinit接口,应用程序可以在这里调用需要静态初始化的模块,比如扫描RS485节点数。此时,调用malloc分配内存,实际执行的是静态分配。
调用动态堆初始化;----此后调用malloc函数,将执行真正的动态分配。
启动多事件调度;
调用main函数;----在main中调用的初始化,或者创建线程,如果调用malloc,执行的当然就是动态分配了。
诸位看官,看出玄机来了吧,对于产品A和B,只要在动态堆初始化执行完之前,调用节点扫描程序,就会按照实际配置的节点数,“静态”地分配缓冲区。对于需求C也一样,进一步地,如果一个模块,在产品x中其线程是静态的,在产品y中是动态的,代码是完全一致的,二进制级别一致。完全取决于你是在appinit接口中调用,还是main函数中调用。 没看明白这说的是什么,,倒觉得很多RTOS都提供使用静态分配的数组做为动态分配的内存池。。。
是偷换概念吗???
还是我太笨了没看明白。。 简单地说,你需要静态分配,却又不能在编译时知道需要分配多少内存的时候,djyos给你足够的照顾,你调用的是malloc,但执行的却是静态分配,得到的也是静态分配的结果。
又或者说,你在代码中有一个静态变量比如:
intbuf;
在编译时,你不知道x应该填多少,就像前面讲到的产品A,你不知道用户在RS485总线上连接了几个节点,djyos允许你在产品上电的时候才填入x值。 估计没人看懂你的意思。。
{在djyos中,提出了准静态分配的概念,在必要的初始化完成前,先执行静态堆初始化,此后调用 malloc函数,结果执行的是从堆中静态分配,好像堆的底部成了RW段一样}
。。。。。。。。。。。。。。。。。。。。。。。。 回复【3楼】jijuxie321
估计没人看懂你的意思。。
{在djyos中,提出了准静态分配的概念,在必要的初始化完成前,先执行静态堆初始化,此后调用 malloc函数,结果执行的是从堆中静态分配,好像堆的底部成了RW段一样}
。。。。。。。。。。。。。。。。。。。。。。。。
-----------------------------------------------------------------------
改了一下,看能看懂了不。 我看了下,大概意思应该是
初始化时 可以有动态扩展静态分配内存区的机会
应该是一旦进行了动态内存分配,就不能再“准静态分配了”
支持下打酱油OS~ 这段描述的感觉还不是太清晰。
我认为,基于Block的内存分配算法,典型如uCOS/II这样的,分配掉而不Free,天然具有LZ指出的“准静态”结果。多Block Size的的算法我没怎么了解过,简单分析之下,结论不变。
基于链表或者类似数据结构的,不等分配大小的堆算法;我理解到的实质就是开始的时候保存一个备用分配的单向链表,要求Malloc/Free成对,在某个时刻之后销毁;建立正常数据结构,执行通常意义下的堆操作。。。
LZ提出的应用场景确实有实用价值,但对是否需要如此统一接口持保留观点。
在初始堆内存中,调用A接口,静态分配掉一些内存;剩余的二次初始化,丢给Malloc分配之用,不同的函数完成不同意义下的功能,更清晰。 回复【6楼】dr2001
这段描述的感觉还不是太清晰。
我认为,基于Block的内存分配算法,典型如uCOS/II这样的,分配掉而不Free,天然具有LZ指出的“准静态”结果。多Block Size的的算法我没怎么了解过,简单分析之下,结论不变。
基于链表或者类似数据结构的,不等分配大小的堆算法;我理解到的实质就是开始的时候保存一个备用分配的单向链表,要求Malloc/Free成对,在某个时刻之后销毁;建立正常数据结构,执行通常意义下的堆操作。。。
LZ提出的应用场景确实有实用价值,但对是否需要如此统一接口持保留观点。
在初始堆内存中,调用A接口,静态分配掉一些内存;剩余的二次初始化,丢给Malloc分配之用,不同的函数完成不同意义下的功能,更清晰。
-----------------------------------------------------------------------
这样是不行的,按你的意思,假设动态分配初始化前用smalloc分配,初始化后用malloc。
那么我写一个driver,就要先问问项目经理或系统工程师,我的driver是在动态分配初始化前调用还是在后调用,在前则用smalloc函数,在后则用malloc函数。同样功能的程序,如果源代码随用户调用的时机而变的话,作为OS作者,我不知道怎么跟用户交代啊。
再者,准静态分配和动态分配,对于应用程序是透明的,他们只需要得到所需要的内存即可,不需要知道是如何分配的。所以,接口需要保持一致才行的。
在我blog中有一篇文章“http://space.chinaaet.com/Blog/detail.aspx?id=2466”提到,djyos分si、dlsp、mp三种运行模式,我希望为不同模式编写的应用程序,能够实现源代码级别兼容。一个应用程序,很可能在si模式中,用准静态分配,在dlsp模式中,用动态分配,如果接口不统一,就做不到这点。 复杂,手工分配释放易错,体系结构相关,速度慢。
个人观点:最好避免内存分配释放工作。
1,采用前后台
2,用静态内存分配方式,一次分配完,内存不够不用复杂内存管理算法,直接扩大内存。
3,采用自动内存管理模式,向用户隐藏内存管理细节。 回复【7楼】djyos都江堰操作系统
-----------------------------------------------------------------------
核心问题在于,按照您的说法,用户使用的Heap函数族,在不同的上下文环境,具有二重操作意义。具体的推论是,我Malloc的内存,是不一定能安全释放掉的。
假定,我的代码调用的是Malloc,而我不知道我是在什么条件下调用的,因为我不需要问项目经理和相应的系统工程师OS的具体实现,我只是撰写了代码。于是,如果是静态的,回头我要Free这段内存。OK,一个是Malloc和Free的成对关系可能不对,另外可能是我在动态时才去Free。我不了解djyOS的具体实现,类似的情况中,哪些会发生,但是这些都是潜在的可能。
此时,Free可能返回个错误信息,然后,我是管这个错误信息呢?还是不管?还是说不管什么时候,我的Malloc-Free必须成对,类似于Stack?这事儿您认为是简单化操作了?还是复杂化了?
我认为,在我知道Heap系列函数,具有二重操作特性的时候,我必须更加谨慎且小心的使用这些函数,而不是如以往那样使用。如果我不知道,出了问题,显然花费我更海量的时间来应对和处理。这显然就要求我必须了解当前程序所处的环境,我的操作具有什么具体的上下文含义。
您认为,这样是混淆了?还是更清楚了?对此我持保留看法。
【引用】“……准静态分配和动态分配,对于应用程序是透明的,他们只需要得到所需要的内存即可,不需要知道是如何分配的。……”
问题是,我们还需要考虑不同情况的释放。
我认为,Malloc既然是标准库函数,就应该具备标准函数的特性,而不是添加和上下文强相关的复杂功能。
我认为,您完全可以构建MallocEx系列的Heap函数,具备您所述的准静态特性,同时通过变量返回分配时的内存属性,告诉代码应该采用什么样的应对策略。此时和MallocStatic/MallocDynamic两个函数,采用尝试-采用对应策略的方法类似。一个是我先尝试静态,不行我在去动态,我自己决定后续策略;另一个是我拿到内存,同时告诉我后续我应该怎么用它。类似的。
供参考。 回复【9楼】dr2001
-----------------------------------------------------------------------
你的理解不对。
我楼主位中说道:
“调用malloc函数,结果执行的是从堆中静态分配,好像堆的底部成了RW段一样,这时候分配器的内存,可以释放,但要求严格按照“malloc-free”成对出现的方式,否则虽然不会出错,但free变成空操作。”
这有几层含义:
1、如果你严格按照“malloc-free”成对出现的方式调用,是可以正常释放的,不需要问系统工程师。
2、所有编程修养方面的书籍都会告诉你,“malloc-free”要成对使用,动态内存管理函数允许不成对调用,实际上有一定的防御性质。
3、如果你违反成对调用“malloc-free”的规则,不会出致命错误,程序也正常运行,只是free成了空操作。后果仅仅是在初始化阶段会造成内存泄漏,由于不是持续的内存泄漏,也不会是严重错误。
4、准静态分配只是在初始化阶段出现,此时分配的内存,基本上是不会释放的。不成对调用“malloc-free”不出致命错误,实际上也是防御性质的。
如果你有兴趣,可以看看我的uart driver,上面就有malloc和free函数,不管你在初始化时调用(静态分配),还是在main函数中调用(动态),都是一样的结果。该driver中,如果初始化uart成功,则分配的内存不会被释放,如果失败,则成对调用“malloc-free”予以释放,不用问系统工程师的。
djyos的原则之一,是要让项目经理和系统工程师更省心,不会设置你说的这样“陷阱”的。
djyos的原则之二,是低耦合,不会让代码跟被调用位置耦合上的。 mark!!!学习 楼主这才是好文章。嘿嘿。以前老说好,但是人家怎么知道呢。现在写的不错,支持一下。
楼主现在做的怎么样了?哎,要推行一个OS,要弄到国家相关部门才行。不管怎么样,要支持楼主的精神,我等支持你。 本帖最后由 xzp21st 于 2012-3-26 22:35 编辑
之前看的云里雾里的,最近仔细看了djy的书,终于看明白了。楼主的思想真是好啊,学到很多东西,真希望能跟你一起工作学习啊,哈哈
我的理解是:内存就是系统栈+堆,系统栈由编译器管理,堆就是剩下的内存,由djyos管理。djy在初始化完堆(就是初始化堆顶和堆底)之后,用户可以在此时根据需求调用malloc族函数从堆中分配出一块内存,该内存在程序运行过程中基本不去释放,就相当于定义了一个全局的数组放在那里。这和直接定义一个全局数组不同,因为直接定义的话你必须告诉编译器数组长度,也就是说用户得按照需求最大的情况去定义数组大小,比如楼主大侠举得例子,如果最大挂256个节点的话,就得直接定义256个节点所需的数组大小,这样你如果就挂一个节点的话,内存就浪费了。但是如果采用准静态分配的话,用户可以在初始化完堆之后和初始化动态分配堆之前根据所挂节点数从堆中割适量内存出来,因为基本不会再去释放这块内存,就相当于定义了一个大小合适的全局数组,就没浪费内存。之后再进行动态分配堆初始化,具体过程还没看呢,估计就是在剩下的堆内存中采取块相连分配方法进行内存管理,这之后再调用malloc族函数的话就是从剩下的堆中(不包括之前准静态分配的那块内存)按照djy的内存管理方法动态分配内存了。不管是准静态分配还是之后的动态分配,调用的分配函数都是一个函数,malloc。。。 不知道都江堰系统目前如何了各项外设是否已经开始设计?而不仅仅是一个内核了? 没看太明白,MARK一下哦。 {:lol:}标准C是死的,GNU之C是活的。
标准C是死的,GNU之C是活的。 不是很明白,。。。。。
页:
[1]