首先来描述几个名词:

  1. NUMA:Non-Uniform Memory Access即内存非一致性访问.在很多巨星机上内存被分成了多个区域,CPU访问不同的内存区域耗费的时间是不同的.但在同一区域内,CPU访问这些内存的时间是一致的。
  2. UMA:Uniform Memory Access即内存一致性访问,即所CPU访问所有内存的耗时都是一致的。

3. node:NUMA架构下的每一个内存区域称作是node,node用struct pglist_data来描述。所有的node在Linux内核中用一个全局链表pgdat_list关联起来,pg_data_t->pgdat_next指向下一个区域。

4. zone:每一个node下面的内存又被划分为多个小区域,每个区域称作是zone.zone一般分为三个:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM.Linux中使用struct zone来描述一个zone。

5. page:Linux内核对每一个物理内存页框的描述。

node和zone的关系如下图:

Nodes

Linux内核中每一个node由struct pglist_data来进行描述。其定义如下<include/linux/mmzone.h>,下面来介绍下主要成员的作用。

typedef struct pglist_data {struct zone node_zones[MAX_NR_ZONES];struct zonelist node_zonelists[GFP_ZONETYPES];int nr_zones;struct page *node_mem_map;struct bootmem_data *bdata;unsigned long node_start_pfn;unsigned long node_present_pages; /* total number of physical pages */unsigned long node_spanned_pages; /* total size of physical page range, including holes */int node_id;struct pglist_data *pgdat_next;wait_queue_head_t       kswapd_wait;struct task_struct *kswapd;
} pg_data_t;

node_zones:当前node中的zones:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM

node_zonelists:该数组决定了当前node中分配物理内存时,选择zone的顺序.该数组的初始化由mm/page_alloc.c中的build_zonelists()来完成。build_zonelists()由free_area_init_core()调用触发。一般状况下当从ZONE_HIGHMEM中分配内存失败时,会再尝试从ZONE_NORMAL或者ZONE_DMA中分配。

node_mem_map:该指针表示当前node中的物理页框数字中的第一个页框描述符struct page的地址。在内核中有些地方也可以直接使用全局数组mem_map来访问获取node的第一个page地址,实现node_mem_map相同的功能。

node_start_paddr:当前node的物理地址的起始地址。该字段类型是一个unsigned long型,有的时候可能会超长,一个比较好的颁发是在此字段中中记录物理页框号(Page Frame Number,PFN)。PFN = page_phys_addr >> PAGE_SHIFT

node_start_mapnr:当前node中起始物理地址在全局数组mem_map中的偏移。

node_size:当前node中page的总数。

系统中维护的所有node都在一个全局列表pgdat_list中。该list由init_bootmem_core()函数初始化。

Zones

Linux内核中每一个zone由struct zone结构来描述。该结构主要使用来统计page的使用和释放信息。其定义如下,接下来再看下重点成员:

struct zone {spinlock_t     lock;unsigned long      free_pages;unsigned long        pages_min, pages_low, pages_high;unsigned long      protection[MAX_NR_ZONES];ZONE_PADDING(_pad1_)spinlock_t     lru_lock;   struct list_head    active_list;struct list_head    inactive_list;unsigned long     nr_scan_active;unsigned long        nr_scan_inactive;unsigned long      nr_active;unsigned long     nr_inactive;int         all_unreclaimable; /* All pages pinned */unsigned long      pages_scanned;     /* since last reclaim */ZONE_PADDING(_pad2_)int temp_priority;int prev_priority;struct free_area free_area[MAX_ORDER];wait_queue_head_t  * wait_table;unsigned long      wait_table_size;unsigned long       wait_table_bits;ZONE_PADDING(_pad3_)struct per_cpu_pageset  pageset[NR_CPUS];struct pglist_data *zone_pgdat;struct page     *zone_mem_map;/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */unsigned long       zone_start_pfn;char         *name;unsigned long     spanned_pages;  /* total size, including holes */unsigned long      present_pages;  /* amount of memory (excluding holes) */
};

- free_pages:当前zone中free page的总个数

- pages_min,pages_low,page_high:该zone中的水位线,具体在下一小节进行说明。

- free_area:buddy allocator使用的free area bitmaps

- wait_table:等待某个page被释放的进程的等待队列链表。这个队列对于wait_on_page()和unlock_page()函数来说非常重要。

- wait_table_size:wait_table中等待队列的个数。

- wait_table_bits:wait_table_size == (1 << wait_table_bits)

- zone_pgdat:指向当前zone所属的node

- zone_mem_map:当前zone中的第一个page的地址,该page输入全局数组mem_map

- size:当前zone中pages 的个数

Zone初始化

当内核的页表有page_init()建立好之后,就开会初始化zone。每种硬件架构执行zone初始化的时候可能稍有不同,但是最终目标是相同的。每种架构初始化zone的流程中的最终目标是决定传入什么参数值到UMA架构的初始化函数free_area_init()或者NUMA架构的初始化函数free_area_init_node()中。下面来看下参数的含义:

void __init free_area_init_node(int nid, struct pglist_data *pgdat,unsigned long *zones_size, unsigned long node_start_pfn,unsigned long *zholes_size)
{............
}

- nid:要被初始化的zone所属的node的逻辑标识符。

- pgdat:要被初始化成zone的node

- zones_size:包含每个zone的page的个数的数组

- node_start_pfn:当前node的起始PFN

- zholes_size:包含每个zone中memory holes的个数的数组。

无论是free_area_init()还是free_area_init_node()都会调用free_area_init_core()来真正初始化每个struct zone中的成员。在做zone初始化的时候,系统无法知道每个zone中有多少可用的page,这些信息知道boot memory allocator退休之后才会知道。

mem_map初始化

mem_map是在系统启动阶段被创建。在NUMA系统中,全局数组mem_map被当做是一个虚拟的数组其起始位置为PAGE_OFFSET(一般就是指虚拟地址3G的位置)。系统中的每个node初始化的过程中调用free_area_init_node()来为这个虚拟数组分配填充mem_map的一部分内容。而在UMA系统中,因为只有一个node,所以其node为全局变量contig_page_data,而全局变量mem_map就是这个contig_page_data中的ode_mem_map。

对于UMA架构来说,核心函数free_area_core()将为当前初始化的node分配一个本地的lmem_map,lmem_map是从boot memory allocator中使用alloc_bootmem_node()来进行分配。该新分配的lmem_map将会成为全局的mem_map。但NUMA系统中与此有稍微不同。

对于NUMA架构来说,核心函数free_area_core()将从node节点自己的内存中分配lmem_map,而全部变量mem_map不会显式的分配,而是将其设定为PAGE_OFFSET的虚拟地址上,其被当做是一个虚拟的数组。而lmem_map被存储在node中的node_mem_map,pg_data_t->node_mem_map,其在虚拟数组mem_map的某个位置上。当node和zone被初始化完整之后,mem_map将会被当成一个真正的数组了。

Pages

page定义

系统中的每一个物理页框都有一个相应地struct page,其用来持续跟踪物理页框的状态。其定义如下:

struct page {page_flags_t flags;        /* Atomic flags, some possibly updated asynchronously */atomic_t _count;        /* Usage count, see below. */atomic_t _mapcount;        /* Count of ptes mapped in mms,to show when page is mapped & limit reverse map searches. unsigned long private; /* Mapping-private opaque data:usually used for buffer_heads if PagePrivate set;/* used for swp_entry_t if PageSwapCache */struct address_space *mapping;   /* If low bit clear, points to inode address_space, or NULL.*  If page mapped as anonymous memory, low bit is set, and it points to anon_vma object:*  see PAGE_MAPPING_ANON below.*/pgoff_t index;         /* Our offset within mapping. */struct list_head lru;/* Pageout list, eg. active_list protected by zone->lru_lock !*//* On machines where all RAM is mapped into kernel address space,* we can simply calculate the virtual address. On machines with* highmem some memory is mapped into kernel virtual memory* dynamically, so we need a place to store that address.* Note that this field could be 16 bits on x86 ... ;)** Architectures with slow multiplication can define* WANT_PAGE_VIRTUAL in asm/page.h */
#if defined(WANT_PAGE_VIRTUAL)void *virtual;    /* Kernel virtual address (NULL if not kmapped, ie. highmem) */
#endif /* WANT_PAGE_VIRTUAL */
};

- mapping:当文件或者设备有内存映射,那么文件的inode中有一个成员address_space,如果该page属于这个文件那么mapping字段将指向这个address_space。

- lru:根据page的替换策略,active_list或者inactive_list中的pages都可能被换出。该字段是active_list或者inacvtive_list的头。

- virtual:通常来讲只有ZONE_NORMAL中的物理内存才会被内核直接映射到虚拟地址空间,而ZONE_HIGHMEM中的物理内存需要调用kmap()来讲物理地址转成虚拟地址,通常只有固定数目的pages被kmap()转换,如果当前page被kmap映射了,则virtual字段记录它的虚拟地址。

- index:mapping中偏移。

- flags:该字段表示描述page状态的标志位

- count:当前page正在被使用的次数。当count为0时,该page可能被释放。只要大于0,那么当前被一个或者多个进程或者内核在使用中。

| bit名称 | 描述 |

PG_active | 当一个page在avtive_list LRU中时,该bit被置位。从active_list LRU中被删除时,该bit被清零。该bit标记page当前的热度

PG_arch_1 | 该bit位依据不同的架构而不同的page状态位。通常当一个page第一次整体进入page cache的时候该位被清零

PG_checked | 仅仅被Ext2文件系统使用

PG_dirty|该标记位1时表示该page需要被刷新到磁盘上。磁盘文件对应的page被写后不会立即刷新到磁盘,该bit为来保证dirty page在刷新之前不被释放

PG_error|在磁盘IO过程中发生错误,该bit为被置位

PG_fs_1|该bit位保留给文件系统自己用。当前只有NFS用该bit位来表示当前page是否同远端server同步

PG_highmem|高端内存的pages不能被kernel直接映射,因此在mem_init()的时候high memory page该bit位被置位

PG_launder|系统一般会先将该bit置位然后再调用writepage()函数,当系统想换出一个page,在扫描时如果发现一个page的该bit为被置位且PG_locked被置位,则系统会等待IO完成。

PG_locked|当进行磁盘IO时该bit为必须置位,IO完成,该bit为清零

PG_lru|如果一个page在active_list或者inactive_list中时,该bit为置位

PG_referenced|当一个page被映射并且通过映射或者哈希表有访问,则该bit为置位,其主要用作LRU list更新过程中。

PG_slab|如果page正在被slab allocator使用,该bit为置位

PG_skip|某些架构中用该bit为来标记跳过虚拟地址空间中没有对应物理内存的部分

PG_unused|该标记位目前没有使用

PG_uptodate|当一个page从磁盘中读入内容时没有报错,该bit为置位

### 映射 Pages 到 Zones

在kernel2.4中,struct page中有个struct zone的指针,用来表示page输入哪个zone,这是一种相当浪费的行为。因为当有几千个page的时候,这一个小小的指针也会耗费很多的memory。在kernel2.6中,struct page中的zone字段被删除了,取而代之的是使用page->flags中的高ZONE_SHIFT(x86下是8)位来决定page属于哪个zone。

首先创建一个zone_table,mm/page_alloc.c:

/** Used by page_zone() to look up the address of the struct zone whose* id is encoded in the upper bits of page->flags*/
struct zone *zone_table[1 << (ZONES_SHIFT + NODES_SHIFT)];
EXPORT_SYMBOL(zone_table);

然后调用set_page_zone设置zone的ID。

static inline void set_page_zone(struct page *page, unsigned long nodezone_num)
{page->flags &= ~(~0UL << NODEZONE_SHIFT);page->flags |= nodezone_num << NODEZONE_SHIFT;
}

高端内存--High Memory

为什么要支持高端内存?

因为kernel所能使用的虚拟地址空间是有限的(一般只ZONE_NORMAL)。所以kernel需要支持High Memory的概念。在x86 32位系统中,对于高端内存来说有两个阈值:4GB,64GB

4GB:因为物理地址是32位,最大4GB。所以虚拟地址也是32位即虚拟地址空间是0 ~~ 4GB。由于内核只能使用虚拟地址3G ~ 4G这一个G的虚拟地址,而这一个G的虚拟地址大部分被kernel从ZONE_NORMAL和ZONE_DMA直接线性映射所占用,如果系统想要访问更多的物理内存就得从ZONE_HIGMEM中获取物理页框再调用kmap()将其映射到虚拟地址空间,从而让高端物理内存可以。

64GB:Intel发明了PAE技术(Page Address Extension)让32位系统可以使用更多的物理内存。即 $2^{36}$ = 64GB

PAE理论上让处理器可以访问64GB物理内存,但是实际上32位Linux系统中的进程无法访问那么多物理内存因为虚拟地址空间仍然是4GB。

再者来说,PAE不允许内核自己使用如此多的内存。一个struct page占用44字节,而struct page所占用的的虚拟内从空间是在内核可用的虚拟地址空间内。因此描述1GB的物理内存,所需要的struct page就要占用11MB的虚拟地址空间,16GB的物理内存描述就要占用176MB的虚拟地址空间,这会使得内核所使用的虚拟地址空间更加紧张。

因此在32位系统中要访问更多的物理内存,就得内核就得支持高端内存的概念。

所谓高端内存就是指将内核现行映射完成之后还有多余的物理内存,通过动态映射的方式将物理地址转换为虚拟地址,从而使得这部分物理地址可用。这也仅仅是临时可用,一个进程也不能一次malloc 3G以上的内存使用。

高端内存具体如何使用?

例如socket中的buffer就是使用高端内存,比如网络上来了大量数据,内核先调用kmap()将一些page映射成虚拟地址,然后将网络数据copy到物理内存,然后调用kunmap()解除这段虚拟地址到物理地址的映射,此时内核记录的是struct page的地址来描述这些buff在哪里。

当上层应用要读取socket的数据的时候,kernel再次调用kmap()将之前存放网络数据的page再次映射成虚拟地址,让上层应用将这些数据copy出去。

linux 初始化内存管理_Linux内存管理第二章 -- Describing Physical Memory相关推荐

  1. mysql 内存越界_linux内存管理浅析

    [ 地址映射 ](图:左中) linux内核使用页式内存管理,应用程序给出的内存地址是虚拟地址,它需要经过若干级页表一级一级的变换,才变成真正的物理地址. 想一下,地址映射还是一件很恐怖的事情.当访问 ...

  2. 【高软】系统与规划管理师教材笔记-第二章-信息技术知识

    笔记也是自己看教材和在网上搜集的一些重点知识内容整合下来的,希望能对大家有作用吧.软考不易,且行且珍惜,祝愿大家早日领证! 建议大家还是先过一遍教材,有一个初步的理解和印象之后再着重记忆重点知识,有一 ...

  3. Unix/Linux下的Curses库开发指南——第二章 curses库I/O处理

    第2章 curses库I/O处理 2.1 curses库简介 curses库是curses开发包中最重要的一个库,其中提供了一些基本的屏幕操作函数,包括输入/输出,屏幕初始化,屏幕处理中断以及窗口的创 ...

  4. linux内存管理_Linux内存管理(转)

    声明:这篇文章是从网上转来的,本来想注上原作者,发现不少相同文章不同作者都标注原创,这就尴尬了...后来,我看此文章中插图都是英文,用图片搜索到原英文作者及出处,发现CSDN上真是TM乱七八糟,直接拿 ...

  5. 【Linux系列】【基础版】第二章 文件、目录管理

    2. 文件.目录管理2.1 有哪些文件目录2.1.1 /bin 2.1.2 /boot2.1.3 /dev2.1.4 /etc2.1.5 /home2.1.6 /lib /lib642.1.7 /me ...

  6. linux关机_LINUX快速入门第二章:Linux 系统启动过程

    linux启动时我们会看到许多启动信息. Linux系统的启动过程并不是大家想象中的那么复杂,其过程可以分为5个阶段: 内核的引导. 运行 init. 系统初始化. 建立终端 . 用户登录系统. 内核 ...

  7. linux rz 上传文件夹_第二章Linux服务器环境搭建之Tomcat安装

    一.下载tomcat安装包 官网下载地址: http://tomcat.apache.org/download-80.cgi 我下载的版本是tomcat 9,具体下载那个版本需要根据自己的需要进行选择 ...

  8. 操作系统:第二章 进程管理2 - 处理机调度

    本文已收录至 Github(MD-Notes),若博客中有图片打不开,可以来我的 Github 仓库:https://github.com/HanquanHq/MD-Notes,涵盖了互联网大厂面试必 ...

  9. linux delete内存不下降_linux内存分配管理

    linux内存分配管理 一.前言 作为从事与C/C++程序开发人员,我们一直需要很好的管理内存,申请和释放:可能很多只知道使用malloc.new去申请,使用free.delete去释放,但是,去根究 ...

最新文章

  1. ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full comm
  2. Servlet中的监听器
  3. Filter使用详解
  4. qt tableview修改表格内容_如何修改一次代码就可以完成多种类型 cell 的 UITableView 增删修改
  5. Spring MVC学习总结(17)——SpringMVC之接口规范与Controller规范
  6. 在个人Blog页面显示积分与排名
  7. 《编译原理》学习笔记 ·002【第二章:文法和语言(形式语言理论)-1】
  8. 整除光棍 (20 分)
  9. ftp服务器文件上传代码,Java上传文件FTP服务器代码
  10. 续【将数据从MongoDB迁移到mysql】
  11. 嵌入式C语言数据类型
  12. spring集成shiro原理
  13. 如何使用PS修改图片背景
  14. BERT简介及中文分类
  15. vue使用LayIM组件接入第三方通讯平台:融云
  16. 出海竞争加剧,全球头部秀场直播平台LiveMe如何应对新挑战?
  17. Spark高级分析与机器学习笔记
  18. 大脑神经网络具有什么性,神经网络跟大脑的关系
  19. 选择一款对的固定资产管理系统让固定资产管理轻松无比
  20. Python类和对象使用

热门文章

  1. JavaWeb显示器
  2. 还是分了的好——看惠普、赛门铁克拆分
  3. JavaScript模板引擎原理,几行代码的事儿
  4. CodeChef Ada Pawns
  5. 配置百度云盘python客户端bypy上传备份文件
  6. 我的Spring 之旅---Spring实战
  7. PostgreSQL GIN multi-key search 优化
  8. iOS开发 Xcode8中遇到的问题及改动
  9. javascript处理事件的一些兼容写法
  10. 基础知识(9)- Swing用户界面组件