关于操作系统内存分配调用的讨论

本文主要探讨操作系统内存分配机制及delete的使用。内存分配由操作系统完成,new等是系统调用。现代操作系统可在进程退出时强制回收内存,但多进程共用内存需手动delete。还对比了Java垃圾回收和操作系统强制回收,指出不同操作系统内存分配策略不同。

前注:这是我在学校BBS上贴的一个回复,贴于此处与大家讨论.

原文是:

发信人: true (对自己更残酷一点), 信区: C_and_CPP
标  题: new出来的空间系统会不会自动回收?
发信站: 哈工大紫丁香 (Fri May 14 12:07:43 2004), 转信

当程序运行完之后,我觉得系统应该会自动回收才对,是否?

各个操作系统都一样吗? 在哪里找理论根据?

我的第一个回复是:

发信人: iamxiaohan (潇寒·System Programmer), 信区: C_and_CPP
标  题: Re: new出来的空间系统会不会自动回收?
发信站: BBS 哈工大紫丁香站 (Fri May 14 12:16:51 2004)

这主要看操作系统是怎么运作的,
对于目前一般的操作系统而言,进程在销毁时回强行回收所有分配的资源,
所以你可以不用delete,但用delete会让系统及时回收资源,这是一个良好的
编程习惯
【 在 true (对自己更残酷一点) 的大作中提到: 】
: 当程序运行完之后,我觉得系统应该会自动回收才对,是否?
: 各个操作系统都一样吗? 在哪里找理论根据?

而下面的是sun的回复

发信人: Sun (大灯泡), 信区: C_and_CPP
标  题: Re: new出来的空间系统会不会自动回收?
发信站: 哈工大紫丁香 (Fri May 14 13:08:12 2004), 转信


【 在 iamxiaohan (潇寒·System Programmer) 的大作中提到: 】
: 这主要看操作系统是怎么运作的,
: 对于目前一般的操作系统而言,进程在销毁时回强行回收所有分配的资源,
这个由参考文献吗?:)

: 所以你可以不用delete,但用delete会让系统及时回收资源,这是一个良好的
: ...................

下面贴的,就是对sun的回复,欢迎大家继续讨论或指正。

发信人: iamxiaohan (潇寒·System Programmer), 信区: C_and_CPP
标  题: Re: new出来的空间系统会不会自动回收?
发信站: BBS 哈工大紫丁香站 (Sat May 15 09:25:05 2004)

看见N本书这样说过,对于现代操作系统而言,是可以不delete的,其实new操作到

最后会被编译器链接到malloc上,是调用的malloc,而malloc是一个系统调用,

用来分配内存。因此,内存的分配最终是由操作系统而不是由编译器完成的,编译

器只是起个调用系统调用以分配内存的的动作,而真正怎么分配是由操作系统决定

的.

一般操作系统都有一块很大的自由内存区,这块自由内存区也称为堆,当你用new

分配这块内存的时候,也被称为在堆上分配,当你用new产生一个操作系统调用,

要求分配内存的时候,操作系统就检查这块堆,检索出一块大小合适的且被标记为

“未分配”的内存,并返回它的指针,然后再把这块内存标记为“可分配”,当你调

用delete的时候,操作系统再清除这块内存的”可分配“标记,以便可以再次分配

这块内存。这就是一个最最简单的内存分配动作,打个比喻,操作系统就像银

行,new就像你向银行借钱打个借条,delete就像你还钱的时候,银行取消掉借条.

以前还有问,为什么在delete后,还可以操作这块内存,而不出现段错误?从上

面的描述中可以看出,delete后的内存还是在内存空间中,只是重新被操作系统

标记为“可分配”了,因此你当然还可以对其进行操作,只不过,你并不知道它在

什么时会被分配出去,如果它被分配出去了,你对此块内存的操作就非常危险了



当然,一个健壮的操作系统完全可以在你访问不属于你的内存时(或者访问被标记

为"可分配"的内存时),产生一个错误中断或错误陷阱,这主要看操作系统的怎么管

理的了。一个非常健壮的操作系统也许会检测所有对堆内存的访问,如果发现你

访问了一块不是你的内存,就会出错。而这样的检测是比较费时间的,因此,有

些操作系统可以选择只对”已分配“的内存进行这样的检测,于是,在此块内存

被delete重新标记为”可分配“到被重新分配前,你可以继续对此内存进行操作,

而一旦此内存被重新分配出去,内存标记表为“已分配”,这时,你再继续使用这

块内存时,操作系统就会报错了。这样,有时后你访问已delete的内存时,不会出

错,而有时又会出错,这就是由于在先前这块内存还未被分出,操作系统不检测你对

此内存的访部是否合法,因此没出错,但下次你再访问时,这块内存已被分给其它进

程使用了,因此,操作系统会对这个内存进行检测,于是就出错了.

对于一些采用页式管理的操作系统,最底层的内存分配一次分配一页(4KB),然

后在其上实现一层高级内存管理,以便用你可以分配指定大小的内存而不是一整

页了,当一页上的所有空间全部被delete,即全部标记为“可分配”时,操作系统

就会将这页的存在标记置为“不存在”,然后将其从内存中去除,并在页表中去除

,这样,当重新访问时,CPU会产生缺页中断,操作系统会对这样的中断进行处理

,处理或许是会重新载入此页,或许是产出一个错误,因为操作系统发现你已

经delete了,无权再访问此页了,要访问,你需要重新new,因此,这就会出现一

个非常有趣的现象:你delete了一块内存,然后不停的访问,前99次都可以,而

第100次就不行了,这或许就是因为前99次时,因为此页上还有其它进程没

有delete的空间,而第100次时,其它进程洽好全部delete了,于是操作系统把此

页作废了,你再访问时,操作系统就会发现你是非法访问,于就就出错了。因此

,99次运行正确的程序,不见得第100次运行就会正确!这样做的好处就是,操作

系统只在产生缺页中断的时候,才检测内存访问是否合法,由于内存访问是很频

烦的操作,降低检测次数,当然就可以系统效率升高。

操作系统在分配内存时,会建立一个数据结构,对一块已分配的内存保留它的很

多信息,比如这块内存的首地址,大小。因此,才会有这样一种显象,当malloc

或new一块内存时,你需要指定要分配的内存大小,而当你free或delete时,却不

需要指定大小,而只需给出内存的首地址指针就可以了,这主要是因为,操作系

统只需要把这块内存重新标记为”可分配“,而其内存大小,在操作系统分配内存

时,操作系统就自动把它记录下来了。

现代操作系统,在分配内存时,还会记录内存被分配给哪个进程了,这样,当进

程在退出的时候,就完全可以强制性回收所有的分配给这个进程的空间。当然,

如果一个操作系统在分配内存时,并没有记录这块内存并分配给谁了,那么它就

不能强制回收一块内存,这时就需要你必须用delete。这样的情况主要出现在古

董级的操作系统上,因为当时的内存非常有限,不允许操作系统如此大方的用它

来存储它需要存储的信息,不过,对于现在来说,保存一下内存被分配给那个进

程了实在是小case,因此,现在你可以不太注意是否使用了delete了,不过

用delete会及时的释放内存空间,但不用delete,却只会在进程完蛋时才释放,

这其间的区别,自然就不用多说了。

不过我想提醒一下,操作系统强制回收不等于我们常常说的“垃圾回收机制!”,

看看java,它的垃圾回收机制,它是先不断的分配内存,只要能分配就分配,如

果不能分配了,它再查找一下哪些内存已经不被程序使用了,它就回收它,但操

作系统强制回收机制却没有这样的能力,它不知道哪些内存已经不被程序使用了

,对于它而言,只要是没有delete的内存,它就认为程序还在使用它。说简单一

点,对于java或C两个程序,如果都只用new而不用delete,那么java可以在程序

运行的过程中就回收内存,而c的程序却不行,只能在程序运行结束后,由操作系

统一次性强制回收,java是边用边收,程序可以永远运行,而c是只用不收,当最

后没有再能用的时候,程序不得不被杀除,这时操作系统再回收,于是,如果你

在java中用for(;;)new...,java程序可以一直运行下去,而c却总用内存用尽的

时候。

那么是否是所有的new,都可以不delete呢?这也不见得,有的操作系统会在内存

分配的时候,除了保留内存分配的指针及大小外,还要保留一个类型参数,用来

标记这个内存是进程单独使用,还是多进程共用。如果是单独使用,当然可以在

进程结束时,由操作系统自动回收;如果是多进程共用,这当然就不行了,因为

,此进程虽然结束了,但还有进程在使用,因此不能回收。

在多进程共用内存的分配上,这很大可能是由引用计数实现的,每块内存都有一

个引用计数值,最初的引用计数值是0,new一次就+1,delete一次就-1,只有当

其值为0时,才会被操作系统真正回收,这时,如果你new了,但没有delete,则

引用计数就不可能成为0,这块内存就会一直被保留,而无法被操作系统回收,这

种情况下,你就必须在new后delete了。

内存的分配是由操作系统完成的,而不是由编译器完成的,malloc,new等等,只

是一个系统调用,它调用操作系统的内存分配函数来完成内存分配的功能,而具

体怎么分配是操作系统决定的,编译器无法决定,也无法知道,因此,这是平台

相关,而编译器无关的。

各个操作系统对于内存分配的策略各有不同,反正是CPU就提供了那些功能,在这

些功能上,怎么实现以满足需求是八仙过海,各显神通,这没有参考资料,也没

有标准答案,linux为了满足它的需求,采用了一种实现方式,而windows为了满

足它的需要,采用了另一种实现方式。像pyos,我的想法是提供一个功能更强一

点的内存管理,比如,一个进程可以申请分配一个共享内存区,而只有申请分配

的内存具有写权限,而其它进程只具有读权限,这又是另外一种内存分配策略。

我想我们可以提出一个需求,然后讨论可以用什么方法实现这样的需求,可以讨

论linux或windows为什么要这样实现,而不是讨论linux或windows怎样实现,不

是讨论应当怎样实现,或者去讨论书上说怎样实现。

原文:http://purec.binghua.com/Article/ShowArticle.asp?ArticleID=130

<think> 我们正在讨论实时操作系统(RTOS)中的动态内存分配算法。实时操作系统内存分配有严格的要求,包括可预测的分配时间、避免碎片化以及保证高优先级任务的及时响应。常见的动态内存分配算法如首次适应(First-fit)、最佳适应(Best-fit)等可能无法满足实时性要求,因此需要专门的设计。 根据引用[2]和[3],我们了解到传统动态分区分配算法(包括首次适应、循环首次适应、最佳适应和最坏适应)的基本原理。然而,实时操作系统通常需要更高级的内存分配策略来确保实时性能。 实时操作系统中常用的动态内存分配算法包括: 1. 固定大小内存池(Fixed-size Memory Pools):也称为内存分区,系统预先分配多个大小固定的内存块。分配和释放操作都是常数时间,避免了碎片问题,但可能造成内部碎片。 2. 伙伴系统(Buddy System):将内存划分为大小固定的块,每次分配时按照2的幂次进行,可以合并相邻的空闲块。分配和释放时间可预测,但也会产生内部碎片。 3. TLSF(Two-Level Segregated Fit):一种专门为实时系统设计的内存分配算法,它能够在常数时间内完成分配和释放操作,同时减少碎片。TLSF通过两级位图和多级空闲链表来管理不同大小的内存块。 下面我们详细讨论这些算法: ### 1. 固定大小内存池(Memory Pools) - **原理**:系统创建多个内存池,每个池中的内存块大小固定。当任务申请内存时,从对应大小的内存池中分配一个块。释放时,将块归还到对应的池中。 - **优点**:分配和释放操作都是O(1)时间复杂度,非常快速且可预测,不会产生外部碎片。 - **缺点**:如果申请的内存大小与池的块大小不匹配,则会产生内部碎片。同时,需要预先确定内存块的大小和数量,可能不够灵活。 - **适用场景**:实时性要求极高且内存申请大小已知且相对固定的情况。 ### 2. 伙伴系统(Buddy System) - **原理**:将内存划分为大小为2的幂次的块。分配时,如果请求的大小不是2的幂次,则向上取整到最近的2的幂次。然后从空闲链表中查找合适的块,如果找不到,则将大块分裂成两个大小相等的伙伴块,直到得到合适的大小。释放时,检查相邻的伙伴块是否空闲,如果空闲则合并。 - **优点**:分配和释放的时间复杂度为O(log n),可预测性较好,外部碎片较少。 - **缺点**:内部碎片可能较大,特别是当申请大小略大于2的幂次时。此外,分裂和合并操作需要时间。 - **适用场景**:需要动态分配多种大小内存的实时系统,且允许一定的内部碎片。 ### 3. TLSF(Two-Level Segregated Fit) - **原理**:TLSF使用两级结构来管理空闲内存块。第一级按内存块大小的指数范围进行划分(例如,2^4~2^5,2^5~2^6等),第二级在每个指数范围内线性分割成若干个子范围。每个子范围由一个空闲链表管理,链表中的块按大小排序。通过位图快速定位到合适的空闲链表,然后在该链表中查找合适的块(通常选择第一个足够大的块)。 - **优点**:分配和释放操作的时间复杂度为O(1),且碎片率较低。它能够满足实时系统对时间可预测性的要求。 - **缺点**:实现相对复杂。 - **适用场景**:对时间可预测性和内存利用率都有较高要求的实时系统。 ### 比较 | 算法 | 分配时间 | 释放时间 | 碎片问题 | 适用性 | |------------------|------------|------------|----------------|----------------------------| | 固定大小内存池 | O(1) | O(1) | 无外部碎片 | 固定大小内存请求 | | 伙伴系统 | O(log n) | O(log n) | 内部碎片较多 | 允许内部碎片,请求大小变化 | | TLSF | O(1) | O(1) | 碎片较少 | 通用实时应用 | 在实时操作系统中,选择内存分配算法时需要考虑: - **时间可预测性**:最坏情况下的分配时间必须是确定的,以满足实时任务的截止时间。 - **碎片控制**:长期运行后,碎片不应导致内存分配失败。 - **内存利用率**:尽量减少内部碎片和外部碎片。 因此,固定大小内存池和TLSF在实时系统中应用广泛。例如,VxWorks、FreeRTOS等实时操作系统提供了内存池(或类似)的机制,而TLSF被用于一些需要高效动态内存管理的实时系统中。 参考引用[2][3][4]中提到的传统算法(如首次适应、最佳适应等)由于在分配时可能需要遍历整个空闲链表(最坏情况O(n)),因此不适用于实时系统,除非进行优化(如使用TLSF这样的优化结构)。 总结:实时操作系统中动态内存分配算法的设计目标是保证时间可预测性和低碎片。固定大小内存池、伙伴系统和TLSF是常用的算法,其中TLSF因其常数时间操作和较低的碎片率成为许多实时系统的选择。 §§相关问题§§ 1. 实时操作系统中,固定大小内存池的具体实现细节和优化方法有哪些? 2. TLSF算法是如何实现常数时间分配和释放的?其数据结构设计有何特点? 3. 在实时系统中,如何评估和比较不同内存分配算法的性能(如时间确定性、碎片率)? 4. 除了固定大小内存池、伙伴系统和TLSF,还有哪些内存分配算法适用于实时系统? </think>实时操作系统(RTOS)的动态内存分配算法需满足**严格的时间确定性**和**低碎片化**要求,确保高优先级任务不受延迟影响。以下是关键算法及特性: --- ### 一、实时系统常用动态内存分配算法 #### 1. **固定大小内存池(Memory Pools)** - **原理**: 预分配多个等大的内存块,形成池结构。分配时直接取空闲块,释放时归还至池。 - **特点**: - **分配/释放时间恒定**:$O(1)$ 复杂度,确保实时性[^2][^4]。 - **零外部碎片**:因块大小固定,无碎片问题。 - **内部碎片**:若请求大小小于块尺寸则产生浪费。 - **适用场景**: 周期性任务(如传感器数据采集)需固定大小内存的场景。 #### 2. **伙伴系统(Buddy System)** - **原理**: 内存按$2^n$大小分割。分配时递归分裂大块,释放时合并相邻空闲块("伙伴")。 - **特点**: - **时间可预测**:分裂/合并操作在 $O(\log N)$ 内完成[^3][^4]。 - **低外部碎片**:合并机制减少碎片。 - **内部碎片**:请求大小非$2^n$时浪费空间。 - **优化变体**: - **二进制伙伴系统**:严格按$2^n$分割。 - **加权伙伴系统**:支持非$2^n$的块大小,提升利用率。 #### 3. **TLSF(Two-Level Segregated Fit)** - **原理**: 两级空闲链表管理: 1. 第一级按指数范围分区(如 $[2^4, 2^5)$)。 2. 第二级线性细分各区(如每区再分16子类)。 通过位图快速定位合适块。 - **特点**: - **常数时间操作**:分配/释放均 $O(1)$[^4]。 - **碎片率低**:精准匹配请求大小,外部碎片少。 - **适用于硬实时系统**:如 VxWorks、FreeRTOS。 #### 4. **Slab分配器** - **原理**: 为高频对象(如任务控制块)预分配缓存(Slab),每个Slab含多个同构对象。 - **特点**: - **极速分配**:直接取用预初始化对象。 - **零初始化延迟**:对象创建时已初始化。 - **适用场景**: 内核对象(信号量、队列)的快速分配。 --- ### 二、实时系统的特殊要求 1. **时间确定性** - 算法最坏执行时间(WCET)必须可预测。 - *示例*:TLSF 和内存池保证 $O(1)$,避免遍历链表的不确定性[^2][^4]。 2. **碎片控制** - **外部碎片**:通过合并机制(伙伴系统)或固定块(内存池)抑制。 - **内部碎片**:TLSF 按请求大小分配,减少浪费。 3. **优先级反转避免** - **分区堆(Partitioned Heap)**:为不同优先级任务分配独立内存池,防止低优先级任务阻塞高优先级任务。 --- ### 三、算法对比 | **算法** | **分配时间** | **碎片情况** | **适用性** | |------------------|--------------|--------------------|--------------------------| | 固定大小内存池 | $O(1)$ | 无外部,有内部 | 硬实时,固定大小请求 | | 伙伴系统 | $O(\log N)$ | 低外部,有内部 | 通用实时系统 | | TLSF | $O(1)$ | 极低外部,无内部 | 硬实时,变长请求 | | Slab分配器 | $O(1)$ | 无外部 | 高频小型对象分配 | --- ### 四、实际应用 - **FreeRTOS**:默认提供 `heap_1`~`heap_5` 分配器,其中 `heap_4` 使用合并空闲块机制,`heap_5` 支持非连续内存区域[^2]。 - **VxWorks**:采用伙伴系统和内存池,为关键任务预留专用池。 - **嵌入式Linux(RT补丁)**:使用 TLSF 替代传统 `glibc malloc`,确保实时性。 > 💡 **设计建议**:硬实时系统首选内存池或 TLSF;若请求大小多变且需高效利用内存,TLSF 是最优解。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值