虚拟存储的基本思想:
一个进程的代码、数据、堆栈总量可能超过物理内存容量,OS复制把当前用到的部分留在内存中,把其他部分放在磁盘上(swap区)。当需要用到的某一部分不在内存中,则把暂时不用的写回磁盘,把需要的调入内存。
进程直接产生的是虚拟地址,虚拟地址空间可能比物理内存大的多(不是和磁盘比较),虚拟地址在使用时先送到MMU,MMU把映射成的物理地址送到内存总线。内存和磁盘间的数据交换以页为单位,虚拟地址的页需要映射到物理地址中页帧,页未被映射的内容存于磁盘,present bit表示该页在不在内存上。当进程访问未映射的页时产生缺页失效,进入缺页中断,OS根据一定算法写回页和把需要访问的页写入页帧。虚拟内存的页无需在物理内存中保持特定的顺序。
页表
32为系统当页大小为4K将有2^20个页,可以使用三级页表2^7 * 2^7 * 2^6,一级把4G空间分成128个32M,二级把32M分成128个256K,三级把256K分成64个4K。32位系统中大多数进程用到的虚拟地址空间只是4G空间的一小部分(最低端存放代码,最高端存放堆栈),其余大部分用不到。所以除了顶级页表和若干二、三级页表,中间的很多二、三级页表都不需要装入内存。
页表项的每个条目包含权限控制、存在、修改、访问位等。
页表如果都存在内存每个指令的内存操作都要进行查表,页表访问将影响系统效率。使用TLB(转译后备缓冲)它的访问速度比物理内存快,在TLB中存储最近访问过的页表项。
Linux虚拟内存管理
当命令执行时,可执行的命令文件被打开,同时其内容被映射到进程的虚拟内存。这些操作通过修改描述进程内存映像的数据结构来完成,称为内存映射。只有映像的起始部分调入内存,其余部分仍留在磁盘。
为了将虚拟地址转换成物理地址,处理器必须依次得到每级页表包含的偏移值。还需要有页目录在物理内存中的起始地址,该地址保存在寄存器中。
- 处理器首先根据页目录在物理内存中的起始地址和第一个偏移值(虚拟地址中),访问页目录。得到中间页目录的起始地址。
- 然后根据中间目录的起始地址和第二个偏移值,访问中间页目录,得到页表的起始地址
- 再根据页表的起始地址和第三个偏移值访问页表,得到页帧号
- 最后根据页帧号和页内偏移得出物理地址
linux/include/asm-i386/pgtable-3level.h
- 该文件中定义三层页表相关宏
linux/include/linux/sched.h
- linux启动一个新进程则为其分配一个task_struct结构体
- 该结构体中包含指向mm_struct的结构体指针,mm_struct包含了用户进程和存储管理有关的信息
struct mm_struct {
struct vm_area_struct * mmap; /* 虚存结构链表 */
rb_root_t mm_rb; /* 虚存构成的红黑树 */
struct vm_area_struct * mmap_cache; /* last find_vma result */
pgd_t * pgd;
atomic_t mm_users; /* How many users with user space? */
atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */
int map_count; /* number of VMAs */
struct rw_semaphore mmap_sem;
spinlock_t page_table_lock; /* Protects task page tables and mm->rss */
struct list_head mmlist;
//进程代码段起始地址和结束地址,数据段起始地址和结束地址
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
unsigned long rss, total_vm, locked_vm;
unsigned long def_flags;
unsigned long cpu_vm_mask;
unsigned long swap_address;
unsigned dumpable:1;
/* Architecture-specific MM context */
mm_context_t context;
};
程序执行时,可执行映像的内容被调入进程虚拟地址空间(没有调入物理内存,仅仅是连接到进程的虚拟地址)
- struct mm_struct第一项struct vm_area_struct 指针指向的结构(多个由链表串起来)描述了虚拟内存的起始和结束地址,进程对次内存的存取权限以及一组内存操作函数。
struct vm_area_struct
- 每个struct vm_area_struct数据结构表示可执行映像的一部分:可执行代码、初始化数据、未初始化数据等待。
- 进程虚拟空间的0-3GB可以直接使用,剩余的1GB属于内核。在创建用户进程时,核心代码段和数据段被映射到3GB以后的虚拟空间,供内核使用。所有进程的3-4GB虚拟空间的映像都是相同的。
//linux/include/linux/mm/h
struct vm_area_struct {
struct mm_struct * vm_mm; /* The address space we belong to. */
unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end; /* The first byte after our end address
within vm_mm. */
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next;
pgprot_t vm_page_prot; /* Access permissions of this VMA. */
unsigned long vm_flags; /* Flags, listed below. */
rb_node_t vm_rb;
/*
* For areas with an address space and backing store,
* one of the address_space->i_mmap{,shared} lists,
* for shm areas, the list of attaches, otherwise unused.
*/
struct vm_area_struct *vm_next_share;
struct vm_area_struct **vm_pprev_share;
/* Function pointers to deal with this struct. */
struct vm_operations_struct * vm_ops;
/* Information about our backing store: */
unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE
units, *not* PAGE_CACHE_SIZE */
struct file * vm_file; /* File we map to (can be NULL). */
unsigned long vm_raend; /* XXX: put full readahead info here. */
void * vm_private_data; /* was vm_pte (shared mem) */
};
mem_map_t描述每个物理页面,启动时初始化,内容记录页面的年龄、物理页帧号
//linux/include/linux/mm/h
typedef struct page {
struct list_head list; /* ->mapping has some page lists. */
struct address_space *mapping; /* The inode (or ...) we belong to. */
unsigned long index; /* Our offset within mapping. */
struct page *next_hash; /* Next page sharing our hash bucket in
the pagecache hash table. */
atomic_t count; /* Usage count, see below. */
unsigned long flags; /* atomic flags, some possibly
updated asynchronously */
struct list_head lru; /* Pageout list, eg. active_list;
protected by pagemap_lru_lock !! */
wait_queue_head_t wait; /* Page locked? Stand in line... */
struct page **pprev_hash; /* Complement to *next_hash. */
struct buffer_head * buffers; /* Buffer maps us to a disk block. */
void *virtual; /* Kernel virtual address (NULL if
not kmapped, ie. highmem) */
struct zone_struct *zone; /* Memory zone we are in. */
} mem_map_t;
include/linux/mmzone.h
-zone_t中有一个free_area_t数组
- 数组元素0维护1个页面大小的空闲块的链表
- 元素2维护2^2=4个页面大小的空闲块链表
- free_list表示队列头
- map是每一块是否空闲的标志,如第N块空闲则第N位置位
页面分配
从free_area数据结构的free_list域着手沿链表搜索空闲块,如果某一个链表没有空闲块,则继续在下一个元素维护的链表中搜索。如果找到的空闲块不小于请求块的两倍,则将其分割成两部分,一部分分配一部分放入上个链表。
typedef struct free_area_struct {
struct list_head free_list;
unsigned long *map;
} free_area_t;
typedef struct zone_struct {
/*
* Commonly accessed fields:
*/
spinlock_t lock;
unsigned long free_pages;
unsigned long pages_min, pages_low, pages_high;
int need_balance;
/*
* free areas of different sizes
*/
free_area_t free_area[MAX_ORDER];
/*
* Discontig memory support fields.
*/
struct pglist_data *zone_pgdat;
struct page *zone_mem_map;
unsigned long zone_start_paddr;
unsigned long zone_start_mapnr;
/*
* rarely used fields:
*/
char *name;
unsigned long size;
} zone_t;
按需调页
- arch/i386/mm/fault.c
- 缺页中断会进入do_page_fault函数