540 likes | 706 Views
嵌入式系统及应用. 第八章 内存管理. 主要内容. 概述 内存管理机制 内存保护. 概述. 不同实时内核所采用的内存管理方式不同,有的简单,有的复杂。 实时内核所采用的内存管理方式与应用领域和硬件环境密切相关。 在强实时应用领域,内存管理方法就比较简单,甚至不提供内存管理功能。 一些实时性要求不高,可靠性要求比较高,且系统比较复杂的应用在内存管理上就相对复杂些,可能需要实现对操作系统或是任务的保护。. 概述. 嵌入式实时操作系统在内存管理方面需要考虑如下因素:
E N D
主要内容 • 概述 • 内存管理机制 • 内存保护
概述 • 不同实时内核所采用的内存管理方式不同,有的简单,有的复杂。 • 实时内核所采用的内存管理方式与应用领域和硬件环境密切相关。 • 在强实时应用领域,内存管理方法就比较简单,甚至不提供内存管理功能。 • 一些实时性要求不高,可靠性要求比较高,且系统比较复杂的应用在内存管理上就相对复杂些,可能需要实现对操作系统或是任务的保护。
概述 • 嵌入式实时操作系统在内存管理方面需要考虑如下因素: • 不使用内存管理:最快速和最确定的内存管理方式,适用于那些小型的嵌入式系统,系统中的任务比较少,且数量固定。 • 通常的操作系统都至少具有基本的内存管理方法:提供内存分配与释放的系统调用。 • 快速而确定的内存管理
概述 • 不使用虚拟存储技术 • 虚拟存储技术: • 为用户提供一种不受物理存储器结构和容量限制的存储管理技术,是桌面/服务器操作系统为在所有任务中使用有限物理内存的通常方法,每个任务从内存中获得一定数量的页面,并且,当前不访问的页面将被置换出去,为需要页面的其他任务腾出空间。 • 置换是一种具有不确定性的操作:当任务需要使用当前被置换出去的页面中的代码和数据时,将不得不从磁盘中获取页面,而在内存中另外的页面又可能不得不需要先被置换出去。 • 在嵌入式实时操作系统中一般不使用虚拟存储技术,以避免页面置换所带来的开销。
概述 • 内存保护 • 平面内存模式: • 应用程序和系统程序能够对整个内存空间进行访问。 • 平面内存模式比较简单,易于管理,性能也比较高。 • 适合于程序简单、代码量小和实时性要求比较高的领域。 • 内存保护: • 应用比较复杂、程序量比较大的情况; • 防止应用程序破坏操作系统或是其他应用程序的代码和数据。
概述 • 内存保护包含两个方面的内容: • 防止地址越界:每个应用程序都有自己独立的地址空间,当应用程序要访问某个内存单元时,由硬件检查该地址是否在限定的地址空间之内,只有在限定地址空间之内的内存单元访问才是合法的,否则需要进行地址越界处理; • 防止操作越权:对于允许多个应用程序共享的存储区域,每个应用程序都有自己的访问权限,如果一个应用程序对共享区域的访问违反了权限规定,则进行操作越权处理。
内存管理机制 • 静态分配 • 系统在启动前,所有的任务都获得了所需要的所有内存,运行过程中将不会有新的内存请求。 • 在强实时系统中,减少内存分配在时间上可能带来的不确定性。 • 不需要操作系统进行专门的内存管理操作。 • 系统使用内存的效率比较低下,只适合于那些强实时,且应用比较简单,任务数量可以静态确定的系统。
内存管理机制 • 动态分配 • 堆(heap):应用通过分配(malloc)与释放(free)操作来使用内存。 • 堆会带来碎片: • 内存被逐渐划分为位于已被使用区域之间的越来越小的空闲区域。 • 垃圾回收: • 对内存堆进行重新排列,把碎片组织成为大的连续可用内存空间。但垃圾回收的时间长短不确定:不适合于处理实时应用。 • 在实时系统中,避免内存碎片的出现,而不是在出现内存碎片时进行回收。
内存管理机制 • 常用管理方式: • 固定大小存储区: • 在指定边界的一块地址连续的内存空间中,实现固定大小内存块的分配。 • 可变大小存储区: • 在指定边界的一块地址连续的内存空间中,实现可变大小内存块的分配。 • 应用根据需要从固定大小存储区或者可变大小存储区中获得一块内存空间,用完后将该内存空间释放回相应的存储区。
内存块1 128字节 内存块2 512字节 内存块3 内存块4 固定大小存储区管理 可供使用的一段连续的内存空间被称为是一个分区; 分区由大小固定的内存块构成,且分区的大小是内存块大小的整数倍数。 一个大小为512字节的分区,内存块为128个字节的分区
typedef struct { PartitionID ID; /*分区的ID*/ PartitionName Name; /*分区的名字*/ void *starting_address; /*分区的起始地址*/ int length; /*分区的长度*/ int buffer_size; /*内存块的大小*/ PartitionAttribute attribute; /*属性*/ int number_of_used_blocks; /*剩余内存块数*/ MemoryChain memory; /*内存块链*/ } Partition; 分区的数据结构 ID表示分区的标识;starting_address表示分区的起始地址; length表示分区的存储单元的数量;buffer_size表示分区中每个内存块的大小; attribute表示分区的属性;number_of_used_blocks表示分区中已使用内存块的数量; memory为一个指针,指向分区中由空闲内存块组成的双向空闲内存块链表的头结点。
… … next next next 分区1 previous previous previous 分区2 空闲内存块链表
固定大小存储区管理 • 分区的操作 • 创建分区 • 删除分区 • 从分区得到内存块 • 把内存块释放到分区 • 获取分区ID • 获取当前创建的分区的数量 • 获取当前所有分区的ID • 获取分区信息
固定大小存储区管理 • 如果内存块处于空闲状态,将使用内存块中的几个字节作为控制结构,用来存放用于双向链接的前向指针和后向指针。 • 在使用内存块时,内存块中原有的控制信息不再有效,其中的所有存储空间都可以被使用。 • 固定大小存储区管理的系统开销对用户的影响为零。 • 由于内存块的大小固定,不存在碎片的问题。
可变大小存储区管理 • 可变大小存储区管理为基于堆的管理方式。 • 堆为一段连续的、大小可配置的内存空间,用来提供可变内存块的分配。 • 可变内存块称为段,最小分配单位称为页,即段的大小是页的大小的整数倍。 • 如果申请段的大小不是页的倍数,实时内核将会对段的大小进行调整,调整为页的倍数。 • 例如,从页大小为256个字节的堆中分配一个大小为350字节的段,实时内核实际分配的段大小为512个字节。
typedef struct { HeapID ID; /*堆的ID*/ HeadName name; /*堆的名字*/ TaskQueue waitQueue; /*等待队列*/ void *starting_address; /*内存空间起始地址*/ int length; /*内存空间长度/字节*/ int page_size; /*页长度(字节)*/ int maximum_segment_size; /*最大可用段大小*/ RegionAttribute attribute; /*堆的属性*/ int number_of_used_blocks; /*分配的块数*/ HeapMemoryChain memory; /*堆头控制结构*/ }Heap; 堆的数据结构 waitQueue用来表示任务等待队列,如果任务从堆中申请段不能得到满足,将被阻塞在堆的等待队列上; starting_address用来表示堆在内存中的起始地址;length表示堆的大小; page_size为页的大小;maximum_segment_size表示堆中当前最大可用段的大小; attribute表示堆的属性;number_of_used_blocks表示已分配使用的内存块的数量; memory表示空闲段链表。
可变大小存储区管理 • 可变大小存储区中的空闲段通过双向链表链接起来,形成一个空闲段链。 • 在创建堆时,只有一个空闲段,其大小为整个存储区的大小减去控制结构的内存开销。 • 从存储区中分配段时,可依据首次适应算法,查看空闲链中是否存在合适的段。 • 当把段释放回存储区时,该段将被挂在空闲段链的链尾。 • 如果空闲链中有与该段相邻的段,则将其合并成一个更大的空闲段。 • 由于对申请的内存的大小作了一些限制,避免了内存碎片的产生。
0 其它控制标志 0 其它控制标志 1 其它控制标志 空闲段 空闲段 正被使用的段 堆1 堆2 … 堆的空闲段链 在段的控制块中设置了一个标志位,表示段被使用的情况:1表示该段正被使用,0表示该段空闲。 在固定大小存储区管理方式中,只有在空闲状态下,内存块才拥有控制信息。在可变大小存储区管理方式中,无论段空闲或是正在被使用,段的控制结构都始终存在。
可变大小存储区管理 • 堆的操作 • 创建堆 • 从堆中得到内存块 • 释放内存块到堆中 • 扩展堆 • 获得已分配内存块的实际可用空间大小 • 删除堆 • 获得堆的ID • 获得在堆上等待的任务数量 • 获得等待任务的ID列表 • 获得堆的数量 • 获得堆列表 • 获得堆信息
Memory management in uC/OS-II memory partition Multiple memory partitions fixed-sized memory blocks from a partition
/* MEMORY CONTROL BLOCK */ typedef struct { /* Pointer to beginning of memory partition*/ void *OSMemAddr; /* Pointer to list of free memory blocks */ void *OSMemFreeList; /* Size (in bytes) of each block of memory */ INT32U OSMemBlkSize; /* Total number of blocks in this partition*/ INT32U OSMemNBlks; /* Number of memory blocks remaining in this partition*/ INT32U OSMemNFree; } OS_MEM; Memory control block data structure
List of free memory control blocks created by OS_MemInit Each memory control block can be used to manage a partition
/* Max. number of memory partitions ... */ #define OS_MAX_MEM_PART 32 /* Storage for memory partition manager */ OS_MEM OSMemTbl[OS_MAX_MEM_PART]; void OS_MemInit (void) { OS_MEM *pmem; INT16U i; pmem = (OS_MEM *)&OSMemTbl[0];/* Point to memory control block (MCB) */ for (i = 0; i < (OS_MAX_MEM_PART - 1); i++) { /* Init. list of free memory partitions */ /* Chain list of free partitions */ pmem->OSMemFreeList = (void *)&OSMemTbl[i+1]; /* Store start address of memory partition */ pmem->OSMemAddr = (void *)0; pmem->OSMemNFree = 0; /* No free blocks */ pmem->OSMemNBlks = 0; /* No blocks */ pmem->OSMemBlkSize = 0; /* Zero size */ pmem++; } pmem->OSMemFreeList = (void *)0; /* Initialize last node */ pmem->OSMemAddr = (void *)0; /*Store start address of memory partition*/ pmem->OSMemNFree = 0; /* No free blocks */ pmem->OSMemNBlks = 0; /* No blocks */ pmem->OSMemBlkSize = 0; /* Zero size */ /* Point to beginning of free list */ OSMemFreeList = (OS_MEM *)&OSMemTbl[0]; } OS_MemInit
OS_MEM *OSMemCreate (void *addr, INT32U nblks, INT32U blksize, INT8U *err) { OS_MEM *pmem; INT8U *pblk; void **plink; INT32U i; OS_ENTER_CRITICAL(); pmem = OSMemFreeList; /* Get next free memory partition */ if (OSMemFreeList != (OS_MEM *)0) {/* See if pool of free partitions was empty*/ OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList; } OS_EXIT_CRITICAL(); if (pmem == (OS_MEM *)0) { /* See if we have a memory partition */ *err = OS_MEM_INVALID_PART; return ((OS_MEM *)0); } plink = (void **)addr; /* Create linked list of free memory blocks */ pblk = (INT8U *)addr + blksize; for (i = 0; i < (nblks - 1); i++) { *plink = (void *)pblk; plink = (void **)pblk; pblk = pblk + blksize; } *plink = (void *)0; /* Last memory block points to NULL */ pmem->OSMemAddr = addr; /* Store start address of memory partition */ pmem->OSMemFreeList = addr; /* Initialize pointer to pool of free blocks */ pmem->OSMemNFree = nblks; /* Store number of free blocks in MCB */ pmem->OSMemNBlks = nblks; pmem->OSMemBlkSize = blksize; /* Store block size of each memory blocks*/ *err = OS_NO_ERR; return (pmem); } OSMemCreate
pmem OSMemAddr = addr OSMemFreeList= addr OSMemBlkSize = blksize OSMemNBlks = nblks OSMemNFree = nblks Contiguous memory OSMemCreate() arguments 0 Partition created by OSMemCreate
void *OSMemGet (OS_MEM *pmem, INT8U *err) { void *pblk; OS_ENTER_CRITICAL(); if (pmem->OSMemNFree > 0){/* See if there are any free memory blocks*/ pblk=pmem->OSMemFreeList;/* Yes, point to next free memory block*/ pmem->OSMemFreeList=*(void **)pblk;/*Adjust pointer to new free list */ pmem->OSMemNFree--; /* One less memory block in this partition */ OS_EXIT_CRITICAL(); *err = OS_NO_ERR; /* No error */ return (pblk); /* Return memory block to caller */ } OS_EXIT_CRITICAL(); *err = OS_MEM_NO_FREE_BLKS;/* No, Notify caller of empty memory partition */ return ((void *)0); /* Return NULL pointer to caller */ } OSMemGet
pblk Partition after OSMemGet
INT8U OSMemPut (OS_MEM *pmem, void *pblk) { OS_ENTER_CRITICAL(); if (pmem->OSMemNFree >= pmem->OSMemNBlks) { /* Make sure all blocks not already returned */ OS_EXIT_CRITICAL(); return (OS_MEM_FULL); } /* Insert released block into free block list */ *(void **)pblk = pmem->OSMemFreeList; pmem->OSMemFreeList = pblk; pmem->OSMemNFree++; /* One more memory block in this partition */ OS_EXIT_CRITICAL(); return (OS_NO_ERR); /* Notify caller that memory block was released*/ } OSMemPut
pblk Partition after OSMemPut
内存保护 • 内存保护可通过硬件提供的MMU(memory management unit)来实现。 • 目前,大多数处理器都集成了MMU: • 大幅度降低那些通过在处理器外部添加MMU模块的处理方式所存在的内存访问延迟。 • MMU现在大都被设计作为处理器内部指令执行流水线的一部分,使得使用MMU不会降低系统性能,相反,如果系统软件不使用MMU,还会导致处理器的性能降低。 • 在某些情况下,不使能MMU,跳过处理器的相应流水线,可能导致处理器的性能降低80%左右。
内存保护 • 早期的嵌入式操作系统大都没有采用MMU: • 一方面是出于对硬件成本的考虑; • 另一方面是出于实时性的考虑。 • 嵌入式系统发展到现在,硬件成本越来越低,MMU所带来的成本因素基本上可以不用考虑 • 原来的嵌入式CPU的速度较慢,采用MMU通常会造成对时间性能的不满足,而现在CPU的速度也越来越快,并且采用新技术后,已经将MMU所带来的时间代价降低到比较低的程度 • 嵌入式CPU具有MMU的功能已经是一种必要的趋势。
内存保护 • 由于采用MMU后对应用的运行模式甚至开发模式都会有一些影响,大量嵌入式操作系统都没有使用MMU。 • 对于安全性、可靠性要求高的应用来讲如果不采用MMU,则几乎不可能达到应用的要求。 • 如果没有MMU的功能,将无法防止程序的无意破坏,无法截获各种非法的访问异常,当然更不可能防止应用程序的蓄意破坏了。 • 采用MMU后,便于发现更多的潜在问题,并且也便于问题的定位。 • 未采用MMU时,内存模式一般都是平面模式,应用可以任意访问任何内存区域、任何硬件设备,程序中出现非法访问时,开发人员是无从知晓的,也非常难于定位。
内存保护 • MMU通常具有如下功能: • 内存映射; • 检查逻辑地址是否在限定的地址范围内,防止页面地址越界; • 检查对内存页面的访问是否违背特权信息,防止越权操作内存页面; • 在必要的时候(页面地址越界或是页面操作越权)产生异常。
物理地址 逻辑地址 应用 程序 MMU 物理内存 内存映射把应用程序使用的地址集合(逻辑地址)翻译为实际的物理内存地址(物理地址)
内存保护 • 大多数处理器的典型页面大小为4K字节,有些处理器也可能使用大于4K字节的页面,但页面大小总是2的幂,以对发生在MMU中的地址映射行为流水线化。 • 当页放置到物理内存时,页面将放置到页框架(page frame)中。 • 页框架是物理内存的一部分,具有与页面同样的大小,且开始地址为页面大小的整数倍。
内存保护 • MMU包含着能够把逻辑地址映射为物理地址的表,称为页表。 • 操作系统能够在需要的时候对这种映射关系进行改变: • 应用程序对内存的需求发生变化或是添加或删除应用程序的时候。 • 在应用程序中的任务发生上下文切换时。
逻辑地址 物理地址 偏移量 特权和其他信息 页框架 偏移量 页号 页框架 页表基址寄存器 页表 基于页表的内存映射过程 例如,一个系统具有32位的地址空间和4K字节的页面,32位的地址空间由20位的页号和12位的页内偏移量构成。MMU将检查20位的页号,并为该页面提供(根据MMU表进行映射)页框架地址。
内存保护 • 每个内存页还具有一些特权和状态信息。 • MMU提供二进制位来标识每个页面的特权或状态信息。这些二进制位用来确定页面中的内容是否: • 可被处理器指令所使用(执行特权) • 可写(写特权) • 可读(读特权) • 已被回写(脏位) • 当前在物理内存中(有效位)
内存保护 • 在操作系统的支持下,MMU还提供虚拟存储功能,即在任务所需要的内存空间超过能够从系统中获得的物理内存空间的情况下,也能够得到正常运行。 • 当需要的页面被添加到逻辑地址空间时,任务对内存页面的合法访问,将自动访问到物理内存。 • 页面当前不在物理内存中时,将导致页面故障异常,然后操作系统负责从后援存储器(如硬盘或是FLASH存储设备)中获取需要的页面,并从产生页面故障的机器指令处重新执行。
内存保护 • MMU通常具有如下不同功能程度的使用方式: • 没有使用MMU,应用程序和系统程序能够对整个内存空间进行访问。 • 采用该模式的系统比较简单、性能也比较高,适合于程序简单、代码量小和实时性要求比较高的领域。 • 大多数传统的嵌入式操作系统都采用该模式; 0级,内存的平面使用模式
内存保护 • 通常只是打开MMU,并通过创建一个域(domain,为内存保护的基本单位,每个域对应一个页表)的方式来使用内存,并对每次内存访问执行一些必要的地址转换操作。 • 该模式仍然只是拥有MMU打开特性的平面内存模式; • 1级,处理具有MMU和内存缓存的嵌入式处理器
内存保护 • MMU被打开,且创建了静态的域(应用程序的逻辑地址同应用程序在物理内存中的物理地址之间的映射关系在系统运行前就已经确定),以保护应用和操作系统在指针试图访问其他程序的地址空间时不会被非法操作。 • 通常使用消息传送机制实现数据在被MMU保护起来的各个域之间的移动。 • 2级,内存保护模式
内存保护 • 通过操作系统使用CPU提供的内存映射机制,内存页被动态地分配、释放或是重新分配。 • 从内存映射到基于磁盘的虚拟内存页的过程是透明的。 • 3级,虚拟内存使用模式
内存保护 • 0级模式为大多数传统嵌入式实时操作系统的使用模式,同1级模式一样,都是内存的平面使用模式,不能实现内存的保护功能。 • 2级模式是目前大多数嵌入式实时操作系统所采用的内存管理模式,既能实现内存保护功能,又能通过静态域的使用方式保证系统的实时特性。 • 3级模式适合于应用比较复杂、程序量比较大,并不要求实时性的应用领域。
逻辑地址 4G 物理内存 数据1 应用程序1的页表 数据2 代码1 数据1 0 代码2 4G 数据2 代码1 应用程序2的页表 操作系统 0 代码2 0 应用程序1 应用程序2 基于静态域的MMU使用方式 也称为是一一对应的使用方式。 在这种一一对应的使用方式中,应用程序的逻辑地址同应用程序在物理内存中的物理地址相同,简化了内存管理的实现方式。
内存保护 • 在嵌入式实时操作系统中,MMU通常被用来进行内存保护: • 实现操作系统与应用程序的隔离 • 应用程序和应用程序之间的隔离 • 防止应用程序破坏操作系统的代码、数据以及应用程序对硬件的直接访问。 • 对于应用程序来讲,也可以防止别的应用程序对自己的非法入侵,从而破坏应用程序自身的运行。
内存保护 • 在内存保护方面,MMU提供了以下措施: • 防止地址越界 • 通过限长寄存器检查逻辑地址,确保应用程序只能访问逻辑地址空间所对应的、限定的物理地址空间,MMU将在逻辑地址超越限长寄存器所限定的范围时产生异常; • 防止操作越权 • 根据内存页面的特权信息控制应用程序对内存页面的访问,如果对内存页面的访问违背了内存页面的特权信息,MMU将产生异常。
应用1 应用2 应用3 操作系统 简单的MMU保护模式 应用程序之间要通信就只能通过操作系统提供的通信服务,如:信号量、管道、消息、共享内存等,而不能直接访问彼此的地址空间。 MMU通常还提供权限等级,不同的权限等级对硬件访问的权限不一样。操作系统一般运行在核心态,具有所有的特权,而应用则一般运行在用户态,具有一般权限,以防止应用程序的故意破坏。