良许Linux教程网 干货合集 Linux内核架构详解(三)

Linux内核架构详解(三)

Linux系统中内核是一个非常重要的一部分,那么Linux内核具体是什么样子呢?下面本篇文章和大家深入讲解一下Linux系统内核机构,有需要的朋友可以参考一下。

Linux内核架构详解(三)

5.AMD64地址空间的设置

‎ 处理器必须隐藏对未实现的地址空间的访问。 一种做法是禁止使用超出物理地址空间的虚拟地址。 ‎

硬件所采用的方案, 符号扩展(sign extension)

img

注: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]置位,从而是上半部的第一个有效地址。

img

注: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 ‎

img

注:1)setup_memory分析检测到的内存区,以找到低端内存区中最大的页帧号。

   2)全局变量max_low_pfn保存了可映射的最高页的编号。

‎ 3 )setup_bootmem_allocator负责发起所有有必要的步骤,已初始化bootmem分配器。 他首先调用函数init_bootmem,该函数是 ‎‎init_bootmem_core‎‎的一个前端。 ‎

‎ 4) ‎‎init_bootmem_core‎‎ ‎‎的目的在于执行bootmem分配器的第一个初始化步骤。 ‎

img

imgimg

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

内存区中第一页内的链表元素,可用于将内存区维持在链表中。

img

注:1)伙伴不必是彼此连接的。

‎ 2)若一个内存区在分配期间分解为两半,内核会自动将未用的一半加入到对应的链表中。 若某个时刻,由于内存的释放,两个内存区都处于空闲状态,可通过其地址判断其是否为伙伴。 ‎

‎ 3)基于伙伴系统的内存管理专注于某个节点的某个内存域。 DMA或者高端内存域。 但所有的内存域和节点的伙伴系统都通过备用分配列表连接起来。 ‎

   4)在首选的内存域或节点无法满足内存分配请求时,首先尝试同一个节点的另一个内存域,接下来再尝试另一个节点,直至满足请求。

img

3.5.2避免碎片

反碎片(anti-fragmentation):试图从一开始尽可能防止碎片。

工作原理:

‎ 1)不可移动页。 在内存中有固定的位置,不能移动到其他的地方。 ‎

‎ 2)可回收页。 不能直接移动,但可以删除,其内容可以从某些源重新生成。 ‎

‎ 3)可移动页。 可以随意的移动。 属于用户空间应用程序的页属于该类别。 它们是通过页表映射的,如果他们复制到新的位置,页表项可以相应的更新,应用程序不会注意到任何事。 ‎

至此关于Linux系统内核讲解的全部教程分享结束,大家有任何问题都可以在评论区留言啊。

以上就是良许教程网为各位朋友分享的Linux系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你!

137e00002230ad9f26e78-265x300

本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部