当给用户态进程分配内存时:
- 进程对动态内存的请求被认为是不紧迫的。因此,一般来说,内核总是尽量推迟给用户态进程分配动态内存。
- 由于用户进程是不可信任的,因此,内核必须能随时准备捕获用户态进程引起的所有寻址错误。
当用户态进程请求动态内存时,并没有获得请求的页框,而仅仅获得对一个新的线性地址区间的使用权,而这一线性地址区间旧成为进程地址空间的一部分。这一区间叫做“线性区”。
进程的地址空间
进程的地址空间由允许进程使用的全部线性地址组成。每个进程所看到的线性地址集合是不同的。内核通过所谓线性区的资源来表示线性地址区间,线性区是由起始线性地址、长度和一些访问权限来描述的。为了效率起见,起始地址和线性区的长度都必须是4096的倍数,以便每个线性区所识别的数据完全能填满分配给他的页框。下面是进程获得新线性区的典型情况:
- 执行新的进程的时候,一个全新的地址空间分配给新进程。
- 正在运行的进程,又调用新的进程,进程标示符不变,装入这个程序以前的线性区被释放,并有一组新的线性区被分配给这个进程。
- 进程对一个文件执行“内存映射”,这时分配新的线性区来映射这个文件
- 进程可能持续对用户态堆栈增加数据,直到映射这个堆栈的线性区用完为止。内核也许会决定扩展这个线性区的大小。
- 进程可能创建一个IPC共享线性区来与其他合作进程共享数据,内核分配一个新的线性区来实现这个方案。
- 进程通过malloc类似的函数扩展自己的动态区,内核可能决定扩展给这个堆分配的线性区。
brk() | 改变进程堆的大小 |
execve() | 装入一个新的可执行文件,从而改变进程的地址空间 |
_exit() | 结束当前进程并撤销它的地址空间 |
fork() | 创建一个新进程,并为它创建新的地址空间 |
mmap,mmap2 | 为文件创建一个内存映射,从而扩大进程的地址空间 |
mremap | 扩大或缩小线性区 |
remap_file_pages | 为文件创建非线性映射 |
munmap | 撤销对文件的内存映射,从而缩小进程的地址空间 |
shmat | 创建一个共享线性区 |
shmdt | 撤销一个共享线性区 |
确定一个进程当前所拥有的线性区是内核的基本任务,这可以让缺页异常处理程序有效地区分引发这个异常处理程序的两种不同类型的无效线性地址:
- 由编程错误引发的无效线性地址
- 由缺页引发的无效线性地址;
内存描述符
与内存地址空间有关的全部信息包含在内存描述符的数据结构中。这个结构的类型为mm_struct,进程描述符的mm字段就指向这个结构。所有内存描述符存放在一个双向链表中,每个描述符在mmlist字段存放链表相邻元素的地址。链表的第一个元素是init_mm的mmlist字段。init_mm时初始化阶段进程0所使用的内存描述符。mmlist_lock自旋锁保护多处理器系统对链表的同时访问。mm_users字段存放共享mm_struct数据结构的轻量级进程的个数。mm_count字段是内存描述符的主使用计数器。
一个内存描述符由两个轻量级进程共享,它的mm_users字段为2,而mm_count字段存放的为1。如果内核想确保内存描述符在一个长操作的中间不被释放,那么,就应该增加mm_users字段而不是mm_count字段的值。
struct mm_struct {
struct vm_area_struct * mmap; /* 指向线性区对象的链表头 */
struct rb_root mm_rb; //指向线性区对象的红-黑树的根
struct vm_area_struct * mmap_cache; /* 指向最后一个引用的线性区对象 */
unsigned long (*get_unmapped_area) (struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags); //在进程地址空间中搜索有效线性地址区间的方法
void (*unmap_area) (struct vm_area_struct *area);//释放线性地址区间时调用的方法
unsigned long mmap_base; /* 标识第一个分配的匿名线性区域或文件内存映射的线性地址 */
unsigned long free_area_cache; /* 内核从这个地址开始搜索进程地址空间中线性地址的空闲区间 */
pgd_t * pgd; //指向页全局目录
atomic_t mm_users; /* 次使用计数器 */
atomic_t mm_count; /* 主使用计数器 */
int map_count; /* 线性区的个数*/
struct rw_semaphore mmap_sem; //线性区的读/写信号量
spinlock_t page_table_lock; /* 线性区的自旋锁和页表的自旋锁 */
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, anon_rss, total_vm, locked_vm, shared_vm;
unsigned long exec_vm, stack_vm, reserved_vm, def_flags, nr_ptes;
unsigned long saved_auxv[42]; /* for /proc/PID/auxv */
unsigned dumpable:1;
cpumask_t cpu_vm_mask;
/* Architecture-specific MM context */
mm_context_t context;
/* Token based thrashing protection. */
unsigned long swap_token_time;
char recent_pagein;
/* coredumping support */
int core_waiters;
struct completion *core_startup_done, core_done;
/* aio bits */
rwlock_t ioctx_list_lock;
struct kioctx *ioctx_list;
struct kioctx default_kioctx;
unsigned long hiwater_rss; /* High-water RSS usage */
unsigned long hiwater_vm; /* High-water virtual memory usage */
};
内核线程的内存描述符
内核线程仅运行在内核态。内核线程不用线性区,因此内核线程的很多字段对内核线程是没有意义的。
进程描述符的mm字段指向进程所拥有的内存描述符,而active_mm字段指向进程运行时所使用的内存描述符。内核线程不用有任何内存描述符,所以mm字段总是NULL,内核线程得以运行时,它的的active_mm字段被初始化为前一个运行进程的active_mm值。
线性区
linux通过类型为vm_area_struct的对象实现线性区。
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. */
struct rb_node vm_rb;
union {
struct {
struct list_head list;
void *parent; /* aligns with prio_tree_node parent */
struct vm_area_struct *head;
} vm_set;
struct raw_prio_tree_node prio_tree_node;
} shared;
struct list_head anon_vma_node; /* Serialized by anon_vma->lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock */
/* 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). */
void * vm_private_data; /* was vm_pte (shared mem) */
unsigned long vm_truncate_count;/* truncate_count or restart_addr */
#ifndef CONFIG_MMU
atomic_t vm_usage; /* refcount (VMAs shared if !MMU) */
#endif
#ifdef CONFIG_NUMA
struct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
};
每个线性区描述符表示一个线性地址区间。vm_start字段包含区间的第一个线性地址,而vm_end字段包含区间之外的第一个线性地址。