一、内存管理(用户空间)
可执行程序在存储时分代码区(Text)、数据区(data)、未初始化数据区(bss);在运行时,又多了堆区、栈区;
1、代码区:存放可执行代码,指令按程序设计流程执行;
2、数据区:存放全局初始化数据和静态数据,只初始化一次;
3、BSS区:在运行时,会改变数据的值;
4、栈区(stack):由编译器自动分配释放,存放函数参数值,局部变量值等;
5、堆区(heap):用于动态分配内存,由程序员分配和释放,程序结束时由OS回收;
内存分配和释放函数:
函数所在库文件#include
void *malloc(size_t size):在堆中申请内存空间,若配置成功则返回一指针,失败则返回NULL。
void free(void *ptr):释放申请的内存空间;
void *realloc(void *ptr,size_t size):更改已经分配的内存空间,扩容;
void *calloc(size_t nmemb,size_t size):配置nmemb个相邻的内存单位,每一单位的大小为size,并将内存内容初始化为0,等效于 malloc(nmemb*size),在memset清零;
二、内存管理(内核空间)
大多数的嵌入式微控制器只存在内存空间,无I/O空间,内存空间可以直接通过地址、指针来访问,程序和程序运行中使用的变量和其他数据都存在于内存空间中。
内存管理单元(MMU)辅助操作系统实现内存管理、虚拟地址和物理地址的映射、内存访问权限保护、Cache缓存控制。
Linux系统提供了复杂的存储管理系统,使得进程所能访问的内存达到4GB。并分为用户空间0-3GB和内核空间3-4GB。用户进程只有通过系统调用(代表用户进程在内核态执行)等方式才可以访问到内核空间。
1GB的内核空间划分:(从低地址到高地址)物理内存映射区、虚拟内存分配区、高端内存映射区、专用页面映射区、系统保留映射区。
虚拟内存抽象模型及虚存段:
当处理器执行一个程序时,它从内存中读取指令并解码执行。当执行这条指令时,处理器将还需要在内存的某一个位置读取或存储数据。处理器根据操作系统保存的一些信息(页表)将虚拟内存地址转换为物理地址。
为了让这种转换更为容易进行,虚拟内存和物理内存都分为大小固定的块,叫做页面。每一个页面有一个唯一的页帧号,叫做PFN(page frame number)。
在这种分页方式下,一个虚拟内存地址由两部分组成:一部分是位移地址,另一部分是页帧号。每当处理器遇到一个虚拟内存地址时,它都将会分离出位移地址和页帧号。然后再将页帧号翻译成物理地址,以便正确地读取其中的位移地址。
处理器利用页表来完成上述的工作。抽象模型可以理解成一个简单一级的页表结构。
又,程序执行时,可执行映像的内容被调入进程虚拟地址空间,它的共享库同样如此,然而可执行文件实际上并没有全部调入到物理内存中,而是仅仅连接到进程的虚拟内存。当程序的其他部分运行需要引用这部分时才把它们从磁盘上调入内存。每个进程的虚拟内存用一个mm_struct来表示,它包含一些vm_area_struct的指针,描述了虚拟内存的起始、结束位置,存取权限,内存操作函数。当一个进程试图访问虚拟地址不在物理内存时(发生缺页中断),则用到nopage()函数,会调用缺页中断函数do_page_fault()。
Linux分页管理(三级页表结构):
虚拟存储系统中的所有地址都是虚拟地址,而不是物理地址,通过页表机制,由处理器(确切说是MMU)实现从虚拟地址到物理地址的转换。
Linux的缺省页面大小为8KB,则虚拟地址的低13位表示虚拟地址页内偏移量,其余表示页号(三级页表结构中表示页目录PGD、中间目录PMD、页表PTE),MMU通过此机制最后根据页帧号和页内偏移量得出物理地址。
虚拟地址与物理地址的关系(用于常规内存):
偏移量PAGE_OFFSET通常为3GB;
virt_to_phys()—内核虚拟地址转化为物理地址,即__pa(x);
phys_to_virt()—物理地址转化为内核虚拟地址,即__va(x);
代码清单如下:
#ifndef __virt_to_phys #define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET) #define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET) #endif
static inline unsigned long virt_to_phys(void *x) { return __virt_to_phys((unsigned long)(x)); }
static inline void *phys_to_virt(unsigned long x) { return (void *)(__phys_to_virt((unsigned long)(x))); }
/* * Drivers should NOT use these either. */ #define __pa(x) __virt_to_phys((unsigned long)(x)) #define __va(x) ((void *)__phys_to_virt((unsigned long)(x))) #define pfn_to_kaddr(pfn) __va((pfn) << PAGE_SHIFT)
|
内存分配和释放函数:
void *kmalloc(size_t size,int flags):申请内存位于物理内存映射区,物理上连续;
void kfree(*ptr):对应的释放内存函数;
__get_free_page:此系列函数是kmalloc实现的基础,它返回一个指向新页的指针;
__get_zeroed_page:返回一个指向新页的指针并将该页清零;
__get_free_pages(unsigned int flags,unsigned int order):分配多页并返回首地址,页数为2order;
void free_page(unsigned long addr)、void free_page(unsigned long addr,unsigned long order);
void *vmalloc(unsigned long size):分配较大的内存页;
void vfree(void *ptr):释放内存;
flags标志参数(常用):
GFP_KERNEL—会引起阻塞所以在中断处理函数、tasklet和内核定时器不能用;
GFP_ATOMIC—在中断处理函数、tasklet和内核定时器等非进程上下文使用,是原子级的,不会阻塞;
__GFP_DMA—具备DMA能力;
除此之外还有slab和内存池,用于分配大量小对象的后备缓存技术。
int (*mmap)(struct file *,struct vm_area_struct *):内存映射,将用户空间的一段内存直接与设备内存关联,当用户访问这段地址时,实际转化为对设备的访问,大小必须是页的整数倍。
阅读(1520) | 评论(0) | 转发(0) |