内存管理

标签: 内存管理

一、程序的编译链接过程

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

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


版权声明:本文为weixin_37853880原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_37853880/article/details/80501618

智能推荐

【Sublime】使用 Sublime 工具时运行python文件

使用 Sublime 工具时报Decode error - output not utf-8解决办法   在菜单中tools中第四项编译系统 内最后一项增添新的编译系统 自动新建 Python.sublime-build文件,并添加"encoding":"cp936"这一行,保存即可 使用python2 则注释encoding改为utf-8 ctr...

java乐观锁和悲观锁最底层的实现

1. CAS实现的乐观锁 CAS(Compare And Swap 比较并且替换)是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的,也可以理解为自旋锁 JUC是指import java.util.concurrent下面的包, 比如:import java.util.concurrent.atomic.AtomicInteger; 最终实现是汇编指令:lock...

Python 中各种imread函数的区别与联系

  原博客:https://blog.csdn.net/renelian1572/article/details/78761278 最近一直在用python做图像处理相关的东西,被各种imread函数搞得很头疼,因此今天决定将这些imread总结一下,以免以后因此犯些愚蠢的错误。如果你正好也对此感到困惑可以看下这篇总结。当然,要了解具体的细节,还是应该 read the fuc...

用栈判断一个字符串是否平衡

注: (1)本文定义:左符号:‘(’、‘[’、‘{’…… 右符号:‘)’、‘]’、‘}’……. (2)所谓的字符串的符号平衡,是指字符串中的左符号与右符号对应且相等,如字符串中的如‘(&r...

JAVA环境变量配置

位置 计算机->属性->高级系统设置->环境变量 方式一 用户变量新建path 系统变量新建classpath 方式二 系统变量 新建JAVA_HOME,值为JDK路径 编辑path,前加 方式三 用户变量新建JAVA_HOME 此路径含lib、bin、jre等文件夹。后运行tomcat,eclipse等需此变量,故最好设。 用户变量编辑Path,前加 系统可在任何路径识别jav...

猜你喜欢

常用的伪类选择器

CSS选择器众多 CSS选择器及权重计算 最常用的莫过于类选择器,其它的相对用的就不会那么多了,当然属性选择器和为类选择器用的也会比较多,这里我们就常用的伪类选择器来讲一讲。 什么是伪类选择器? CSS伪类是用来添加一些选择器的特殊效果。 常用的为类选择器 状态伪类 我们中最常见的为类选择器就是a标签(链接)上的为类选择器。 当我们使用它们的时候,需要遵循一定的顺序问题,否则将可能出现bug 注意...

ButterKnife的使用介绍及原理探究(六)

前面分析了ButterKnife的源码,了解其实现原理,那么就将原理运用于实践吧。 github地址:       点击打开链接 一、自定义注解 这里为了便于理解,只提供BindView注解。 二、添加注解处理器 添加ViewInjectProcessor注解处理器,看代码, 这里分别实现了init、getSupportedAnnotationTypes、g...

1.写一个程序,提示输入两个字符串,然后进行比较,输出较小的字符串。考试复习题库1|要求:只能使用单字符比较操作。

1.写一个程序,提示输入两个字符串,然后进行比较,输出较小的字符串。 要求只能使用单字符比较操作。 参考代码: 实验结果截图:...

小demo:slideDown()实现二级菜单栏下拉效果

效果如下,鼠标经过显示隐藏的二级菜单栏 但是这样的时候会存在一个问题,就是鼠标快速不停移入移出会导致二级菜单栏闪屏现象,一般需要使用stop()来清除事件  ...

基于docker环境的mysql主从复制

1、安装docker 可以参考之前的博客,之前写过了~ 2、拉取mysql镜像 3、创建mysql01和mysql02实例 主: 从: 4、进入容器修改配置 1)修改主数据库配置 进入主数据库容器 切换到 etc/mysql/目录下 查看可以看到my.cnf文件,使用vim编辑器打开,但是需要提前安装 安装vim命令: 安装成功后,修改my.cnf文件 新增配置后的my.cnf: binlog 日...