一、虚拟内存管理
我们都知道,每个用户进程可以拥有4GB的可寻址的虚拟地址空间,也可以称为进程地址空间。在32位系统中,进程可以合法访问用户空间的虚拟地址。若要访问内核虚拟地址,就要通过系统调用才行。
内核中是如何来管理这些进程地址空间的呢?根据内核风格,抽象出一个结构体来描述这个进程地址空间,这个结构体就是struct vm_area_struct,看名称可以知道,这就是一虚拟地址的结构体,简称VMA。
一个进程包含下列内存区域:代码段、数据段、用户进程栈、MMAP区域、堆区域。
I、内存描述符mm_struct
linux内核需要管理每个进程所有的内存区域和它们对应的页映射,所以必须抽象出一个数据结构,就是mm_struct数据结构,进程控制块PCB数据结构task_struct中有一个指针mm指向这个mm_struct数据结构。在include\linux\mm_types.h有定义。下面是它的主要成员。
mmap:进程所有的VMA形成一个单链表,这是该链表的链表头
mm_rb:VMA红黑树的根结点
get_unmapped_area:用来判断虚拟内存是否有足够的空间
mmap_base:指向mmap区域的起始地址
pgd:指向进程的页表PGD目录(以及页表)
mm_users:记录正在使用该进程地址空间的线程数目
mm_count:mm_struct的主引用计数
mmap_sem:保护进程地址空间VMA的一个读写信号量
start_code,end_code:代码段的起始地址和终止地址
start_data,end_data:数据段的起始地址和终止地址
brk:表示当前堆中VMA的结束地址
struct mm_struct {
struct vm_area_struct *mmap; /* list of VMAs */
struct rb_root mm_rb;
u32 vmacache_seqnum; /* per-thread vmacache */
unsigned long (*get_unmapped_area) (struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags);
unsigned long mmap_base; /* base of mmap area */
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) */
atomic_long_t nr_ptes; /* PTE page table pages */
atomic_long_t nr_pmds; /* PMD page table pages */
int map_count; /* number of VMAs */
spinlock_t page_table_lock; /* Protects page tables and some counters */
struct rw_semaphore mmap_sem;
struct list_head mmlist; /* List of maybe swapped mm's. These are globally strung
* together off init_mm.mmlist, and are protected
* by mmlist_lock
*/
unsigned long hiwater_rss; /* High-watermark of RSS usage */
unsigned long hiwater_vm; /* High-water virtual memory usage */
unsigned long total_vm; /* Total pages mapped */
unsigned long locked_vm; /* Pages that have PG_mlocked set */
unsigned long pinned_vm; /* Refcount permanently increased */
unsigned long shared_vm; /* Shared pages (files) */
unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE */
unsigned long stack_vm; /* VM_GROWSUP/DOWN */
unsigned long def_flags;
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
...
}
II、malloc实现
malloc是C语言中最常见用的内存分配函数。
先思考以下几个问题:
1、malloc返回的内存是否马上就分配物理内存?如果不是,什么时候才分配?
2、malloc申请100个字节,内核就分配100个字节给进程吗?
3、两个进程malloc之后返回的虚拟地址是一样的,是否代表两者的物理内存也一样呢?
malloc函数其实是为用户空间分配进程地址空间的,用内核术语来说就是分配一块VMA,相当于一块空的纸箱。当使用这段内存时,CPU去查询页表,发现页表并没有映射到物理地址上,即为空。那么CPU会发出缺页异常,然后在缺页异常里一页一页的分配内存。
我们都知道处理器的mmu硬件最小处理单元是页,所以内核分配内存、建立虚拟地址和物理地址映射关系都是以页为单位的。
每个进程都有自己的一份页表,mm_struct有一个pgd成员指向这个页表的基地址,每个进程都有一个mm_struct数据结构,进程的VMA会挂入自己的红黑树和链表,即使两个进程分配到的虚拟地址是一样的,但是他们也有不同的VMA,分别被两套页表来管理。
以下为malloc实现流程: