Linux内核MMU机制实现讲解

Posted 一口Linux

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核MMU机制实现讲解相关的知识,希望对你有一定的参考价值。

目录;

1、ARM32/64页表映射

2、内核内存布局组织

3、分配物理页面及释放

简介

内存管理是相当复杂的体系结构,它所涉及到三个层次:用户空间、内核空间和硬件空间。

用户空间层大家可以理解为Linux内核内存管理为用户空间暴露的系统调用接口,比如brk、mmap等系统调用。基本是都是以libc库直接封闭成我们常用的C语言系统标准函数,例如mmap()和malloc等。

内核空间层包含的模块非常丰富,用户空间和内核空间的接口是系统调用,因此内核空间首先需要处理这些内存管理相关的系统调用,比如sys_brk、sys_mmap、sys_madvise等等。接下来就包括VMA管理、缺页中断管理、匿名页面、page cache、页面回收、反映映射、slab分配器、页表管理等模块。

具体通过如下图所示将内存管理框架描述出来。后台私信:资料:免费领取(下图学习资料)

Linux内核MMU机制实现讲解

 

Linux内核MMU机制实现讲解

 

具体硬件层面,主要包括处理器的MMU、TLB及cache部件,以及板载的物理内存,比如LPDDR或者DDR。

内存大小

在ARM Linux中,各种设备的相关属性描述都采用DTS方式来展现,DTS是device tree source的简称。该DTS文件定义内存的起始地址为0x60000000,大小为0x40000000,即1GB大小内存空间。

Linux内核MMU机制实现讲解

 

内核在启动过程中,需要解析这些TDS文件,实现代码在early_init_dt_scan_memory()函数当中。具体代码调用关系为:

start_kernel(void)-->setup_arch(&command_line)-->setup_machine_fdt-->early_init_dt_scan_nodes->early_init_dt_scan_memory()。

Linux内核MMU机制实现讲解

 

Linux内核MMU机制实现讲解

 

Linux内核MMU机制实现讲解

 

解析“memory”描述的信息从而得到内存的base_address和 size信息,最后内存块信息通过


early_init_dt_add_memory_arch(base, size)-->memblock_add()函数添加到memblock子系统当中。

 

物理内存映射

在内核使用内存前,需要初始化内核的页表,初始化页表主要在map_lowmem()函数当中,在映射页表之前,需要把页表的页表项清零,主要在prepare_page_table函数中实现。

<span style="color:#222222"><code>kernel 通过paging_init来映射物理内存
<span style="color:#114ba6">void</span> __<span style="color:#8a7304">init <span style="color:#a82e2e">paging_init</span>(<span style="color:#114ba6">void</span>)
</span>{</code></span>

为pgd申请内存并赋值,这时候还没有buddy system,因此在早期都是通过memblock来申请内存的

<span style="color:#222222"><code>	<span style="color:#114ba6">phys_addr_t</span> pgd_phys = early_pgtable_alloc();
	<span style="color:#114ba6">pgd_t</span> *pgdp = pgd_set_fixmap(pgd_phys);</code></span>

可以看到kernel本身占用的内存是单独映射的,这两个函数我们后面详细分析

<span style="color:#222222"><code>	map_kernel(pgdp);
	map_mem(pgdp);</code></span>

 

在head.s中有申请一段memory来临时映射物理内存swapper_pg_dir,这里我们可以复用

这段内存,这样我们就可以把通过memblock申请的pgd_phys 释放掉

<span style="color:#222222"><code>	cpu_replace_ttbr1(__va(pgd_phys));
	memcpy(swapper_pg_dir, pgdp, PGD_SIZE);
	cpu_replace_ttbr1(lm_alias(swapper_pg_dir));
 
	pgd_clear_fixmap();</code></span>

释放pgd_phys 占用的内存

<span style="color:#222222"><code>	memblock_free(pgd_phys, PAGE_SIZE);
 
	<span style="color:#999999">/*
	 * We only reuse the PGD from the swapper_pg_dir, not the pud + pmd
	 * allocated with it.
	 */</span></code></span>

我们只是复用swapper_pg_dir 中pgd的memory,因此治理释放到pud和pmd 占用的内存

<span style="color:#222222"><code>	memblock_free(__pa_symbol(swapper_pg_dir) + PAGE_SIZE,
		      __pa_symbol(swapper_pg_end) - __pa_symbol(swapper_pg_dir)
		      - PAGE_SIZE);
}</code></span>

首先看看map_kernel

<span style="color:#222222"><code><span style="color:#114ba6">static</span> <span style="color:#114ba6">void</span> __<span style="color:#8a7304">init <span style="color:#a82e2e">map_kernel</span>(<span style="color:#114ba6">pgd_t</span> *pgdp)
</span>{
	<span style="color:#114ba6">static</span> <span style="color:#114ba6">struct</span> <span style="color:#a82e2e">vm_struct</span> <span style="color:#a82e2e">vmlinux_text</span>, <span style="color:#a82e2e">vmlinux_rodata</span>, <span style="color:#a82e2e">vmlinux_inittext</span>,
				<span style="color:#a82e2e">vmlinux_initdata</span>, <span style="color:#a82e2e">vmlinux_data</span>;
 
	<span style="color:#114ba6">pgprot_t</span> text_prot = rodata_enabled ? PAGE_KERNEL_ROX : PAGE_KERNEL_EXEC;</code></span>

 

从下面这段可以看出kernel 本身占用的memory可以分为下面这5段

<span style="color:#222222"><code>	<span style="color:#00753b">map_kernel_segment(pgdp,</span> <span style="color:#00753b">_text,</span> <span style="color:#00753b">_etext,</span> <span style="color:#00753b">text_prot,</span> <span style="color:#00753b">&vmlinux_text,</span> <span style="color:#a82e2e">0</span><span style="color:#00753b">,</span>
			   <span style="color:#00753b">VM_NO_GUARD);</span>
	<span style="color:#00753b">map_kernel_segment(pgdp,</span> <span style="color:#00753b">__start_rodata,</span> <span style="color:#00753b">__inittext_begin,</span> <span style="color:#00753b">PAGE_KERNEL,</span>
			   <span style="color:#00753b">&vmlinux_rodata,</span> <span style="color:#00753b">NO_CONT_MAPPINGS,</span> <span style="color:#00753b">VM_NO_GUARD);</span>
	<span style="color:#00753b">map_kernel_segment(pgdp,</span> <span style="color:#00753b">__inittext_begin,</span> <span style="color:#00753b">__inittext_end,</span> <span style="color:#00753b">text_prot,</span>
			   <span style="color:#00753b">&vmlinux_inittext,</span> <span style="color:#a82e2e">0</span><span style="color:#00753b">,</span> <span style="color:#00753b">VM_NO_GUARD);</span>
	<span style="color:#00753b">map_kernel_segment(pgdp,</span> <span style="color:#00753b">__initdata_begin,</span> <span style="color:#00753b">__initdata_end,</span> <span style="color:#00753b">PAGE_KERNEL,</span>
			   <span style="color:#00753b">&vmlinux_initdata,</span> <span style="color:#a82e2e">0</span><span style="color:#00753b">,</span> <span style="color:#00753b">VM_NO_GUARD);</span>
	<span style="color:#00753b">map_kernel_segment(pgdp,</span> <span style="color:#00753b">_data,</span> <span style="color:#00753b">_end,</span> <span style="color:#00753b">PAGE_KERNEL,</span> <span style="color:#00753b">&vmlinux_data,</span> <span style="color:#a82e2e">0</span><span style="color:#00753b">,</span> <span style="color:#a82e2e">0</span><span style="color:#00753b">);</span>
 
<span style="color:#999999">#最终map_kernel_segment->__create_pgd_mapping->pud->pmd->pte等来映射</span>
<span style="color:#00753b">}</span></code></span>

memblock 中的memory映射如下:

<span style="color:#222222"><code><span style="color:#114ba6">static</span> <span style="color:#114ba6">void</span> __<span style="color:#8a7304">init <span style="color:#a82e2e">map_mem</span>(<span style="color:#114ba6">pgd_t</span> *pgdp)
</span>{
	<span style="color:#114ba6">phys_addr_t</span> kernel_start = __pa_symbol(_text);
	<span style="color:#114ba6">phys_addr_t</span> kernel_end = __pa_symbol(__init_begin);
	<span style="color:#114ba6">struct</span> <span style="color:#a82e2e">memblock_region</span> *<span style="color:#a82e2e">reg</span>;
	<span style="color:#114ba6">int</span> flags = <span style="color:#a82e2e">0</span>;</code></span>

是否开debug选项

<span style="color:#222222"><code>	<span style="color:#114ba6">if</span> (debug_pagealloc_enabled())
		flags = NO_BLOCK_MAPPINGS | NO_CONT_MAPPINGS;
 
	<span style="color:#999999">/*
	 * Take care not to create a writable alias for the
	 * read-only text and rodata sections of the kernel image.
	 * So temporarily mark them as NOMAP to skip mappings in
	 * the following for-loop
	 */</span></code></span>

由于kernel 占用的memory页包含在memblock中,且我们在前面已经映射kernel了,所以

<span style="color:#222222"><code>	#这里标记kernel占用的memory不用在重复映射了
	memblock_mark_nomap(kernel_start, kernel_end - kernel_start);
<span style="color:#00753b">#ifdef CONFIG_KEXEC_CORE</span>
	<span style="color:#114ba6">if</span> (crashk_res.end)
		memblock_mark_nomap(crashk_res.start,
				    resource_size(&crashk_res));
<span style="color:#00753b">#endif</span>
 
	<span style="color:#999999">/* map all the memory banks */</span>
	#开始映射memblock中的<span style="color:#8a7304">memory
	<span style="color:#a82e2e">for_each_memblock</span>(memory, reg) </span>{
		<span style="color:#114ba6">phys_addr_t</span> start = reg->base;
		<span style="color:#114ba6">phys_addr_t</span> end = start + reg->size;</code></span>

起始地址大于结束地址的话,肯定更有问题,退出

<span style="color:#222222"><code>		<span style="color:#114ba6">if</span> (start >= end)
			<span style="color:#114ba6">break</span>;
		#如果memblock包含MEMBLOCK_NOMAP,则不用映射,例如前面提到的kernel的映射
		<span style="color:#114ba6">if</span> (memblock_is_nomap(reg))
			<span style="color:#114ba6">continue</span>;</code></span>

开始映射memblock中的memory

<span style="color:#222222"><code>		__map_memblock(pgdp, start, end, PAGE_KERNEL, flags);
	}
 
	
	__map_memblock(pgdp, kernel_start, kernel_end,
		       PAGE_KERNEL, NO_CONT_MAPPINGS);</code></span>

清除kernel memory段的MEMBLOCK_NOMAP

<span style="color:#222222"><code>	memblock_clear_nomap(kernel_start, kernel_end - kernel_start);
 
 
}</code></span>

 

分配物理页面及释放

伙伴系统维护空闲页面所组成的块,每一块都是2地方幂个页面。每个管理区struct zone中struct free_area free_area[MAX_ORDER]数组就用于管理伙伴算法:

#define MAX_ORDERCONFIG_FORCE_MAX_ZONEORDER =11

struct free_area {

struct list_head free_list[MIGRATE_TYPES]; //空闲链表

unsigned long nr_free; //空闲链表个数

};

MAX_ORDER = 11意味着最大支持的连续物理快2^11* 4K = 8M。其示意图如下:

 

Linux内核MMU机制实现讲解

以上是关于Linux内核MMU机制实现讲解的主要内容,如果未能解决你的问题,请参考以下文章

Binder 机制进程通信 | 用户空间与内核空间 | MMU 与虚拟内存地址

MMU

内存管理

Linux 内核 内存管理内存管理架构 ① ( 内存管理架构组成 | 用户空间 | 内核空间 | MMU 硬件 | Linux 内核架构层次 | Linux 系统调用接口 )

RK3399平台开发系列讲解(同步与互斥篇)Linux内核锁机制详解 - 视频介绍

RK3399平台开发系列讲解(同步与互斥篇)Linux内核锁机制详解 - 视频介绍