MMU具有物理地址和虚拟地址转换,内存访问权限保护等功能。这使得Linux操作系统能单独为每个用户进程分配独立的内存空间并且保证用户空间不能访问内核空间的地址,为操作系统虚拟内存管理模块提供硬件基础。
Linux内存管理
在Linux操作系统中,进程的4G空间被分成两个部分----用户空间和内核空间。用户空间一般为0~3GB(即PAGE_OFFSET,在X86系统中等于0xC0000000),而剩余的3GB~4GB为内核空间。用户进程在通常情况下只能访问用户空间的虚拟地址,不能访问内核空间的虚拟地址。用户只有通过系统调用才能访问到内核空间。
每个进程的用户空间都是完全独立的、互不相干的,用户进程各自有不同的页表。内核进程是由内核负责映射的,它并不会跟着用户进程改变,是固定的。内核空间的虚拟地址独立其他程序的空间。
Linux内核中的1GB的内核地址划分为物理内存映射区、虚拟内存分配区、高端页面映射区、专用页面映射区和保留区。
在物理区和和高端映射区之间的是虚拟内存分配区,用于vmalloc()函数,他的前部与物理区存在一个隔离带,他的后部与高端内存映射区存在一个隔离带。
内存存取
1.用户空间内存动态申请
函数为malloc,malloc( )申请的一定要free( )释放,这两个函数成对使用,否则会造成内存泄露。对于Linux内核而言,C库的malloc()函数通常通过底层的brk( )和mmap( )来实现。
2.内核空间内存动态申请
内核申请涉及到的函数主要有kmalloc( )、__get_free_page( )和vmalloc( )、kmalloc( )和__get_free_page( )(及其相关函数)申请的内存位于物理内存映射区,在物理上也是连续的,他们与真实的物理地址相差一个固定的偏移量。而vmalloc()申请的内存位于虚拟内存映射区的一块内存,这块内存在物理内存上并不一定连续,而vmalloc()申请的虚拟内存和物理内存之间也没有简单的换算关系。
(1)kmalloc( )
kmalloc()的第一个参数为要分配的内存块的大小,第二个参数为标志位,用于控制kmalloc()函数的行为。最常用的标志位是GFP_KERNERL,含义是在内核的虚拟空间中申请内存。kmalloc的底层依赖于__get_free_page()来实现。使用__get_free_page( )时,若暂时不能满足,则进程会睡眠。因此不能再中断上下文或者持有自旋锁的时候不能使用GFP_KERNERL标志。
在中断处理函数中,或者tasklet和定时器等非进程上下文不能阻塞时,可以使用GFP_ATOMIC标志位。用此标志申请时,若内核没有空闲,则不会等待。
使用kmalloc( )申请的内存由kfree( )释放。
(2)__get_free_page( )
此函数是Linux内核本质上的最底层用于获取空闲内存的方法。因为底层的算法以page的2的n次方来管理内存。所以最底层的实现是以页为单位。
此系列函数包含__get_zeroed_page()返回一个指向新页的指针,并且将该页清零。__get_free_page( )返回一个指向新页的指针,但是该页不清零。__get_free_pages( )该函数返回一个指向几页的指针的首地址,分配的页数为2的oder次方,并且分配的页数也不清零。oder允许的最大值为10(即1024页)或者11(2048),具体依赖于硬件平台。释放的时候采用free_page( )。如果释放的oder 和申请的oder不一致,则会造成内存混乱。
(3)vmalloc()一般只存在于软件中的较大的顺序缓冲区的分配内存,vmalloc()远大于__get_free_page()的开销为了完成vmalloc,新的页表需要被建立。因此用vmalloc完成少量内存的申请是不妥的。
vmalloc不能用在原子上下文,因为他的内部实现时调用__GFP_KERNERL标志位的kmalloc来实现的。
(4)slab与内存池
在Linux系统中用页作为单元来申请和释放内存是很容易导致浪费。另外,在操作系统中,进程会涉及到大量的重复生成、释放问题。例如:inode,task_struct 等。slab 就是使得在前后两次使用时分配在同一内存区域,或者同一类内存空间,并且保留了基本的数据结构,就可以达到提高效率。
slab的使用流程如下:
注:slab 在底层虽然依赖于__get_free_page( )来实现,但是,申请到的以页为单位的内存空间分隔成更小的内存单元来管理,这样不会造成内存的浪费。在Linux中还有内存池的使用,同样用于大量的小对象的后备缓冲技术的使用。