欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

内存管理

程序员文章站 2022-04-26 12:10:06
...

一、程序的编译链接过程

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 */
};

下图说明了进程的虚拟内存管理和各种数据结构之间的关系

内存管理

相关标签: 内存管理