内存管理
一、程序的编译链接过程
1、预处理
主要处理一下几个方面内容:
①宏定义 ②文件包含 ③条件编译 ④去掉注释
2、编译
把源代码转换成相应的汇编语言的过程
3、汇编
把汇编语言转换成二进制代码,即目标程序
4、链接
将多个目标程序连同库文件(静态库、动态库)一起整合成一个可执行文件,可以被操作系统载入内存执行。
☆☆☆在这个过程有一个重要的过程:产生用来表示操作数或指令的地址逻辑地址 《深入理解计算机系统》
二、虚拟内存空间的映射
当我们运行可执行文件时,操作系统把它加载到内存,经过一系列的初始化、设置相关寄存器的值等动作建立一个进程,放入队列等待内核调度被CPU执行。
首先我们理解可执行文件中的逻辑地址是如何被映射到物理地址的?
逻辑地址由两部分组成:段标识符、偏移量
1、段标识符是一个16位长的字段
2、偏移量是一个32位长的字段
段标识符的信息被存储在段寄存器中,内核在建立一个进程时都要将其段寄存器设置好。
CPU中设置了6个段寄存器,其中3个有专门的用途:
cs 代码段寄存器,指向包含程序指令的段
ss 栈段寄存器,指向包含当前程序栈的段
ds 数据段寄存器,指向包含静态数据或全局数据的段
段寄存器的格式如下:
各字段的含义:
Index:指明段描述符项的索引号
TI:指明段描述符是在全局描述符表GDT中还是局部描述表LDT中
RPL:CPU当前的特权级(0——内核态 3——用户态)
逻辑地址转换为线性地址的过程:(如下图示意过程)
①首先从段寄存器中取得Index
②从寄存器gdtr或ldtr取得GDT或LDT的地址(由TI字段指定,对Linux总是选择GDT),通过Index可以得到具体的段描述符项
③从段描述符可以得到线性地址的基址,基址+偏移量——>线性地址
对Linux而言,从段描述符得到的线性地址的基址为0X0,所以逻辑地址总是把自身映射到线性地址。
Linux采用页式内存管理,它的段式内存映射只是为了与兼容特定的硬件(如Intel的i386)
三、页式内存管理(线性地址——>物理地址)
对于32位系统来说,Linux采用两级分页
寻址过程:
①cr3寄存器保存页目录的基址,线性地址的DIRECTORY字段记录了目录项的偏移量,基址+偏移量就可以找到对应的目录项
②目录项里记录了页表的基址,线性地址的TABLE字段记录了表项的偏移量,基址+偏移量就可以找到对应的表项
③表项中记录了物理地址的基址,线性地址的OFFSET字段记录了物理地址的偏移量,基址+偏移量就可以找到对应的物理地址
对于64位系统来说,Linux采用三级或四级分页,依赖具体的硬件平台
以下是内核对物理空间的管理以及相应的数据结构
2、内核中把物理地址划分成许多物理页,每个物理页定义成一个page数据结构
struct page {
page_flags_t flags; /* Atomic flags, some possibly
* updated asynchronously */
atomic_t _count; /* Usage count, see below. */
atomic_t _mapcount;
unsigned long private;
struct address_space *mapping;
pgoff_t index; /* Our offset within mapping. */
struct list_head lru;
#if defined(WANT_PAGE_VIRTUAL)
void *virtual; /* Kernel virtual address (NULL if
not kmapped, ie. highmem) */
#endif /* WANT_PAGE_VIRTUAL */
};
3、物理内存就被表示成一个很大的page结构数组,并定义一个全局指针变量指向该数组。类似于下面这样:struct page *mem_map;
struct page physical_page[PAGE_NUM];
mem_map = physical_page;
系统中的每一个物理页都有一个page结构。系统在初始化时根据物理内存的大小建立一个page结构数组,作为物理页面的“仓库”,里面的每个page结构元素都代表着一个物理页面。
4、在多核处理器体系结构中,整个物理内存又被分为很多节点,每个CPU都有自己的专属的内存节点
节点对应的数据结构为pglist_data
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[GFP_ZONETYPES];
int nr_zones;
struct page *node_mem_map;
struct bootmem_data *bdata;
unsigned long node_start_pfn;
unsigned long node_present_pages; /* total number of physical pages */
unsigned long node_spanned_pages; /* total size of physical page
range, including holes */
int node_id;
struct pglist_data *pgdat_next;
wait_queue_head_t kswapd_wait;
struct task_struct *kswapd;
int kswapd_max_order;
} pg_data_t;
5、内核又把属于某个CPU的内存区域分为两个管理区,ZONE_DMA和ZONE_NORMAL
对应的数据结构为zone_struct
struct zone {
/* Fields commonly accessed by the page allocator */
unsigned long free_pages;
unsigned long pages_min, pages_low, pages_high;
unsigned long lowmem_reserve[MAX_NR_ZONES];
struct per_cpu_pageset pageset[NR_CPUS];
/*
* free areas of different sizes
*/
spinlock_t lock;
struct free_area free_area[MAX_ORDER];
ZONE_PADDING(_pad1_)
/* Fields commonly accessed by the page reclaim scanner */
spinlock_t lru_lock;
struct list_head active_list;
struct list_head inactive_list;
unsigned long nr_scan_active;
unsigned long nr_scan_inactive;
unsigned long nr_active;
unsigned long nr_inactive;
unsigned long pages_scanned; /* since last reclaim */
int all_unreclaimable; /* All pages pinned */
int temp_priority;
int prev_priority;
ZONE_PADDING(_pad2_)
/* Rarely used or read-mostly fields */
wait_queue_head_t * wait_table;
unsigned long wait_table_size;
unsigned long wait_table_bits;
/*
* Discontig memory support fields.
*/
struct pglist_data *zone_pgdat;
struct page *zone_mem_map;
/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
unsigned long zone_start_pfn;
unsigned long spanned_pages; /* total size, including holes */
unsigned long present_pages; /* amount of memory (excluding holes) */
/*
* rarely used fields:
*/
char *name;
};
整个结构大概如下图所示:
接下来研究内核对虚拟内存空间的管理
6、一个进程的(虚拟)用户空间被分成了许多离散的“区间”,内核定义了vm_area_struct结构来抽象这种“区间”
struct vm_area_struct {
struct mm_struct * vm_mm; /* 属于哪个虚拟内存空间,结构mm_struct是对虚拟内存空间的抽象,每个进程拥有一个,也就是每个进程都有自己的用户空间 */
unsigned long vm_start; /* 区间的起始*/
unsigned long vm_end; /* 区间结束后的第一个地址,不包含在区间内 */
/* 一个进程虚拟内存空间的所有区间构成一个链表 */
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
}
/*
* These are the virtual MM functions - opening of an area, closing and
* unmapping it (needed to keep files on disk up-to-date etc), pointer
* to the functions called when a no-page or a wp-page exception occurs.
*/
struct vm_operations_struct {
void (*open)(struct vm_area_struct * area);
void (*close)(struct vm_area_struct * area);
struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int *type);
int (*populate)(struct vm_area_struct * area, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock);
#ifdef CONFIG_NUMA
int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);
struct mempolicy *(*get_policy)(struct vm_area_struct *vma,
unsigned long addr);
#endif
};
7、每个进程都有自己的(虚拟)用户空间,内核中用mm_struct结构来描述
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; /* base of mmap area */
unsigned long free_area_cache; /* first hole */
pgd_t * pgd; /* 指向进程的页全局目录 */
atomic_t mm_users; /* 虽然每个进程拥有一个mm_struct结构,但是同一个mm_struct可能被多个进程共享,比如父子进程 */
atomic_t mm_count; /* mm_users和mm_count记录了该mm_struct结构被几个进程共享 */
int map_count; /* 区间链表中的结点个数 */
struct rw_semaphore mmap_sem; /* 信号量,几个进程可以共享一个mm_struct结构,那么对同一个vm_area_struct的访问必须互斥 */
spinlock_t page_table_lock; /* 跟信号量作用类似 */
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 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 */
};
下图说明了进程的虚拟内存管理和各种数据结构之间的关系
上一篇: Django的部分bug
下一篇: 看到这样考核绩效的团队,别犹豫,赶紧走!