Linux系统中内核是一个非常重要的一部分,那么Linux内核具体是什么样子呢?下面本篇文章和大家深入讲解一下Linux系统内核机构,有需要的朋友可以参考一下。
5.AMD64地址空间的设置
处理器必须隐藏对未实现的地址空间的访问。 一种做法是禁止使用超出物理地址空间的虚拟地址。
硬件所采用的方案, 符号扩展(sign extension)
注:1)虚拟地址的低47位,即[0,46],可以任意设置。
2)比特位[47,63]的值总是相同的:或者全0,或者全1.此类地址称之为规范的。
3)整个空间划分为3部分,下半部,上半部,二者之间的禁用区。
4)上下两部分共同构成跨越2^48字节的一个地址空间。
5)地址空间的下半部是[0x0,0x0000 7FFF FFFF FFFF]
6 ) 地址空间的上半部是[0xFFF 800 0000 0000,0XFFFF FFFF FFFF FFFF].
7)0x0000 7FFF FFFF FFFF是一个二进制数,低47位都是1,其他位都是0,因此是非可寻址区域之前的最后一个地址。
8)0xFFFF 8000 0000 0000中,比特位[47,63]置位,从而是上半部的第一个有效地址。
注:1)可访问的地址空间的整个下半部用作用户空间,而整个上半部专用于内核。
2)内核地址空间起始于一个起防护作用的空洞,以防止偶然访问地址空间的非规范部分。
3)若出现这种情况,处理器会引发一个一般性保护异常(general protection exception)
4)物理内存页则一致性映射到从PAGE_OFFSET开始的内核空间
5)2^46字节(由MAXMEM指定)专用于物理页帧。
include/asm-x86/pgtable_64.h#define __AC(X,Y) (X##Y) #define _AC(X,Y) __AC(X,Y) /*_用于对给定的常数标记后缀。eg._AC(17,UL)变为(17UL)相当于把常数标记为 unsigned long类型。*/#define __PAGE_OFFSET _AC(0xffff810000000000,UL)#define PAGE_OFFSET __PAGE_OFFSET#define MAXMEM _AC(0X3fffffffffff,UL) 另一个防护性空洞位于一致性映射区和vmalloc内存区之间,后者的范围从VMALLOC_START到VMALLOC_END虚拟内存映射(virtual memory map,VMM)内存区紧接着vmalloc内存区之后。只有内核使用了稀疏内存模型。VMM内存区的页表进行特定的设置,使得物理内存中所有的struct page 实例都映射到没有空洞的内存区中。include/asm-x86/pgtable_64.h#define VMALLOC_START _AC(0Xffffc20000000000,UL)#define VMALLOC_END _AC(0xffffe1ffffffffff,UL) include/asm-x86/page_64.h#define __PHYSICAL_START CONFIG_PHYSICAL_START#define __KERNEL_ALIGN 0x200000#define __START_KERNEL (__START_KERNEL_map + __PHYSICAL_START)#define __START_KERNEL_map _AC(0xffffffff80000000,UL)#define KERNEL_TEXT_SIZE (40*1024*1024)#define KERNEL_TEXT_START _AC(0xffffffff80000000,UL)映射模块的内存区从MODULES_VADDR到MODULES_END#define MODULES_VADDR _AC(0xffffffff88000000,UL)#deifne MODULES_END _AC(0xfffffffffffff00000,UL)#define MODULES_LEN(MODULES_END - MODULES_VADDR) /*该内存区可用的内存数量由MODULES_LEN计算*/
3.4.3启动过程期间的内存管理
1)bootmem分配器用于在启动阶段早期分配内存。
2) 最先适配(first-fit)分配器用于在启动阶段管理内存。
该分配器使用一个位图来管理页,位图比特位的数目与系统中物理内存页的数目相同,比特位为1,表示已用页,比特位为
0,表示空闲页。
3) 最先最佳(first-best)或者最先适配位置
在需要分配内存时,分配器逐位扫描位图,直到找到一个能提供足够连续页的位置,
该过程不高效。 每次分配都从头扫描比特链,因此在内核完全初始化后,不能将该分配器用于内存管理。
1.数据结构
1)内核(为系统中的每个节点都)提供了一个bootmem_data结构的实例来管理一些数据。
2)该结构所需的内存无法动态分配,必须在编译时分配给内核。
3)内存不连续的系统可能需要多个bootmem分配器。 如果物理地址空间中散步着空洞,也可以为每个连续内存区注册一个bootmem分配器。
typedef struct bootmem_data{ unsigned long node_boot_start; /*保存了系统中第一个页的编号,大多数体系结构下都是0*/ unsigned long node_low_pfn; /*可以直接管理的物理地址空间中最后一页的编号,即ZONE_NORMAL的结束页*/ void *node_bootmem_map; /*指向存储分配位图的内存区的指针。*/ unsigned long last_offset; /*如果没有请求分配整个页,则last_offset用作该页内部的偏移量。这使得bootmem分配 器可以分配小于一整页的内存区*/ unsigned long last_pos; /*是上一次分配的页的编号。*/ unsigned long last_success; /*指定位图中上一次成功分配内存的位置,新的分配将由此开始。*/ struct list_head list;}bootmem_data_t;
注册新的自举分配器可使用init_bootmem_core,所有注册的分配器保存在一个链表中,表头是全局变量bdata_list
在UMA系统上,只需一个bootmem_t实例,即contig_bootmem_data.它通过bdata成员与contig_page_data关联起来
mm/page_alloc.cstatic bootmem_data_t contig_bootmem_data;struct pglist_data contig_page_data = {.bdata = &contig_bootmem_data};
***初始化
IA-32使用setup_memory,该函数又调用setup_memory,该函数又调用setup_bootmem_allocator来初始化bootmem分配器。 而AMD64则使用contig_initmem_init
注:1)setup_memory分析检测到的内存区,以找到低端内存区中最大的页帧号。
2)全局变量max_low_pfn保存了可映射的最高页的编号。
3 )setup_bootmem_allocator负责发起所有有必要的步骤,已初始化bootmem分配器。 他首先调用函数init_bootmem,该函数是 init_bootmem_core的一个前端。
4) init_bootmem_core 的目的在于执行bootmem分配器的第一个初始化步骤。
3对内核的接口(Application Programming Interface,API)
***分配内存
1)alloc_bootmem(size)和alloc_bootmem_pages(size)指按指定大小在ZONE_NORMAL内存域分配内存。
————数据是对齐的,这使得内存或者 从可适用于L1高速缓存的理想位置开始,或者从边界开始。
———— _pages是指数据的对齐方式。
2)alloc_bootmem_low和alloc_bootmem_low_pages在ZONE_DMA内存域分配内存。
3)基本上NUMA系统的API相同,但函数名增加了_node后缀,与UMA系统的函数相比,还需要一个额外的参数,指定用于内存分配的节点。
4)这些函数都是alloc_bootmem的前端,后者将实际工作委托给alloc_bootmem_nopanic.
————这些分配器都保存在一个全局链表中,__alloc_bootmem_core会遍历所有的分配器,直至分配成功为止。
5)在NUMA系统上,alloc_bootmem_node则用于实现该API函数,首先工作传递到alloc_bootmem_core,尝试该节点的bootmem分配器进行分配,如果失败,则后退到_alloc_bootmem,并将尝试所有节点。
mm/bootmem.cvoid *_init _alloc_bootmem(unsigned long size,unsigned long align,unsigned long goal) /*size是所需内存区的长度,align表示数据的对齐方式。goal指定了开始搜索适当空闲内存区的起始地址。*/#define alloc_bootmem(x) __alloc_bootmem((x),SMP_CACHE_BYTES, __pa(MAX_DMA_ADDRESS))/*所需分配内存的长度(x)未做任何改变直接传递 给_alloc_bootmem,但内核对齐方式有两个选 项 */#define alloc_bootmem_low(x) __alloc_bootmem((x),SMP_CACHE_BYTES,0) /*SMP_CACHE_BYTES会对齐数据,使之在大多数 体系结构上能够理想的置于L1高速缓存中。*/#define alloc_bootmem_pages(x) __alloc_bootmem((x),PAGE_SIZE,__pa(MAX_DMA_ADDRESS)) /*PAGE_SIZE将数据对齐到页边界*/#define alloc_bootmem_low_pages(x) _alloc_bootmem((x),PAGE_SIZE,0) /*后一种对齐方式是用于分配一个或多个整页*/
注:1)低端DMA内存与普通内存的区别在于其起始地址,搜索适用于DMA的内存从地址0开始,而请求普通内存时则从MAX_DMA_ADDRESS向上(_pa将内存地址转换为页号)
_alloc_bootmem_core函数
(1)从goal开始,扫描位图,查找满足分配请求的空闲内存区。
(2)如果目标页紧接着上一次分配的页,即bootmem_data->last_pos,内核会检查bootmem_data->last_offset,判断所需的内存(包括对齐数据所需的空间)是否能够在上一页分配或从上一页开始分配。
(3)新分配的页在位图对应的比特位设置1,最后一页的数目也保存在bootmem_data->last_pos.如果该页未完全分配,则相应的偏移量保存在bootmem_data->last_offset,否则该值设置为0.
***释放内存
内核提供了free_bootmem函数来释放内存。 它需要两个参数,需要释放的内存区的起始地址和长度。
void free_bootmem(unsigned long addr,unsigned long size);void free_bootmem_node(pg_data_t *pgdat,unsigned long addr,unsigned long size);
1)两个版本都将其工作委托给__free_bootmem_core.因为bootmem分配器没有保存有关页划分的任何信息。
2)内核使用__free_bootmem_core首先计算完全包含在该内存区中,将被释放的页。
3)位图中对应的项设置为0,完成的页的释放。
4.停用bootmem分配器
(乌马) free_all_bootmem free_all_bootmem_node
1)扫描bootmem分配器的页位图,释放每个未使用的页。
2)到伙伴系统的接口是__free_pages_bootmem函数,该函数对每个空闲页调用。
3)该函数依赖于标准函数__free_page
4)它使得这些页并入伙伴系统的数据结构,在其中作为空闲页管理,可用于分配。
5)在页位图已经完全扫描之后,它占据的内存空间也必须释放,此后,只有伙伴系统可用于内存分配。
5释放初始化数据
1)内核提供了两个属性(init和initcall)用于标记初始化函数和数据,这些必须置于函数或数据的声明之前。
3.5物理内存的管理
3.5.1伙伴系统的结构
系统内存中的每个物理内存页(页帧),都对应于一个struct page实例。 每个内存域都关联了一个struct zone实例。 其中保存了用于管理伙伴数据的主要数组。
struct zone{... /* *不同长度的空闲区域 */ struct free_area free_area[MAX_ORDER]; /*free_area[]数组中的各个元素的索引也解释为阶,用于指定对应链表中的连续内存区 包含多少个页帧。eg.第0个链表包含的内存区为单页(2^0=1)...第3个管理的内存区 为4页,以此类推*/...};free_area是一个辅助数据结构定义:struct free_area{ struct list_head free_list[MIGRATE_TYPES]; /*用于连续空闲页的链表,页表包含大小相同的连续内存区*/ unsigned long nr_free; /*指定了当前内存区中空闲页块的数目*/};
内存块的长度是2^order,其中order的范围从0到MAX_ORDER.
#ifndef CONFIG_FORCE_MAX_ZONEORDER#define MAX_ORDER 11 /*该常数通常是11,即一次分配可以请求的页数最大是2^11=2048*/#else#define MAX_ORDER CONFIG_FORCE_MAX_ZONEORDER#endif#define MAX_ORDER_NR_PAGES(1 内存区中第一页内的链表元素,可用于将内存区维持在链表中。
注:1)伙伴不必是彼此连接的。
2)若一个内存区在分配期间分解为两半,内核会自动将未用的一半加入到对应的链表中。 若某个时刻,由于内存的释放,两个内存区都处于空闲状态,可通过其地址判断其是否为伙伴。
3)基于伙伴系统的内存管理专注于某个节点的某个内存域。 DMA或者高端内存域。 但所有的内存域和节点的伙伴系统都通过备用分配列表连接起来。
4)在首选的内存域或节点无法满足内存分配请求时,首先尝试同一个节点的另一个内存域,接下来再尝试另一个节点,直至满足请求。
3.5.2避免碎片
反碎片(anti-fragmentation):试图从一开始尽可能防止碎片。
工作原理:
1)不可移动页。 在内存中有固定的位置,不能移动到其他的地方。
2)可回收页。 不能直接移动,但可以删除,其内容可以从某些源重新生成。
3)可移动页。 可以随意的移动。 属于用户空间应用程序的页属于该类别。 它们是通过页表映射的,如果他们复制到新的位置,页表项可以相应的更新,应用程序不会注意到任何事。
以上就是
为各位朋友分享的 相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多 等着你!