《深入Linux内核架构》第3章 内存管理(2)

目录

3.2 NUMA模型的内存组织

3.2.1 概述

3.2.2 数据结构

3.2.2.1 node

3.2.2.2 zone

水位计算

冷热页

LRU链表

3.2.2.3 page


本节主要讲zone和page的数据结构,zone是重点难点。

3.2 NUMA模型的内存组织

3.2.1 概述

上节已讲

3.2.2 数据结构

3.2.2.1 node

上节已讲

3.2.2.2 zone

每个节点有多个zone。

//会频繁访问该结构,保存在CPU缓存中

struct zone {

        unsigned long         watermark[NR_WMARK];

                多个水位值,内存回收时使用。

        long         lowmem_reserve[MAX_NR_ZONES];

                每个zone预留的内存大小量,留给重要分配。

        int         node;

                所属的内存节点。

        unsigned long         zone_start_pfn;

                该zone起始页帧号。

        struct per_cpu_pageset __percpu         *pageset;

                每个CPU本地的缓存页,可加速页面分配和释放。

        atomic_long_t         managed_pages;

                //被伙伴系统管理的页数。

                类型atomic_long_t:原子变量,防止多处理器同时修改导致数据不一致。

        unsigned long         spanned_pages;

                该zone物理页数,包含空洞。

        unsigned long         present_pages;

                该zone实际可用物理页数。

                managed_pages = present_pages - reserved_pages

        struct free_area         free_area[MAX_ORDER];

                //不同大小的空闲页,被伙伴系统使用。

        

        spinlock_t         lock;

                多CPU访问该结构保护

        int         pages_scanned;

                页面回收时,该zone已扫描的页数量

        bool         contiguous;

                该zone包含的物理页是否连续

        ZONE_PADDING()

                填充,确保后面成员按CPU缓存行对齐。

        struct lruvec         lruvec;

                LRU链表,可跟踪页的使用频率。

                场景:页回收时,回收使用少的页。

                注意:LRU链表不用于页分配,而用于页回收。

        atomic_long_t         vm_stat[NR_VM_ZONE_STAT_ITEMS];

                统计信息,如:

                        NR_FREE_PAGES:该zone中空闲的页数。

                        NR_DIRTIED:该zone中已修改(dirty)页数。

                        NR_DIRTY:该区域中已写回的页数。

}

成员详细解释:

unsigned long watermark[NR_WMARK]; 包含三个水位:

        1. 最低水位WMARK_MIN:

                空闲页数低于该值时,内核强制执行内存回收。

        2. 低水位WMARK_LOW:

                空闲页数低于该值时,kswapd唤醒开始页回收。

                默认值为WMARK_MIN的125%     

        3. 高水位WMARK_HIGH:

                空闲页数高于该值时,为理想状态,不唤醒kswapd       

                默认值为WMARK_MIN的150%     

        注意:比较的单位是页数,不是字节。

struct per_cpu_pageset __percpu         *pageset;

        每个CPU本地的缓存页,用于加速页面分配和释放。

        缓存页按迁移类型进行分组。后面专门讲。

struct free_area         free_area[MAX_ORDER];

        被伙伴系统使用,数组每个元素代表一种特定大小的空闲页链表

        如2K,4K大小页

        struct free_area {

                struct list_head free_list[MIGRATE_TYPES];

                该order大小页,按迁移类型分为多个链表

                unsigned long nr_free;

                该order大小的空闲页的数量

        };

        页按迁移类型分类有:有:

                MIGRATE_UNMOVABLE, 不可迁移

                MIGRATE_RECLAIMABLE, 可回收

                MIGRATE_MOVABLE, 可迁移

                MIGRATE_PCPTYPES, 缓存页

       所以伙伴系统可分配:

                一个2K大小的可迁移页

                一个4K大小的PCP缓存页

                一个8K大小的不可迁移页

flags:

        ZONE_RECLAIM_LOCKED,

                该zone正在内存回收,防止多处理器并发回收内存。

        ZONE_OOM_LOCKED,

                防止并发(Out-of-Memory, OOM)时回收内存。

        ZONE_CONGESTED,

                该zone有大量的脏页面待写回

页回收方法:

        直接回收:即Direct Reclaim,分配内存时,内存不足,内核直接回收。

        kswapd:内核守护线程,定期唤醒并检查水位,决定是否回收。

如何遍历所有node的所有zone?

        for_each_zone(zone) {

        }

水位计算

zone水位单位:页数

全局变量min_free_kbytes:

        表示系统保留的最小空闲物理内存。

        对应参数:/proc/sys/vm/min_free_kbytes

                修改该值,会触发修改所有非高端内存的zone的水位。

min_free_kbytes如何决定非高端内存的zone的水位?

        1. 把字节min_free_kbytes转换成页数pages_min。

        2. 所有node的所有非高端内存zone,一起按各自zone大小比例来分配页数pages_min,分配值就是各自zone的WMARK_MIN水位。

        3. 计算highmem zone的WMARK_MIN:

                固定公式:zone->watermark[WMARK_MIN] = zone->managed_pages / 1024;

                更简单。

        4. 不论是否是非高端内存zone,LOW和HIGH水位计算都一样:

                WMARK_LOW=WMARK_MIN*125%

                WMARK_HIGH=WMARK_MIN*150%

小结:

        min_free_kbytes被所有非高端内存zone分配,得到MIN水位。

        而高端内存的MIN水位和min_free_kbytes无关。

        通过MIN水位得到LOW和HIGH水位。

在系统初始化时,init_per_zone_wmark_min函数会计算出min_free_kbytes。

计算方法:

1. 计算所有node 中非高端内存总和得到lowmem_kbytes。

2. min_free_kbytes = int_sqrt(lowmem_kbytes * 16);         sqrt=平方根

小结:

        高端内存zone的水位计算,和min_free_kbytes无关。

        min_free_kbytes值的计算,也和高端内存zone无关。

总结图:

zone预留页lowmem_reserve = sysctl_lowmem_reserve_ratio[zone] * 该zone页总数

        对应参数:/proc/sys/vm/lowmem_reserve_ratio

冷热页

热页:该空闲页在CPU缓存中。

冷页:该空闲页不在CPU缓存中。

冷热页机制:只缓存单页,即分配阶order为0。

优点:

        提高Cache命中率。

        减小锁竞争。

单页分配需求很多,若每次用伙伴系统申请,zone->lock锁住整个zone。

若多CPU分配同一zone的内存,造成锁竞争。而从自己CPU缓存中分配热页,没有锁竞争。

使用场景:

        不需要给DMA设备分配热页。因为DMA设备不使用CPU及其缓存。

        CPU使用的页,尽量分配热页。

冷热页的数据结构:

        struct per_cpu_pageset

        struct zone {

                struct per_cpu_pageset __percpu; *pageset;

        }

        __percpu:每个CPU都有一个该变量,减少多CPU中锁争用。

        struct per_cpu_pageset pageset {

                struct per_cpu_pages pcp;

        }

        struct per_cpu_pages { 每个CPU都有一个该结构实例

                int         count;         该CPU为该zone缓存的空闲页数

                int         high;         CPU缓存页超出该值的部分,将归还给系统

                int         batch;         CPU缓存页不足时,一次可从zone中分配的页数。

                struct list_head         lists[MIGRATE_PCPTYPES];

                        指向不同迁移类型的链表。

                                包括:可回收页,可移动页,不可移动页。

                };

只申请一个页(order=0)时,可从CPU缓存中per_cpu_pageset中分配。

冷页和热页在同一个链表中:

        分配冷页,从lists链表尾开始分配。

        分配热页,从lists链表头开始分配。

从zone一次性取batch个页,放到各自CPU缓存中。

页释放也先放回CPU缓存,凑够batch个页后再一起还给zone。

LRU链表

struct lruvec {

        struct list_head                         lists[NR_LRU_LISTS]; 五个链表

        struct zone_reclaim_stat         reclaim_stat; 统计

};

五个链表,包括:

        活跃匿名页,活跃文件页,不活跃匿名页,不活跃文件页,不可回收页。

LRU链表:管理页的活跃程度。

        作用:页回收时,选择回收哪些页。

                注:不用于页分配。

小结:一个页可能被挂在三个链表。

        1. LRU链表:用于页回收时选择页。

        2. 伙伴系统链表:按迁移类型组织不同页。

        3. PCP链表:CPU缓存页,按迁移类型组织不同页。

        分配页时会指定分配哪种迁移类型的页。

分配内存时,会区别是否0阶内存分配:

        1. 如果是,先从PCP中分配。若PCP为空,从伙伴系统申请batch个page到PCP。

                即rmqueue_bulk()

        2. 如果不是分配0阶,使用伙伴系统分配。

                即__rmqueue:从伙伴系统删除一个待分配page,需持有zone->lock。

3.2.2.3 page

struct page:

        表示一个4K物理页。一个4G大小的内存需要4G/4K个该结构。

        因为太多实例,内部使用联合体使结构体应尽量小。

struct page { //简化版

        unsigned long                     flags         页标志

        struct address_space         *mapping; 文件映射信息(后续章节讲解)

        pgoff_t                                 index;         mapping中偏移

        atomic_t                                 _mapcount;

                        有多少虚拟地址都指向该页,影响该页能否被回收

        struct list_head                 lru;

                将该页加入到LRU链表中,便于页回收。

        struct kmem_cache *slab_cache;

                用于slab分配器

        void *virtual;

                该页对应的内核虚拟地址

}

成员介绍

flag:

        PG_locked:页面被锁定,防止被换出或回收。

        PG_referenced:页面刚被访问过。用于页面回收时中追踪页面访问频率。

        PG_uptodate:页面内容已和磁盘同步。

        PG_dirty:页面内容已被修改,需写回磁盘。

        PG_swapcache:页面已被换出到交换区。

        PG_slab:页面属于SLAB分配器管理(后续讲解)。

        PG_reserved: 页面被保留,用于特定目的使用。

        PG_error:该页IO操作错误。

        PG_lru:该页是否位于LRU链表上。

        PG_active:该页处于活跃状态,如正在使用的页。

        PG_unevictable:该页被锁住,不可回收。

        PG_highmem:该页属于高端内存zone。

        PG_writeback:该页正写回磁盘。

        PG_reclaim:该页可被回收。

        PG_compound:该页属于一个更大复合页,复合页用于管理大页面huge page。

        PG_head:该页是一个复合页的head页。

        PG_mlocked:该页mlock锁住,不被换出。

等待页面解锁:

        wait_on_page_locked(struct page *page)

等待回写完毕:

        wait_on_page_writeback(struct page *page)

struct file,struct inode,struct address_space关系总结(后续章节详解):

        struct inode:打开文件时创建,代表一个打开文件。

        struct inode:文件系统初始时创建,代表一个磁盘文件。

        struct address_space:文件映射到内存的信息。

1. struct file和struct inode结构体中都有struct address_space指针。

        struct inode->struct address_space *i_mapping->a_ops由文件系统生成inode时赋予。

        如inode->i_mapping->a_ops = &jffs2_file_address_operations;

2. struct file-> struct address_space *f_mapping由struct inode的struct address_space i_mapping赋值

总结图:

相关推荐

  1. 深入理解Linux 内核 内存管理(上)

    2024-03-25 19:16:05       24 阅读
  2. Linux内存管理--系列文章八——内存管理架构

    2024-03-25 19:16:05       19 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-25 19:16:05       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-25 19:16:05       101 阅读
  3. 在Django里面运行非项目文件

    2024-03-25 19:16:05       82 阅读
  4. Python语言-面向对象

    2024-03-25 19:16:05       91 阅读

热门阅读

  1. Docker 容器中运行 JAR 文件的方法

    2024-03-25 19:16:05       46 阅读
  2. 突破编程_C++_查找算法(分块查找)

    2024-03-25 19:16:05       42 阅读
  3. springboot多数据源&动态数据源(主从)

    2024-03-25 19:16:05       37 阅读
  4. 数据结构-双向链表-003

    2024-03-25 19:16:05       30 阅读
  5. 第N5周:调用Gensim库训练Word2Vec模型

    2024-03-25 19:16:05       40 阅读
  6. 接口自动化测试入门基础知识

    2024-03-25 19:16:05       42 阅读
  7. python把图片重命名

    2024-03-25 19:16:05       49 阅读
  8. 委托(非常详细)

    2024-03-25 19:16:05       33 阅读
  9. [leetcode] 994. 腐烂的橘子

    2024-03-25 19:16:05       40 阅读
  10. 爬取MalwareBazaar实现恶意样本数据自由

    2024-03-25 19:16:05       48 阅读
  11. C++多态

    C++多态

    2024-03-25 19:16:05      41 阅读
  12. Python从入门到精通秘籍十九

    2024-03-25 19:16:05       50 阅读
  13. 【软考】蠕虫病毒

    2024-03-25 19:16:05       48 阅读