- 论坛徽章:
- 0
|
一、 LINUX内存管理简介
1.1.LINUX的分页管理机制
在LINUX中,每一个用户进程都可以访问4GB的线性虚拟内存空间。其中从0到3GB的虚拟内存地址是用户空间,用户进程可以直接对其进行访问。从3GB到4GB的虚拟内存地址为核心空间,存放仅供核心态访问的代码和数据,用户进程不可访问。当用户进程通过中断或系统调用对其访问时,就会触发处理器的特权级转换(从处理器的特权级3切换到特权级0),即从操作系统的用户态切换到核心态。
所有进程从3GB到4GB的虚拟空间映像都是一样的,LINUX以此方式让核心态进程共享代码段和数据段。
核心态虚拟空间从3GB到3GB+4M的一段(也就是进程页目录第768项所管辖的范围),被影射到物理空间0到4M段。因此,进程处于核心态时,只要通过访问虚拟空间3GB到3GB+4M段,即访问了物理空间0到4M段。
上述两种空间对用户进程来说都是透明的,用户进程所访问的内存地址都是连续的4GB线性虚拟地址。因此,我们首先关心的是LINUX是如何划分虚拟空间的。
LINUX采用“按需调页”(Demand Paging)技术管理虚拟内存。标准LINUX的虚存页表应为三级页表,依次为页目录(PGD,Page Directory)、中间页目录(PMD,Page Middle Directory)和页表(PTE,Page Table)。如图1-1所示。
PGD PMD PTE Page Frame
图1-1,LINUX的三级页表结构
在INTEL微机上,LINUX的页表结构实际为两级。80386体系结构之页管理机制中的页目录就是PGD,页表就是PTE。而PMD和PGD实际上是合二为一的。所有有关PMD的操作实际上是对PGD的操作。所以源代码中形如*_pgd_*()和*_pmd_*()的函数所实现的功能也是一样的。其实现方法是提供一组转换宏(/include/asm/pgtable.h),使得转换页表时不需要知道页表的入口格式。宏定义形如:
#define _PAGE_4M 0x080 /* 4 MB page */
/* PMD_SHIFT determines the size of the area a second-level page table can map */
#define PMD_SHIFT 22
#define PMD_SIZE (1UL<< MD_SHIFT) /*PMD页表大小为4G=1024*4K
#define PMD_MASK (~(PMD_SIZE-1))
/* PGDIR_SHIFT determines what a third-level page table entry can map */
#define PGDIR_SHIFT 22
#define PGDIR_SIZE (1UL << PGDIR_SHIFT)
#define PGDIR_MASK (~(PGDIR_SIZE-1))
#define PTRS_PER_PTE 1024
#define PTRS_PER_PMD 1
#define PTRS_PER_PGD 1024
extern inline pmd_t * pmd_alloc_kernel(pgd_t * pgd, unsigned long address)
{
return (pmd_t *) pgd;
}
extern inline pmd_t * pmd_alloc(pgd_t * pgd, unsigned long address)
{
return (pmd_t *) pgd;
}
每当启动一个新进程,LINUX都为其分配一个task_struct结构,内含saved_kernel_stack、kernel_stack_page、ldt、tss、mm等内存管理信息,详见进程管理部分。其中,task_struct结构内嵌mm_struct结构,此结构包含了用户进程中与存储有关的信息。
进程控制块中有关存储管理的数据结构有如下五项:
1. unsigned long saved_kernel_stack:在虚拟86方式下,用于保存核心堆栈的地址;
2. unsigned long kernel_stack_page:每个进程都要分配一个页框用作核心堆栈;
3. struct desc_struct *ldt:进程局部描述符表描述符的指针;
4. struct thread_struct tss:进程的任务状态段;
5. struct mm_struct mm[1]:进程用于存储管理的数据都存放于此。如图1-2所示:
此外,每一个进程都有一个页目录,存储该进程所使用的内存页面情况。LINUX按照“按需调页”的原则只分配必需的内存页面,从而避免了页表过多占用存储空间(最坏情况下多达4MB)的情况出现。例如,系统调用fork分配内存页面的情况如下:
l 进程控制块(task_struct结构)1页
l 核心态堆栈1页
l 页目录1页
l 页表占几页(视需要)
系统调用exec分配内存页面的情况如下:
l 可执行文件的文件头1页
l 用户堆栈1页或几页(视需要)
这样,当进程开始运行时,如果执行代码不在内存中,将产生第一次缺页中断,让操作系统分配内存页面,并将执行代码装入内存中。此后,按需要逐渐分配更多的内存页面,并参与页面调度。当系统内存不足时,由操作系统决定是否将该进程的一部分页面换出到磁盘交换区或交换文件中。进程终止时,操作系统释放所有该进程占用的资源,包括内存页面。
1.2.虚存段(vma)的组织和管理
用户共有4GB的虚存空间,但并不是所有的4GB空间用户都可以读写或申请使用。用户实际可申请的虚存空间为0至3GB。在用户进程创建时,已由系统调用fork()将核心的代码段和数据段映射到3GB以后的虚存空间。
所有进程的3GB至4GB的虚存空间的映象都是相同的,以此方式共享核心的代码段和数据段。
为了便于管理,LINUX 将进程虚存空间以虚存段(vma,即virtual memory area)进行管理。这在后面的分析系统调用mlock的数据结构时会详细介绍。
1.3 内存的共享和保护
图1-3,LINUX页共享结构
有些UNIX的内存共享以页表共享的形式实现:设立一临时的公共页表,各进程原本指向共享页表的表项改为指向公共页表的表项,公共页表的表项再指向共享页的地址。这种结构执行效率高:当共享页状态发生变化时,只需修改公共页表的表项即可,只有一次页表的访问操作。
LINUX中内存共享却以页共享的形式实现,共享该页的各进程的页表表项直接指向共享页,见图1-3。这种结构不需设立共享页表,节约内存的占用;但效率较低,当共享页状态发生变化时,共享该页的各进程的页表共享该页的各进程的页表均需修改,要多次访问页表。
LINUX可对虚存段中任一部分加锁或保护(mm/mprotect.c,mm/mlock.c)。虚存段加锁、保护操作可以有四种方式(图1-4):
l 对整个虚存段加锁或保护
l 对虚存段前部加锁或保护
l 对虚存段后部加锁或保护
l 对虚存段中部加锁或保护
图1-4
后三种方式下,需将原vma段分割成两个或三个。分割后的vma段经调用insert_vm_struct()插入vma段的AVL树和链表中。为了减少多余的vma结构,还需调用merge_segment()(mm/mmap.c),尽量合并相邻的vma段。
保护操作的函数调用层次如图1-5,加锁操作的函数调用层次如图1-6。
图1-5
图1-6 |
|