linux 内存管理

linux内存管理(详解)

用户空间与内核空间

人间还是仙界?聊一聊linux系统的用户空间和内核空间
以32 位Linux系统为例,虚拟地址的大小是4GB (0x0000_0000 ~ 0xffff_ffff)。
Linux 用户空间和 内核空间的大小可以通过设置宏 PAGE_OFFSET 来配置,默认PAGE_OFFSET = 0xc000_0000。
当PAGE_OFFSET = 0xc000_0000 时用户空间为 0-3G,内核空间为3-4G。
在这里插入图片描述
Linux为什么要划分用户空间 和 内核空间?
① 处理器模式不同、权限不同
对于x86 体系的CPU,用户空间的代码运行在Ring3 模式,内核空间的代码运行在Ring0 模式;
对于 arm体系的CPU,用户空间的代码运行在usr 模式,内核空间的代码运行在svc 模式;

②安全考虑
内核空间主要用于内核,内核负责管理系统中的各种资源,CPU资源、内存资源和外设资源等等。
Linux是多用户、多进程的系统,用户进程访问这些资源是受限的,空间隔离可以保证系统内核的稳定性,即使一个用户进程奔溃,或是恶意的破坏也不会导致内核奔溃。

③软件设计思想
内核代码偏向于系统管理,用户空间代码偏向于业务逻辑实现,二者分工不同更利于管理。

应用进程地址空间

在Linux 中一个进程的空间大小就是4GB,0-3G 是用户空间,3-4G 是内核空间。
这3G 的虚拟空间就包含代码段、数据段、bss 段、堆栈等等。
在这里插入图片描述

内核使用task_struct 来描述一个用户进程,每个进程都需要分配内存空间资源 用task_struct->mm_struct来描述一个进程的内存资源。
mm_struct->vm_area_struct 用来描述进程中各部分的虚拟地址空间(代码段、数据段等等);mm_struct->pgd 用来描述虚拟地址与物理地址之间的关系。

不同进程之间的内存资源是不可共享的,因为它们都有自己唯一的页表,mm_struct->pgd 表示一个进程的页目录表地址,通过页表就可以找到对应的内存物理地址。所以有时候发现不同进程使用相同的虚拟地址,依然能正常工作,这是因为它们页表中实际映射的物理地址不同。(因为如此不同进程之间通信要使用进程间通信)

其实并不是说一个进程真的拥有 4GB 的内存空间,内核对进程屏蔽了内存空间的管理,表面上给他画出4个G 的大饼,但是只有在进程真正访问一个内存地址的时候,内核才会将这个虚拟地址映射到实际存在的物理地址上。进程用到一点就给他分配一点,所以内核实际上是将物理内存东一块、西一块的分配给不同进程。

内核地址空间

linux内核1G虚拟地址空间的映射规则以及什么是高端内存?
内核中所有的线程,共享1GB 内核空间,因此内核中不同模块之间通信只需要全局变量即可。
用户进程也可以通过调用c库中的函数,执行系统调用产生0x80 号中断来陷入内核空间。

CPU 访问内存的过程

在程序中 CPU 使用的是虚拟地址,需要通过MMU 的转换得到物理地址,在总线上发出物理地址才能访问到内存。
MMU:地址映射单元,完成虚拟地址到物理地址转换的计算。
TLB:页表缓存,是一个高速ram。由于在地址换算过程中MMU 需要经常访问页表,那么如果把页表放在相对低速的内存中,那么转换效率将会变得低下。

在这里插入图片描述

页表映射

linux内核页表映射机制:线性地址如何转为物理地址?
二级页表映射:
以32位 系统为例虚拟地址的大小一共是4GB (0x0000_0000 ~ 0xffff_ffff)。
在程序中想要表达一个虚拟地址就是 用32位长度,比如0xffff_0011,要完成对内存上的数据的访问就要做到 虚拟地址–> 线性地址–> 物理地址的转换,物理地址才是真实存放数据的地址。
在Linux 内核中虚拟地址与线性地址的值是相等的。

下图是一个二级页表的结构图:
线性地址由页目录表索引、页表索引和页表偏移组成,一共32位。
Linux 将一个物理页大小设置为4KB,那么一个4GB 的物理内存就有 4GB / 4KB = 1024* 1024页;
页目录表(Page directory)用来存放页表地址,每一个地址条目的大小4 byte,一个页目录表一个共可以描述1024个页表;
页表(page Table)用来描述一个物理页的基地址,每一个条目需要大小为4 bype,一个页表可以描述1024个页地址;
(1024项页表 x 1024条页地址 x 4KB(页大小) = 4GB )如此刚好能描述4GB 的地址大小。

虚拟地址到物理地址的转换过程(页表映射):
① 从寄存器CR3 得到页目录表基地址。
② 取出线性地址高 10位 作为页目录表索引,通过索引找到页目录表中对应的页表项,得到页表基地址。
③ 取出线性地址中间 10位 作为页表索引,通过页表索引找到页表中描述的物理页项,得到页基地址。
④ 取出线性地址低 12位 作为物理页中的偏移地址,页地址 + 偏移地址 = 物理地址。

每一个进程都有自己唯一的页表,Linux 内核使用mm_struct->pgd 来描述一个进程的页目录表地址,在切换进程时只需要将pgd 存入寄存器CR3 即可完成进程页表的切换。

应用进程或内核线程在使用 malloc 等函数申请内存时,内核分配的是虚拟地址,并没有直接进行映射。当cpu 访问分配的地址空间时,假如没有映射物理地址,会陷入一个缺页异常,进行地址映射。
在这里插入图片描述

四级页表映射:
Linux 64位用户空间与内核空间布局:
在这里插入图片描述

ARMv8 使用的是4级页表映射,4级页表映射参考
4级页表与2级页表的基本原理是一样的,只不过多了几层:全局页目录(pgd)、上级页目录(pud)、中间页目录(pmd)和页表(pte)。
页的大小还是相同的 4KB,但是多级页目录能描述的地址空间也就更广泛了。
一个pgd 一共能描述512项pud,一个pud 一功能描述512项pmd,一个pmd 一共能描述512项pte,一页大小4KB。
(512 x 512 x 512 x4KB = 256TB) 目前64位系统的寻址能力就是256TB。
在这里插入图片描述
使用参考链接中的测试代码可以查看ARMv8 的页表映射情况、和虚拟地址到物理地址的换算:
下图是进程hello 中变量a 地址的虚拟地址->物理地址转换。
在这里插入图片描述

ARMv8 对pgd_t、pud_t、pmd_t、pte_t 等类型定义在:arch\arm64\include\asm\pgtable-types.h (不同架构定义不同)

Linux 内存管理机制

物理内存管理之 node、zone 和page

目前计算机系统有两种体系结构:一致性内存访问 UMA 和非一致性内存访问 NUMA

一致性内存访问 UMA(Uniform Memory Access)也可以称为SMP(Symmetric Multi-Process)对称多处理器。意思是所有的处理器访问内存花费的时间是一样的。也可以理解整个内存只有一个node。
在这里插入图片描述
非一致性内存访问 NUMA(Non-Uniform Memory Access)意思是内存被划分为各个node,访问一个node花费的时间取决于CPU离这个node的距离。每一个cpu内部有一个本地的node,访问本地node时间比访问其他node的速度快。
在这里插入图片描述
Linux 内核用 struct pglist_data描述一个node,用struct page 来描述一个物理内存的最小单位(一页,4KB) 。由于页的描述太小,node的描述太大,于是内核又将一个node中 相同目的的page 用一个struct zone 来描述。

内核源码中 struct pglist_data 定义在include/linux/mmzone.h
比较重要的成员如下:
node 是由多个zone 组成的。

//include/linux/mmzone.h
typedef struct pglist_data {
	struct zone node_zones[MAX_NR_ZONES];		//保存着node 中所有的zone
	......
	unsigned long node_start_pfn;	//该node 所描述的物理内存起始地址
	unsigned long node_present_pages; /* total number of physical pages. 该node 包含物理页的总数*/
	unsigned long node_spanned_pages; /* total size of physical page range, including holes. 所有物理页的大小的总和,包含空洞 */       
} pg_data_t;					     

zone 由多个page 组成。

struct zone {
	unsigned long		zone_start_pfn;		//zone 起始的物理页号

	atomic_long_t		managed_pages;		//zone中 被伙伴系统所管理的page 数量
	unsigned long		spanned_pages;		//一个zone 中page的数量
	unsigned long		present_pages;		//存在的page ,等于spanned_pages - 空洞的page
}

page 是内存管理中,物理内存的最基本单位,即一页,大小为4KB。

//include/linux/mm_types.h
struct page {
	unsigned long flags;		/* Atomic flags, some possibly
					 * updated asynchronously */
	//标志page 的flag,/proc/meminfo 中也是根据此flag 信息来分类

	union {
		struct {	/* Page cache and anonymous pages */
			/**
			 * @lru: Pageout list, eg. active_list protected by
			 * pgdat->lru_lock.  Sometimes used as a generic list
			 * by the page owner.
			 */
			struct list_head lru;	
			//指向一个lru 链表,根据一个page 的使用活跃度来将page 挂入active 链表或inactive  链表,这决定了该page映设的物理内存是否可回收
			/* See page-flags.h for PAGE_MAPPING_FLAGS */
			struct address_space *mapping;
			/*
			mapping:用来指向inode 对应的address_space;
			内核用address_space 来管理一个文件所有的缓存页。
			在Linux系统中打开一个文件的时候,内核会将文件的数据读入到内存中,这些数据在内存上就是存在一个一个page 中。
			最新版本的内核中已经不用address_space 来管理文件page。
			*/
			pgoff_t index;		/* Our offset within mapping. */
			/**
			 * @private: Mapping-private opaque data.
			 * Usually used for buffer_heads if PagePrivate.
			 * Used for swp_entry_t if PageSwapCache.
			 * Indicates order in the buddy system if PageBuddy.
			 */
			unsigned long private;
		}
		......
}

版权声明:本文为weixin_48006170原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。