linux物理内存 top

2016-10-06 10:14:45 qq_26626709 阅读数 1383
  • Linux进程管理

    掌握Linux系统常用基命令 掌握文件操作、打包与解压 掌握vi编辑器的使用 掌握用户与权限管理 掌握进程管理和软件安装

    405人学习 CSDN就业班
    免费试看

linux使用伙伴系统来管理物理内存页。

一、伙伴系统原理

1. 伙伴关系

定义:由一个母实体分成的两个各方面属性一致的两个子实体,这两个子实体就处于伙伴关系。在操作系统分配内存的过程中,一个内存块常常被分成两个大小相等的内存块,这两个大小相等的内存块就处于伙伴关系。它满足 3 个条件 :
  •  两个块具有相同大小记为 2^K
  •  它们的物理地址是连续的
  •  从同一个大块中拆分出来

2. 伙伴算法的实现原理

为了便于页面的维护,将多个页面组成内存块,每个内存块都有 2 的方幂个页,方幂的指数被称为阶 order。order相同的内存块被组织到一个空闲链表中。伙伴系统基于2的方幂来申请释放内存页。
当申请内存页时,伙伴系统首先检查与申请大小相同的内存块链表中,检看是否有空闲页,如果有就将其分配出去,并将其从链表中删除,否则就检查上一级,即大小为申请大小的2倍的内存块空闲链表,如果该链表有空闲内存,就将其分配出去,同时将剩余的一部分(即未分配出去的一半)加入到下一级空闲链表中;如果这一级仍没有空闲内存;就检查它的上一级,依次类推,直到分配成功或者彻底失败,在成功时还要按照伙伴系统的要求,将未分配的内存块进行划分并加入到相应的空闲内存块链表
在释放内存页时,会检查其伙伴是否也是空闲的,如果是就将它和它的伙伴合并为更大的空闲内存块,该检查会递归进行,直到发现伙伴正在被使用或者已经合并成了最大的内存块。

二、linux中的伙伴系统相关的结构

系统中的每个物理内存页(页帧)都对应一个struct page数据结构,每个节点都包含了多个zone,每个zone都有struct zone表示,其中保存了用于伙伴系统的数据结构。zone中的

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. struct free_area      free_area[MAX_ORDER];  
用于管理该zone的伙伴系统信息。伙伴系统将基于这些信息管理该zone的物理内存。该数组中每个数组项用于管理一个空闲内存页块链表,同一个链表中的内存页块的大小相同,并且大小为2的数组下标次方页。MAX_ORDER定义了支持的最大的内存页块大小。
struct free_area的定义如下
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. struct free_area {  
  2.        structlist_head       free_list[MIGRATE_TYPES];  
  3.        unsignedlong        nr_free;  
  4. };  

  • nr_free:其中nr_free表示内存页块的数目,对于0阶的表示以1页为单位计算,对于1阶的以2页为单位计算,n阶的以2的n次方为单位计算。
  • free_list:用于将具有该大小的内存页块连接起来。由于内存页块表示的是连续的物理页,因而对于加入到链表中的每个内存页块来说,只需要将内存页块中的第一个页加入该链表即可。因此这些链表连接的是每个内存页块中第一个内存页,使用了struct page中的struct list_head成员lru。free_list数组元素的每一个对应一种属性的类型,可用于不同的目地,但是它们的大小和组织方式相同。
因此在伙伴系统看来,一个zone中的内存组织方式如下图所示:


基于伙伴系统的内存管理方式专注于内存节点的某个内存域的管理,但是系统中的所有zone都会通过备用列表连接起来。伙伴系统和内存域/节点的关系如下图所示:


系统中伙伴系统的当前信息可以通过/proc/buddyinfo查看:


这是我的PC上的信息,这些信息描述了每个zone中对应于每个阶的空闲内存页块的数目,从左到右阶数依次升高。

三、避免碎片

1.碎片概念

伙伴系统也存在一些问题,在系统长时间运行后,物理内存会出现很多碎片,如图所示:
 
这是虽然可用内存页还有很多,但是最大的连续物理内存也只有一页,这对于用户程序不成问题,因为用户程序通过页表映射,应用程序看到的总是连续的虚拟内存。但是对于内核来说就不行了,因为内核有时候需要使用连续的物理内存。

2.linux解决方案

碎片问题也存在于文件系统,文件系统中的碎片可以通过工具来解决,即分析文件系统,然后重新组织文件的位置,但是这种方不适用于内核,因为有些物理页时不能随意移动。内核采用的方法是反碎片(anti-fragmentation)。为此内核根据页的可移动性将其划分为3种不同的类型:
  • 不可移动的页:在内存中有固定位置,不能移动。分配给核心内核的页大多是此种类型
  • 可回收的页:不能移动,但是可以删除,其内容可以从某些源重新生成。
  • 可移动的页:可以随意移动。属于用户进程的页属于这种类型,因为它们是通过页表映射的,因而在移动后只需要更新用户进程页表即可。
页的可移动性取决于它属于上述三类中的哪一类,内核将页面按照不同的可移动性进行分组,通过这种技术,虽然在不可移动页中仍可能出现碎片,但是由于具有不同可移动性的页不会进入同一个组,因而其它两个类型的内存块就可以获得较好的“对抗碎片”的特性。
需要注意的是按照可移动性对内存页进行分组时在运行中进行的,而不是在一开始就设置好的。

1.数据结构

内核定义了MIGRATE_TYPES中迁移类型,其定义如下: 
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. enum {  
  2.     MIGRATE_UNMOVABLE,  
  3.     MIGRATE_RECLAIMABLE,  
  4.     MIGRATE_MOVABLE,  
  5.     MIGRATE_PCPTYPES,   /* the number of types on the pcp lists */  
  6.     MIGRATE_RESERVE = MIGRATE_PCPTYPES,  
  7.     MIGRATE_ISOLATE,    /* can't allocate from here */  
  8.     MIGRATE_TYPES  
  9. };  
其中前三种分别对应于三种可移动性,其它几种的含义:
  • MIGRATE_PCPTYPES:是per_cpu_pageset,即用来表示每CPU页框高速缓存的数据结构中的链表的迁移类型数目
  • MIGRATE_RESERVE:是在前三种的列表中都没用可满足分配的内存块时,就可以从MIGRATE_RESERVE分配
  • MIGRATE_ISOLATE:用于跨越NUMA节点移动物理内存页,在大型系统上,它有益于将物理内存页移动到接近于是用该页最频繁地CPU
每种类型都对应free_list中的一个数组项。
类似于从zone中的分配,如果无法从指定的迁移类型分配到页,则会按照fallbacks指定的次序从备用迁移类型中尝试分配,它定义在page_alloc.c中。
虽然该特性总是编译进去的,但是该特性只有在系统中有足够的内存可以分配到每种迁移类型对应的链表时才有意义,也就是说每个可以迁移性链表都要有“适量”的内存,内核需要对“适量”的判断是基于两个宏的:
  • pageblock_order:内核认为够大的一个分配的阶。
  • pageblock_nr_pages:内核认为启用该特性时每个迁移链表需要具有的最少的内存页数。它的定义是基于pageblock_order的。
基于这个“适量”的概念内核会在build_all_zonelists中判断是否要启用该特性。page_group_by_mobility_disabled表示是否启用了该特性。
内核定义了两个标志:__GFP_MOVABLE和 __GFP_RECLAIMABLE分别用来表示可移动迁移类型和可回收迁移类型,如果没有设置这两个标志,则表示是不可移动的。如果页面迁移特性被禁止了,则所有的页都是不可移动页。
struct zone中包含了一个字段pageblock_flags,它用于跟踪包含pageblock_nr_pages个页的内存区的属性。在初始化期间,内核自动保证对每个迁移类型,在pageblock_flags中都分配了足够存储NR_PAGEBLOCK_BITS个比特的空间。
set_pageblock_migratetype用于设置一个以指定的页为起始地址的内存区的迁移类型。
页的迁移类型是预先分配好的,对应的比特位总是可用,在页释放时,必须将其返还给正确的链表。get_pageblock_migratetype可用于从struct page中获取页的迁移类型。
通过/proc/pagetypeinfo可以获取系统当前的信息。
在内存初始化期间memmap_init_zone会将所有的内存页都初始化为可移动的。该函数在paging_init中会最终被调到(会经过一些中间函数,其中就有free_area_init_node)。

3.虚拟可移动内存

内核还提供了一种机制来解决碎片问题,即使用虚拟内存域ZONE_MOVABLE。其思想是:可用内存划分为两个部分,一部分用于可移动分配,一部分用于不可移动分配。这样就防止了不可移动页向可移动内存区域引入碎片。
该机制需要管理员来配置两部分内存的大小。
kernel参数kernelcore用于指定用于不可移动分配的内存数量,如果指定了该参数,其值会保存在required_kernelcore会基于它来计算。
kernel参数movablecore用于指定用于可移动分配的内存数量,如果指定了该参数,则其值会被保存在required_movablecore中,同时会基于它来计算required_kernelcore,代码如下(函数find_zone_movable_pfns_for_nodes):
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. corepages = totalpages - required_movablecore;  
  2. required_kernelcore = max(required_kernelcore, corepages);  
如果计算出来的required_kernelcore为0,则该机制将无效。
该zone是一个虚拟zone,它不和任何物理内存相关联,该域中的内存可能来自高端内存或者普通内存。用于不可移动分配的内存会被均匀的分布到系统的各个内存节点中;同时用于可移动分配的内存只会取自最高内存域的内存,zone_movable_pfn记录了取自各个节点的用于可移动分配的内存的起始地址。

四、初始化内存域和节点数据结构

在内存管理的初始化中,架构相关的代码要完成系统中可用内存的检测,并要将相关信息提交给架构无关的代码。架构无关的代码free_area_init_nodes负责完成管理数据结构的创建。该函数需要一个参数max_zone_pfn,它由架构相关的代码提供,其中保存了每个内存域的最大可用页帧号。内核定义了两个数组:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. static unsigned long __meminitdata arch_zone_lowest_possible_pfn[MAX_NR_ZONES];  
  2. static unsigned long __meminitdata arch_zone_highest_possible_pfn[MAX_NR_ZONES];  
这两个数组在free_area_init_nodes用于保存来自max_zone_pfn的信息,并将它转变成[low,high]的形式。
然后内核开始调用find_zone_movable_pfns_for_nodes对ZONE_MOVABLE域进行初始化。

然后内核开始为每一个节点调用free_area_init_node,这个函数将完成:
  1. 调用calculate_node_totalpages计算节点中页的总数
  2. 调用alloc_node_mem_map负责初始化struct pglist_data中的node_mem_map,为它分配的内存将用于存储本节点的所有物理内存的struct page结构。这片内存将对其到伙伴系统的最大分配阶上。而且如果当前节点是第0个节点,则该指针信息还将保存在全局变量mem_map中。
  3. 调用free_area_init_core完成初始化进一步的初始化

free_area_init_core将完成内存域数据结构的初始化,在这个函数中

  1. nr_kernel_pages记录直接映射的页面数目,而nr_all_pages则记录了包括高端内存中页数在内的页数
  2. 会调用zone_pcp_init初始化该内存域的每CPU缓存
  3. 会调用init_currently_empty_zone初始化该zone的wait_table,free_area列表
  4. 调用memmap_init初始化zone的页,所有页都被初始化为可移动的

五、分配器API

伙伴系统只能分配2的整数幂个页。因此申请时,需要指定请求分配的阶。
有很多分配和释放页的API,都定义在gfp.h中。最简单的是alloc_page(gfp_mask)用来申请一个页, free_page(addr)用来释放一个页。
这里更值得关注的获取页面时的参数gfp_mask,所有获取页面的API都需要指定该参数。它用来影响分配器的行为,其中有是分配器提供的标志,标志有两种:
zone修饰符:用于告诉分配器从哪个zone分配内存
行为修饰符:告诉分配器应该如何进行分配
其中zone修饰符定义为

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #define __GFP_DMA   ((__force gfp_t)___GFP_DMA)  
  2. #define __GFP_HIGHMEM   ((__force gfp_t)___GFP_HIGHMEM)  
  3. #define __GFP_DMA32 ((__force gfp_t)___GFP_DMA32)  
  4. #define __GFP_MOVABLE   ((__force gfp_t)___GFP_MOVABLE)  /* Page is movable */  
  5. #define GFP_ZONEMASK    (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)  
这些定义都一目了然,需要指出的是如果同时指定了__GFP_MOVABLE和__GFP_HIGHMEM,则会从虚拟的ZONE_MOVABLE分配。
更详细的可以参考gfp.h,其中包含了所有的标志及其含义。

1.分配页

__alloc_pages会完成最终的内存分配,它是伙伴系统的核心代码(但是在内核代码中,这种命名方式的函数都是需要小心调用的,一般都是给实现该功能的代码自己调用,不作为API提供出去的,因而它的包装器才是对外提供的API,也就是alloc_pages_node)。

1.选择页

选择页中最重要的函数是get_page_from_freelist,它负责通过标志和分配阶来判断分配是否可以进行,如果可以就进行实际的分配。该函数还会调用zone_watermark_ok根据指定的标识判断是否可以从给定的zone中进行分配。该函数需要struct zonelist的指针指向备用zone,当当前zone不能满足分配需求时就依次遍历该列表尝试进行分配。整体的分配流程是:

  1. 调用get_page_from_freelist尝试进行分配,如果成功就返回分配到的页,否则
  2. 唤醒kswapd,然后再次调用get_page_from_freelist尝试进行分配,如果成功就返回分配的页,否则
  3. 如果分配的标志允许不检查阈值进行分配,则以ALLOC_NO_WATERMARKS为标志再次调用get_page_from_freelist尝试分配,如果成功则返回分配的页;如果不允许不检查阈值或者仍然失败,则
  4. 如果不允许等待,就分配失败,否则
  5. 如果支持压缩,则尝试先对内存进行一次压缩,然后再调用get_page_from_freelist,如果成功就返回,否则
  6. 进行内存回收,然后再调用get_page_from_freelist,如果成功就返回,否则
  7. 根据回收内存并尝试分配的结果以及分配标志,可能会调用OOM杀死一个进程然后再尝试分配,也可能不执行OOM这一步的操作,如果执行了,则在失败后可能就彻底失败,也可能重新回到第2步,也可能继续下一步
  8. 回到第2步中调用get_page_from_freelist的地方或者再尝试一次先压缩后分配,如果走了先压缩再分配这一步,这就是最后一次尝试了,要么成功要么失败,不会再继续尝试了

2.移出所选择的页

在函数get_page_from_freelist中,会首先在zonelist中找到一个具有足够的空闲页的zone,然后会调用buffered_rmqueue进行处理,在分配成功时,该函数会把所分配的内存页从zone的free_list中移出,并且保证剩余的空闲内存页满足伙伴系统的要求,该函数还会把内存页的迁移类型存放在page的private域中。
该函数的步骤如图所示:


可以看出buffered_rmqueue的工作过程为:

  1. 如果申请的是单页,会做特殊处理,内核会利用每CPU的缓存加速这个过程。并且在必要的时候会首先填充每CPU的缓存。函数rmqueue_bulk用于从伙伴系统获取内存页,并添加到指定的链表,它会调用函数__rmqueue。
  2. 如果是分配多个页,则会首先调用__rmqueue从内存域的伙伴系统中选择合适的内存块,这一步可能失败,因为虽然内存域中有足够数目的空闲页,但是页不一定是连续的,如果是这样这一步就会返回NULL。在这一步中如果需要还会将大的内存块分解成小的内存块来进行分配,即按照伙伴系统的要求进行分配。
  3. 无论是分配单页还是多个页,如果分配成功,在返回分配的页之前都要调用prep_new_page,如果这一步的处理不成功就会重新进行分配(跳转到函数buffered_rmqueue的开始),否则返回分配的页。
函数__rmqueue的执行过程:
  1. 首先调用__rmqueue_smallest尝试根据指定的zone,分配的阶,迁移类型进行分配,该函数根据指定的信息进行查找,在找到一个可用的空闲内存页块后会将该内存页块从空闲内存页块链表中删除,并且会调用expand使得剩余的内存页块满足伙伴系统的要求。如果在这一步成功就返回,否则执行下一步
  2. 调用__rmqueue_fallback尝试从备用zone分配。该函数用于根据前一类型的备用列表尝试从其它备用列表分配,但是需要注意的是这里会首先尝试最大的分配阶,依次降低分配的阶,直到指定的分配的阶,采用这个策略是为了避免碎片—如果要用其它迁移类型的内存,就拿一块大的过来,而不是在其它迁移类型的小区域中到处引入碎片。同时如果从其它迁移类型的空闲内存页块分配到的是一个较大的阶,则整块内存页块的迁移类型可能会发生改变,从原来的类型改变为申请分配时所请求的类型(即迁移类型发生了改变)。分配成功时的动作和__rmqueue_smallest类似,移出内存页,调用expand。
函数prep_new_page的操作
  1. 对页进行检查,以确保页确实是可用的,否则就返回一个非0值导致分配失败
  2. 设置页的标记以及引用计数等等。
  3. 如果设置而来__GFP_COMP标志,则调用prep_compound_page将页组织成复合页(hugetlb会用到这个)。
复合页的结构如图所示:


复合页具有如下特性:

  • 复合页中第一个页称为首页,其它所拥有页都称为尾页
  • 组成复合页的所有的private域都指向首页
  • 第一个尾页的lru的next域指向释放复合页的函数指针
  • 第一个尾页的lru的prev域用于指向复合页所对应的分配的阶,即多少个页

2.释放页

__free_pages是释放页的核心函数,伙伴系统提供出去的API都是它的包装器。其流程:

  1. 减小页的引用计数,如果计数不为0则直接返回,否则
  2.  如果释放的是单页,则调用free_hot_cold_page,否则
  3.  调用__free_pages_ok
free_hot_cold_page会把页返还给每-CPU缓存而不是直接返回给伙伴系统,因为如果每次都返还给伙伴系统,那么将会出现每次的分配和释放都需要伙伴系统进行分割和合并的情况,这将极大的降低分配的效率。因而这里采用的是一种“惰性合并”,单页会首先返还给每-CPU缓存,当每-CPU缓存的页面数大于一个阈值时(pcp->high),则一次将pcp->patch个页返还给伙伴系统。free_pcppages_bulk在free_hot_cold_page中用于将内存页返还给伙伴系统,它会调用函数__free_one_page。
函数__free_pages_ok最终页会调到__free_one_page来释放页,__free_one_page会将页面释放返还给伙伴系统,同时在必要时进行递归合并。
在__free_one_page进行合并时,需要找到释放的page的伙伴的页帧号,这是通过__find_buddy_index来完成的,其代码非常简单:
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. __find_buddy_index(unsigned long page_idx,unsigned int order)  
  2. {  
  3.        returnpage_idx ^ (1 << order);  
  4. }  
根据异或的规则,这个结果刚好可以得到邻居的页帧号。因为根据linux的管理策略以及伙伴系统的定义,伙伴系统中每个内存页块的第一个页帧号用来标志该页,因此对于order阶的两个伙伴,它们只有1<<order这个比特位是不同的,这样,只需要将该比特与取反即可,而根据异或的定义,一个比特和0异或还是本身,一个比特和1异或刚好可以取反。因此就得到了这个算式。
如果可以合并还需要取得合并后的页帧号,这个更简单,只需要让两个伙伴的页帧号相与即可。
__free_one_page调用page_is_buddy来对伙伴进行判断,以决定是否可以合并。

六、不连续内存页的分配

内核总是尝试使用物理上连续的内存区域,但是在分配内存时,可能无法找到大片的物理上连续的内存区域,这时候就需要使用不连续的内存,内核分配了其虚拟地址空间的一部分(vmalloc区)用于管理不连续内存页的分配。
每个vmalloc分配的子区域都自包含的,在内核的虚拟地址空间中vmalloc子区域之间都通过一个内存页隔离开来,这个间隔用来防止不正确的访问。

1. 用vmalloc分配内存

vmalloc用来分配在虚拟地址空间连续,但是在物理地址空间不一定连续的内存区域。它只需要一个以字节为单位的长度参数。为了节省宝贵的较低端的内存区域,vmalloc会使用高端内存进行分配。
内核使用struct vm_struct来管理vmalloc分配的每个子区域,其定义如下:
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. struct vm_struct {  
  2.     struct vm_struct    *next;  
  3.     void            *addr;  
  4.     unsigned long       size;  
  5.     unsigned long       flags;  
  6.     struct page     **pages;  
  7.     unsigned int        nr_pages;  
  8.     phys_addr_t     phys_addr;  
  9.     const void      *caller;  
  10. };  
每个vmalloc子区域都对应一个该结构的实例。
  • next:指向下一个vmalloc子区域
  • addr:vmalloc子区域在内核虚拟地址空间的起始地址
  • size:vmalloc子区域的长度
  • flags:与该区域相关标志
  • pages:指针,指向映射到虚拟地址空间的物理内存页的struct page实例
  • nr_pages:映射的物理页面数目
  • phys_addr:仅当用ioremap映射了由物理地址描述的内存页时才需要改域,它保存物理地址
  • caller:申请者

2. 创建vmalloc子区域

所有的vmalloc子区域都被连接保存在vmlist中,该链表按照addr排序,顺序是从小到大。当创建一个新的子区域时需要,需要找到一个合适的位置。查找合适的位置采用的是首次适用算法,即从vmalloc区域找到第一个可以满足需求的区域,查找这样的区域是通过函数__get_vm_area_node完成的。其分配过程以下几步:
  1. 调用__get_vm_area_node找到合适的区域
  2. 调用__vmalloc_area_node分配物理内存页
  3. 调用map_vm_area将物理内存页映射到内核的读你地址空间
  4. 将新的子区域插入vmlist链表
在从伙伴系统分配物理内存页时使用了标志:GFP_KERNEL | __GFP_HIGHMEM
还有其它的方式来建立虚拟地址空间的连续映射:
  1. vmalloc_32:与vmallo工作方式相同,但是确保所使用的物理地址总可以用32位指针寻址
  2. vmap:将一组物理页面映射到连续的虚拟地址空间
  3. ioremap:特定于处理器的分配函数,用于将取自物理地址空间而、由系统总线用于I/O操作的一个内存块,映射到内核的虚拟地址空间

3. 释放内存

vfree用于释放vmalloc和vmalloc_32分配的内存空间,vunmap用于释放由vmap和ioremap分配的空间(iounmap会调到vunmap)。最终都会归结到函数__vunmap。
__vunmap的执行过程:
  1. 调用remove_vm_area从vmlist中找到一个子区域,然后将其从子区域删除,再解除物理页面的映射
  2. 如果设置了deallocate_pages,则将物理页面归还给伙伴系统
  3. 释放管理虚拟内存的数据结构struct vm_struct

七、内核映射

高端内存可通过vmalloc机制映射到内核的虚拟地址空间,但是高端内存往内核虚拟地址空间的映射并不依赖于vmalloc,而vmalloc是用于管理不连续内存的,它也并不依赖于高端内存。

1.持久内核映射

如果想要将高端内存长期映射到内核中,则必须使用kmap函数。该函数需要一个page指针用于指向需要映射的页面。如果没有启用高端内存,则该函数直接返回页的地址,因为所有页面都可以直接映射。如果启用了高端内存,则:
  • 如果不是高端内存的页面,则直接返回页面地址,否则
  • 调用kmap_high进行处理

1.使用的数据结构

vmalloc区域后的持久映射区域用于建立持久映射。pkmap_count是一个有LAST_PKMAP个元素的数组,每个元素对应一个持久映射。每个元素的值是被映射页的一个使用计数器:
  1. 0:相关的页么有被使用
  2. 1:该位置关联的页已经映射,但是由于CPU的TLB没有刷新而不能使用
  3. 大于1的其它值:表示该页的引用计数,n表示有n-1处在使用该页
数据结构
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. struct page_address_map {  
  2.     struct page *page;  
  3.     void *virtual;  
  4.     struct list_head list;  
  5. };  
用于建立物理页和其在虚拟地址空间位置之间的关系。
  • page:指向全局数据结构mem_map数组中的page实例的指针
  • virtual:该页在虚拟地址空间中分配的位置
所有的持久映射保存在一个散列表page_address_htable中,并用链表处理冲突,page_slot是散列函数。
函数page_address用于根据page实例获取器对应的虚拟地址。其处理过程:
  1. 如果不是高端内存直接根据page获得虚拟地址(利用__va(paddr)),否则
  2. 在散列表中查找该page对应的struct page_address_map实例,获取其虚拟地址

2.创建映射

函数kmap_high完成映射的实际创建,其工作过程:
  1. 调用page_address获取对应的虚拟地址
  2. 如果没有获取到,则调用map_new_virtual获取虚拟地址
  3. pkmap_count数组中对应于该虚拟地址的元素的引用计数加1
新映射的创建在map_new_virtual中完成,其工作过程:
 
  1. 执行一个无限循环:
    1. 更新last_pkmap_nr为last_pkmap_nr+1
    2. 同时如果last_pkmap_nr为0,调用flush_all_zero_pkmaps,flush CPU高速缓存
    3. 检查pkmap_count数组中索引last_pkmap_nr对应的元素的引用计数是否为0,如果是0就退出循环,否则
    4. 将自己加入到一个等待队列
    5. 调度其它任务
    6. 被唤醒时会首先检查是否有其它任务已经完成了新映射的创建,如果是就直接返回
    7. 回到循环头部重新执行
  2. 获取与该索引对应的虚拟地址
  3. 修改内核页表,将该页映射到获取到的虚拟地址
  4. 更新该索引对应的pkmap_count元素的引用计数为1
  5. 调用set_page_address将新的映射加入到page_address_htable中
flush_all_zero_pkmaps的工作过程:
  1. 调用flush_cache_kmaps执行高速缓存flush动作
  2. 遍历pkmap_count中的元素,如果某个元素的值为1就将其减小为0,并删除相关映射同时设置需要刷新标记
  3. 如果需要刷新,则调用flush_tlb_kernel_range刷新指定的区域对应的tlb。

3.解除映射

kunmap用于解除kmap创建的映射,如果不是高端内存,什么都不做,否则kunmap_high将完成实际的工作。kunmap_high的工作很简单,将对应的pkmap_count中的元素的引用计数的值减1,如果新值为1,则看是否有任务在pkmap_map_wait上等待,如果有就唤醒它。根据该机制的涉及原理,该函数不能将引用计数减小到小于1,否则就是一个BUG。

2.临时内核映射

kmap不能用于无法休眠的上线文,如果要在不可休眠的上下文调用,则需要调用kmap_atomic。它是原子的,特定于架构的。同样的只有是高端内存时才会做实际的映射。
kmap_atomic使用了固定映射机制。在固定映射区域,系统中每个CPU都有一个对应的“窗口”,每个窗口对应于KM_TYPE_NR中不同的类型都有一项。这个映射的核心代码如下(取自powerpc):
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. type = kmap_atomic_idx_push();  
  2. idx = type + KM_TYPE_NR*smp_processor_id();  
  3. vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);      
  4.        __set_pte_at(&init_mm, vaddr, kmap_pte-idx, mk_pte(page, prot), 1);  
  5. local_flush_tlb_page(NULL, vaddr);  
固定映射区域为用于kmap_atomic预留内存区的代码如下:
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. enum fixed_addresses {  
  2.     FIX_HOLE,  
  3.     /* reserve the top 128K for early debugging purposes */  
  4.     FIX_EARLY_DEBUG_TOP = FIX_HOLE,  
  5.     FIX_EARLY_DEBUG_BASE = FIX_EARLY_DEBUG_TOP+((128*1024)/PAGE_SIZE)-1,  
  6. <strong>#ifdef CONFIG_HIGHMEM  
  7.     FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */  
  8.     FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,  
  9. #endif</strong>  
  10.     /* FIX_PCIE_MCFG, */  
  11.     __end_of_fixed_addresses  
  12. };
2014-11-04 17:41:00 weixin_30868855 阅读数 38
  • Linux进程管理

    掌握Linux系统常用基命令 掌握文件操作、打包与解压 掌握vi编辑器的使用 掌握用户与权限管理 掌握进程管理和软件安装

    405人学习 CSDN就业班
    免费试看

VIRT

1、进程需要的虚拟内存大小,包括进程使用的库、代码、数据,以及mallocnew分配的堆空间和分配的栈空间等;

2、假如进程新申请10MB的内存,但实际只使用了1MB,那么它会增长10MB,而不是实际的1MB使用量。

3VIRT = SWAP + RES

RES

1、进程当前使用的内存大小,包括使用中的mallocnew分配的堆空间和分配的栈空间,但不包括swap out量;

2、包含其他进程的共享;

3、如果申请10MB的内存,实际使用1MB,它只增长1MB,与VIRT相反;

4、关于库占用内存的情况,它只统计加载的库文件所占内存大小。

5RES = CODE + DATA

SHR

1、除了自身进程的共享内存,也包括其他进程的共享内存;

2、虽然进程只使用了几个共享库的函数,但它包含了整个共享库的大小;

3、计算某个进程所占的物理内存大小公式:RES – SHR

4swap out后,它将会降下来。

示例

#include <iostream>

 

int main()

{

    char * p = new char [1024*1024*512];

    getchar();

    return 0;

}

top:

PID USER PR  NI  VIRT RES SHR S %CPU %MEM TIME+ COMMAND

401 hhhhh 17   0   523m 916 792  S  0.0  0.0   0:00.00 ./main

原文:

http://blog.csdn.net/huyiyang2010/article/details/7815491

转载于:https://www.cnblogs.com/catkins/p/5270397.html

2017-09-15 11:41:58 heliangbin87 阅读数 2784
  • Linux进程管理

    掌握Linux系统常用基命令 掌握文件操作、打包与解压 掌握vi编辑器的使用 掌握用户与权限管理 掌握进程管理和软件安装

    405人学习 CSDN就业班
    免费试看

内存访问分为两种体系结构:一致性内存访问(UMA)和非一致性内存访问(NUMA)。NUMA指CPU对不同内存单元的访问时间可能不一样,因而这些物理内存被划分为几个节点,每个节点里的内存访问时间一致,NUMA体系结构主要存在大型机器、alpha等,嵌入式的基本都是UMA。UMA也使用了节点概念,只是永远都只有1个节点。本文讲的是UMA模型的嵌入式平台,linux版本:3.10.y。

         每个节点又将物理内存划分为3个管理区,在x86机器上管理区如下:

ZONE_DMA:0~16MB

ZONE_NORMAL:16MB~896MB

ZONE_HIGHMEM:896~末尾(对于64位,则不需要高端内存,虚拟地址足够直接映射)

而在hi3536嵌入式平台上实际的使用时由于系统mem没有超过896M,因此只有一个管理区ZONE_NORMAL(cat /proc/buddyinfo):

Node 0, zone   Normal     70    70     36     22    11     10     2      2      3     2     37

         每个管理区管理该内存区域内的所有页面(linux中每个页面的大小为4KB)。

         以下linux物理内存组织结构标准的关系图:

 

         在Documentation/arm/memory.txt文件中定义了arm平台的线性地址空间布局情况,其与物理内存关系图:

         当用户空间和内核空间比例3:1时,PAGE_OFFSET=0xC0000000。上面橙色虚线地址是一一映射关系,而高于highmem则需要vmalloc申请使用。

         PHYS_OFFSET=0x40000000

         ZRELADDR == virt_to_phys(PAGE_OFFSET +TEXT_OFFSET) = virt_to_phys (TEXT_ADDR) = 0x40008000

         INITRD_PHYS= 0x00800000

         PARAMS_PHYS= 0x00000100

         high_memory和VMALLOC_START之间保留了8M空间间隙

         high_memory=PAGE_OFFSET+ highmem

         当系统内存在0~896MB时,highmem=系统内存

         当系统内存>896MB时,highmem=896MB,多余的内存需要vmalloc映射访问

         VMALLOC_START和VMALLOC_END-1之间用于vmalloc() / ioremap() space映射。

         cat /proc/vmallocinfo:

         0xfb000000-0xfe190000 51970048iotable_init+0x0/0xb0 phys=10000000 ioremap

         映射了CPU寄存器空间

 

1、节点

         每个节点由struct pglist_data定义(include/linux/mmzone.h):

typedef struct pglist_data {

#define MAX_NR_ZONES 3

struct zonenode_zones[MAX_NR_ZONES]; //分别代表3个管理区

#define MAX_ZONELISTS 1

    struct zonelistnode_zonelists[MAX_ZONELISTS]; //按照分配时的管理区顺序排列,如果在ZONE_HIGHMEM中分配失败,就有可能还原成ZONE_NORMAL或ZONE_DMA。

int nr_zones;   //表示管理区的数目,值为1、2、3。

struct page *node_mem_map; //指向该节点第一个物理页面

    struct bootmem_data*bdata; //指向内存引导程序

         unsigned longnode_start_pfn; //该节点的起始页编号

    //calculate_node_totalpages中对以下两个值进行计算

unsigned long node_present_pages;/* total number of physical pages */

    unsigned longnode_spanned_pages; /* total size of physical pagerange, including holes */

    int node_id;      //节点号,在嵌入式上一般为0

    nodemask_treclaim_nodes;   /* Nodes allowed toreclaim from */

    wait_queue_head_tkswapd_wait;         //交换守护进程kswapd使用的等待队列

    wait_queue_head_tpfmemalloc_wait;

    struct task_struct*kswapd;  //指向交换守护进程描述符

    int kswapd_max_order;

enum zone_type classzone_idx; //管理区类型

} pg_data_t;

 

include/linux/mmzone.h中定义了全局节点:

extern struct pglist_data contig_page_data;

#define NODE_DATA(nid)     (&contig_page_data)

 

mm/bootmem.c进行全局节点定义:

struct pglist_data __refdata contig_page_data = {

    .bdata =&bootmem_node_data[0]

};

EXPORT_SYMBOL(contig_page_data);

 

2、管理区

         每个管理区由structzone描述(include/linux/mmzone.h):

struct zone {

unsigned longwatermark[NR_WMARK]; //该管理区的三个水平线值,min, low, high

unsigned long percpu_drift_mark;

unsigned long       lowmem_reserve[MAX_NR_ZONES]; //每个管理区必须保留的页框数

unsigned long       dirty_balance_reserve;

struct per_cpu_pageset __percpu*pageset;         //CPU的页面缓存

    spinlock_t      lock;     //保护该管理区的自旋锁

int            all_unreclaimable; /* All pagespinned */

struct free_area    free_area[MAX_ORDER]; //通过伙伴算法管理的空闲页面

 

ZONE_PADDING(_pad1_)

    spinlock_t      lru_lock;

    struct lruvec       lruvec;

    unsigned long       pages_scanned; //管理区回收页框时使用的计数器,记录上一次回收一同扫描过的页框

    unsigned long       flags;         /* zone flags, see below */

    /* Zone statistics */

    atomic_long_t       vm_stat[NR_VM_ZONE_STAT_ITEMS];

    unsigned int inactive_ratio;

ZONE_PADDING(_pad2_)

 

wait_queue_head_t   * wait_table; //进程等待的散列表,这些进程正在等待管理区中的某页

    unsigned long       wait_table_hash_nr_entries; //散列表数组的大小

    unsigned long       wait_table_bits; //散列表数组的大小对2取log的结果

 

    struct pglist_data  *zone_pgdat; //管理区属于的节点

    /* zone_start_pfn ==zone_start_paddr >> PAGE_SHIFT */

unsigned long       zone_start_pfn;  //管理区的起始页号

unsigned long       spanned_pages; //管理区的大小包括洞

unsigned long       present_pages; //管理区的大小不包括洞

unsigned long       managed_pages;

 

const char      *name; //管理区名字,DMA、NORMAL orHIGHMEM

}

         当系统中的可用内存很少时,守护程序kswapd被唤醒释放页面。每个管理区通过数组watermark来决定唤醒还是睡眠kswapd守护进程,这个数组通过下面的枚举来分别代表page_min, page_low, page_high,他们的之间的关系图如下图:

enum zone_watermarks {

    WMARK_MIN,

    WMARK_LOW,

    WMARK_HIGH,

    NR_WMARK

};

page_low:当空闲页面数达到pages_low时,伙伴算法分配器就会唤醒kswapd释放页面。

page_min:当达到pages_min时,kswap没唤醒则唤醒,同时同步进程释放内存,如果申请内存远远超过实际内存,就会出现out_of_memory。page_min包含里管理区最小保留内存,因此这是GFP_ATOMIC还可以分配出内存。

page_high:当释放页面达到这个值,认为该管理区已经平衡,kswapd开始睡眠。

         当多个进程对同一个页面进行IO操作(个人理解觉得更应该是有写操作)时,比如页面换入或换出,为了防止访问数据的不一致性,该页面会被锁住,而其他进程则通过wait_on_page()函数被添加到等待队列中。如果每个页面都有等待队列则系统会花费大量的内存存放,linux则是将等待队列存储在管理区的wait_table散列表中,这样就只有一个等待队列。其简单流程图如下:

当申请分配标志为GFP_ATOMIC时则不会睡眠,因为该标志不能睡眠的内存分配标志,用在中断处理程序、下半部、持有自旋锁以及其他不能睡眠的地方。

 

3、物理页面

         系统中的每个物理页面都由structpage用以记录该页面的状态,结构体包含了很多union,因为现在有slab/slob/slub三种分配器使用其中一种即可,定义如下(include/linux/mm_types.h):

struct page {

/* First double word block */

         unsigned long flags;         //存放页的状态

         struct address_space*mapping; //如果低位是clear的,则内存映射到文件或设备,它指向文件或设备节点inode的address_space,或者NULL;如果内存映射到匿名则低位被设置,指向匿名对象objects。

         /* Second double word*/

         struct {

        union {

            pgoff_tindex;     //页面是文件映射的一部分,它就是页面在文件中的偏移;页面是交换高速缓存一部分,它就是在交换地址空间中address_space的偏移量

            void*freelist;     //指向slub/slob第一个空闲对象

            boolpfmemalloc;   

        };

 

        union {

            unsigned counters;

            struct {

                union {

                    atomic_t_mapcount;

 

                    struct {/* SLUB */

                       unsigned inuse:16;

                       unsigned objects:15;

                       unsigned frozen:1;

                    };

                    intunits;  /* SLOB */

                };

                atomic_t _count; //页的引用计数

            };

        };

    };

 

    /* Third double word block*/

    union {

        struct list_headlru;   //最近最少使用(LRU)链表的链表首部

        struct {        /* slub per cpu partial pages */

            struct page*next;  /* Next partial slab */

            short int pages;

            short intpobjects;

        };

 

        struct list_headlist;  //slob列表

        struct slab *slab_page;/* slab fields */

};

    /* Remainder is not doubleword aligned */

    union {

        unsigned long private;

        struct kmem_cache*slab_cache;  /* SL[AU]B: Pointer to slab*/

        struct page*first_page;    /* Compound tail pages */

};

#if defined(WANT_PAGE_VIRTUAL)

    void *virtual;          /* Kernel virtual address (NULL if

                       notkmapped, ie. highmem) */

#endif /* WANT_PAGE_VIRTUAL */

}

         Flags的最高ZONES_SHIFT位记录该页面所属的管理区。set_page_zone函数设置页面的管理区。

 

4、源码解析

linux内核的内存管理分三个阶段。
A. 启动---->bootmem初始化完成为第一阶段。此阶段只能使用memblock_reserve函数分配内存。
B. bootmem初始化完--->buddy完成前。该阶段使用引导内存分配器(boot memoryallocator)分配内存。
C. 全部内存初始化完毕,可以用cache和buddy分配内存。

 

(1)~(4)点完成第一阶段,该阶段主要在start_kernel—> setup_arch函数实现:

(1)获取总内存

parse_early_param—> early_mem函数根据uboot传递进来的命令行参数mem=size@start计算出起始和内存大小通过arm_add_memory添加到meminfo全局数组里。

这里需要注意parse_early_param之前setup_machine_tags里parse_tags中的parse_tag_mem32也会解析uboot通过tags方法传递进来的系统内存,但是被early_mem覆盖重新计算。

 

(2)计算高端内存

         sanity_check_meminfo(arch/arm/mm/mmu.c)对meminfo里的内存进行判断,是否需要划分为高端内存。

 

(3)计算保留内存块

         保留内存块包括内核(数据段,代码段等)、页目录等占用的内存块。通过arm_memblock_init(arch/arm/mm/init.c)实现:

A. 将meminof内存通过memblock_add加到memblock.memory类型内存块里(包含全部物理内存)

B.通过memblock_reserve将保留内存添加到memblock.reserved类型内存块里

memblock_reserve(__pa(_stext), _end -_stext); 内核通过查看System.map可以看到

arm_mm_memblock_reserve(); //页表

最终结果显示如下:

MEMBLOCK configuration:

 memory size = 0xfa00000(250M) reservedsize = 0x6ba3cc

 memory.cnt = 0x1

 memory[0x0]   [0x00000040000000-0x0000004f9fffff], 0xfa00000 bytes

 reserved.cnt = 0x2

 reserved[0x0] [0x00000040004000-0x00000040007fff], 0x4000 bytes //页表

 reserved[0x1] [0x000000400081c0-0x000000406be58b], 0x6b63cc bytes //内核

 

在伙伴算法完成之前,内存的分配通过该方式进行。

 

(4)paging_init

         该函数arch/arm/mm/mmu.c实现,完成页表设置、管理区zone、设置零页等。

/*

 *paging_init() sets up the page tables, initialises the zone memory

 *maps, and sets up the zero page, bad page and bad page tables.

 */

void __init paging_init(struct machine_desc*mdesc)

{

   void *zero_page;

 

   memblock_set_current_limit(arm_lowmem_limit);

                                                                                                                                                 

build_mem_type_table();//建立各种类型页表的属性(内存MEMORY类型,设备DEVICE,中断向量表是HIGH_VECTORS),根据不同arm体系进行初始化,这是因为地址转换由MMU硬件单元根据页表处理,每个体系的MMU单元有差别(不是很明白)。

         /*以下见第二章页表管理*/

   prepare_page_table(); //清除在这之前建立的临时页表,以便下面建立正式链表

   map_lowmem(); //为低端内存建立一一的映射表(0~ arm_lowmem_limit),存放在swapper_pg_dir位置,用到前面的页表类型:MT_MEMORY

   dma_contiguous_remap(); //建立DMA映射表,类型为:MT_MEMORY_DMA_READY

   devicemaps_init(mdesc); //完成ffff0000开始的中断向量映射表,完成CPU IO映射表(从0x10000000~0x13190000就是控制器地址空间)、刷新一下TLB

   kmap_init(); //申请kmap高端内存永久映射的页表项,虚拟空间为2M(0xbfe00000 - 0xc0000000),如果未定义高端内存(CONFIG_HIGHMEM)什么也不做。

   tcm_init(); //do nothing

 

         //关于页表见第二章,此处返回中断向量表0xffff0000在pgd中的偏移量

   top_pmd = pmd_off_k(0xffff0000);

 

   /* allocate the zero page. */

   zero_page = early_alloc(PAGE_SIZE);

 

    bootmem_init(); //见下一节重点分析

 

         //一种特殊的页,供初始化为0的数据和写时复制使用

   empty_zero_page = virt_to_page(zero_page);

   __flush_dcache_page(NULL, empty_zero_page);

}

 

(4-1)bootmem_init

         在arch/arm/mm/init.c中定义,完成buddy管理内存需要的工作。

void __init bootmem_init(void)

{

unsigned longmin, max_low, max_high;

 

   max_low = max_high = 0;

find_limits(&min,&max_low, &max_high); //最小物理页号,低端内存最大物理页号,高端内存最大物理页号

/* 申请低端内存所需位图的空间,然后赋值给节点pgdat->bdatapgdat = NODE_DATA(0);只有一个节点,实际为一个全局变量contig_page_data)并将位图所在内存空间保留(memblock.reserved),bdata->node_bootmem_map指向位图空间(bdata struct bootmem_data);先将memblock.memory内存(所有物理内存)的位图清零—表示未使用,再将memblock.reserved 内存的位图置1—表示使用

node_bootmem_map:cf9f7000,该部分内存空间在建立伙伴系统的时候释放,所以放在内存的末端

*/

   arm_bootmem_init(min, max_low);

         arm_memory_present();  //do nothing

         sparse_init();//do nothing

         arm_bootmem_free(min, max_low, max_high); //见下一节介绍

max_low_pfn =max_low - PHYS_PFN_OFFSET; //低端内存的物理页数目

max_pfn = max_high- PHYS_PFN_OFFSET; //高端内存的物理页数目

}

 

(4-2)arm_bootmem_free

         该函数(arch/arm/mm/init.c)先计算zone_size和zhole_size,然后调用free_area_init_node(mm/page_alloc.c),因此这里主要分析free_area_init_node函数:

void __paginginit free_area_init_node(intnid, unsigned long *zones_size,

       unsigned long node_start_pfn, unsigned long *zholes_size)

{

   pg_data_t *pgdat = NODE_DATA(nid);

 

   pgdat->node_id = nid;

   pgdat->node_start_pfn = node_start_pfn;

init_zone_allows_reclaim(nid);

//计算节点的node_spanned_pages和node_present_pages,没有高端内存两者相等

   calculate_node_totalpages(pgdat, zones_size, zholes_size);

 

         //申请所有物理页描述结构体(structpage)所需要的内存空间,mem_map= NODE_DATA(0)->node_mem_map指向该空间

   alloc_node_mem_map(pgdat); //空间大小:page size:200000//2M

   printk("free_area_init_node: node %d, pgdat %08lx, node_mem_map%08lx\n",

           nid, (unsigned long)pgdat,  (unsigned long)pgdat->node_mem_map);

free_area_init_node:node 0, pgdat c06535c0(内核数据区), node_mem_map c06bf000(内核bss后面申请的一个内存地址)

    free_area_init_core(pgdat, zones_size, zholes_size); //见下节

}

 

(4-3)free_area_init_core

         该函数(mm/page_alloc.c)实现如下:

static void __paginginitfree_area_init_core(struct pglist_data *pgdat,

       unsigned long *zones_size, unsigned long *zholes_size)

{

   enum zone_type j;

   int nid = pgdat->node_id;

   unsigned long zone_start_pfn = pgdat->node_start_pfn;

   int ret;

 

         //初始化自旋锁和相关等待队列

   pgdat_resize_init(pgdat);

   init_waitqueue_head(&pgdat->kswapd_wait);

   init_waitqueue_head(&pgdat->pfmemalloc_wait);

pgdat_page_cgroup_init(pgdat);

//初始化各个管理区

   for (j = 0; j < MAX_NR_ZONES; j++) {

       struct zone *zone = pgdat->node_zones + j;

       unsigned long size, realsize, freesize, memmap_pages;

 

                   /*重新计算管理区的大小(减去struct page所占用的内存空间)*/

       size = zone_spanned_pages_in_node(nid, j, zones_size);

       realsize = freesize = size - zone_absent_pages_in_node(nid, j,

                                zholes_size);

       memmap_pages = calc_memmap_size(size, realsize);

       if (freesize >= memmap_pages) {

           freesize -= memmap_pages;

       }

      if (!is_highmem_idx(j))

           nr_kernel_pages += freesize;

       /* Charge for highmem memmap if there are enough kernel pages */

       else if (nr_kernel_pages > memmap_pages * 2)

           nr_kernel_pages -= memmap_pages;

       nr_all_pages += freesize;

 

       zone->spanned_pages = size;

       zone->present_pages = realsize;

       /*

        * Set an approximate value for lowmem here, it will be adjusted

        * when the bootmem allocator frees pages into the buddy system.

        * And all highmem pages will be managed by the buddy system.

        */

       zone->managed_pages = is_highmem_idx(j) ? realsize : freesize;

       zone->name = zone_names[j];

                   //初始化其他自旋锁

       spin_lock_init(&zone->lock);

       spin_lock_init(&zone->lru_lock);

       zone_seqlock_init(zone);

                   //指向节点

       zone->zone_pgdat = pgdat;

 

       zone_pcp_init(zone); //初始化CPU页面缓存

       lruvec_init(&zone->lruvec); //初始化lru

       if (!size)

           continue;

 

       set_pageblock_order();

       setup_usemap(pgdat, zone, zone_start_pfn, size);

                   //初始化该管理区的wait_table和free_area(伙伴算法)

       ret = init_currently_empty_zone(zone, zone_start_pfn,

                        size, MEMMAP_EARLY);

       BUG_ON(ret);

                   //初始化该管理区的所有物理页结构体struct page

       memmap_init(size, nid, j, zone_start_pfn);

       zone_start_pfn += size;

    }

}

 

(5)setup_per_cpu_areas

在init/main.c的start_kernel调用,在mm/percpu.c中定义,只有在SMP系统下才有用,在UP不做任何处理。

该函数主要是为每个处理器设置per-cpu数据区域,per_cpu数据由各个CPU独立使用,即使不锁访问,十分有效。per-cpu数据按照不同的CPU类型使用,以将性能低下引发的缓存一致性问题减小到最小。

 

(6)build_all_zonelists

         在init/main.c的start_kernel调用,在mm/page_alloc.c中定义。初始化每个节点内的zonelists。

 

(7)mm_init 设置内存分配器

         在init/main.c中定义,主要用于设设置伙伴算法内存分配器。源码如下:

static void __init mm_init(void)

{  

   /*

    * page_cgroup requires contiguous pages,

    * bigger than MAX_ORDER unless SPARSEMEM.

    */

   page_cgroup_init_flatmem();

    mem_init(); //见下面

   kmem_cache_init(); //建立kmem_cache和kmem_cache_node两个高速缓存(slab分配器使用)。

   percpu_init_late();

   pgtable_cache_init(); //do nothing

   vmalloc_init(); //vmalloc分配的内存虚拟地址连续,而物理地址无需连续,这里初始vmalloc要用的相关链表等准备工作。

}

 

void __init mem_init(void)

{

   unsigned long reserved_pages, free_pages;

   struct memblock_region *reg;

int i;

 

   max_mapnr   = pfn_to_page(max_pfn+ PHYS_PFN_OFFSET) - mem_map;

 

//以下两者都是释放空闲内存到伙伴系统:memblock的空闲内存,后者是bootmem的空闲内存,其实两者有交叠,伙伴系统会尝试与前后连续页框组成更大的页框块。

/* bootmem分配器核心就是node_bootmem_map这个位图,每一位代表这个node的一个页,当需要分配时就会扫描这个位图,然后获取一段物理页框进行分配,一般都会从开始处向后进行分配,并没有什么特殊的算法在其中。而伙伴系统初始化时页会根据这个位图,将位图中空闲的页释放回到伙伴系统中,而已经分配出去的页则不会在初始化阶段释放回伙伴系统,不过有可能会在系统运行过程中释放回伙伴系统中*/

    free_unused_memmap(&meminfo); //根据代码只有一个bank,所以没做啥事情

totalram_pages+= free_all_bootmem();

//释放高端内存到伙伴系统

   free_highpages();

 

   reserved_pages = free_pages = 0;

 

         //统计空闲内存和保留内存并打印出来

   for_each_bank(i, &meminfo) {

       struct membank *bank = &meminfo.bank[i];

       unsigned int pfn1, pfn2;

       struct page *page, *end;

 

        pfn1 = bank_pfn_start(bank);

       pfn2 = bank_pfn_end(bank);

 

       page = pfn_to_page(pfn1);

       end  = pfn_to_page(pfn2 - 1) + 1;

 

       do {

           if (PageReserved(page))

                reserved_pages++;

           else if (!page_count(page))

                free_pages++;

           page++;

       } while (page < end);

}

}

 

(8)伙伴算法建立后物理内存分布

经过上面源码分析后,下图是该阶段物理内存分布情况:

4000:内核全局页表swapper_pg_dir起始地址,共16K

8000~6be58c:内核存放空间(text\data\bss等)

6bf000~6bf000+200000:存放所有页框描述数据结构structpage

f9f7000:存放早期bootmem分配内存使用的位图空间起始地址,建立伙伴算法后会释放掉

916000~fa00000:空闲页表,全部有伙伴系统管理

前面的9304K保留内存永远不释放,也不归伙伴系统管理

 

内核信息打印如下:

Memory: 250MB = 250MB total

Memory: 246696k/246696k available, 9304k reserved, 0

                   Virtual kernel memory layout:

                       vector  : 0xffff0000 - 0xffff1000   (   4kB)

                       fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB)

                       vmalloc: 0xd0000000 - 0xff000000   ( 752 MB)

                       lowmem  : 0xc0000000 - 0xcfa00000   ( 250 MB)

                       pkmap   : 0xbfe00000 - 0xc0000000   (   2MB)

                       modules: 0xbf000000 - 0xbfe00000   (  14 MB)

                             .text: 0xc0008000 - 0xc05e85c8   (6018 kB)

                             .init: 0xc05e9000 - 0xc0617ec0   ( 188 kB)

                             .data: 0xc0618000 - 0xc06542c0   ( 241 kB)

                            .bss: 0xc06542c0 - 0xc06be58c   ( 425 kB)

2019-09-07 14:14:31 qq_42685292 阅读数 481
  • Linux进程管理

    掌握Linux系统常用基命令 掌握文件操作、打包与解压 掌握vi编辑器的使用 掌握用户与权限管理 掌握进程管理和软件安装

    405人学习 CSDN就业班
    免费试看

 

方式一:free

直观的展示linux系统内存使用及交换区的容量大小等

方式二:top

动态实时查看系统中每一个进程的资源使用情况

方式三:cat /proc/meminfo

方式四:gnome-system-monitor 一个显示最近一段时间内的CPU、内存、交换区及网络使用情况的视图工具。

方式五:ps aux –sort -rss

各个线程的资源使用情况

方式六:vmstat -s

vmstat命令显示实时的和平均的统计,覆盖CPU、内存、I/O等内容。

2016-12-14 10:37:36 u011341352 阅读数 58288
  • Linux进程管理

    掌握Linux系统常用基命令 掌握文件操作、打包与解压 掌握vi编辑器的使用 掌握用户与权限管理 掌握进程管理和软件安装

    405人学习 CSDN就业班
    免费试看
查看linux系统中空闲内存/物理内存使用/剩余内存


查看系统内存有很多方法,但主要的是用top命令和free 命令
当执行top命令看到结果,要怎么看呢?这里说明一下:
Mem: 666666k total, 55555k used,并不是代表你的应用程序已经使用了55555k的内存,这55555k是包含了:应用程序内存 + 缓冲 + 缓存的内存的。
用free命令查看更直接:
下面是一个例子(单位是MB): 
[root@linuxzgf ~]# free -m
                    total       used       free     shared    buffers     cached
Mem:          7982       6811       1171          0        350       5114
-/+ buffers/cache:       1346       6636
Swap:        16935         11      16924


在这里例子中,应用程序只使用了1346MB内存,还有6636MB空闲内存可以使用. 
一些简单的计算方法: 
物理已用内存 = 实际已用内存 - 缓冲 - 缓存 = 6811M - 350M - 5114M


物理空闲内存 = 总物理内存 - 实际已用内存 + 缓冲 + 缓存 


应用程序可用空闲内存 = 总物理内存 - 实际已用内存 


应用程序已用内存 = 实际已用内存 - 缓冲 - 缓存


top命令的结果详解
top命令 是Linux下常用的性能 分析工具 ,能够实时显示系统 中各个进程的资源占用状况,类似于Windows的任务管理 器。下面详细介绍它的使用方法。


top - 02:53:32 up 16 days,  6:34, 17 users,  load average: 0.24, 0.21, 0.24
Tasks: 481 total,   3 running, 474 sleeping,   0 stopped,   4 zombie
Cpu(s): 10.3%us,  1.8%sy,  0.0%ni, 86.6%id,  0.5%wa,  0.2%hi,  0.6%si,  0.0%st
Mem:   4042764k total,  4001096k used,    41668k free,   383536k buffers
Swap:  2104472k total,     7900k used,  2096572k free,  1557040k cached


  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
32497 jacky     20   0  669m 222m  31m R   10  5.6       29:27.62 firefox
 4788 yiuwing   20   0  257m  18m  13m S    5  0.5          5:42.44 konsole
 5657 Liuxiaof  20   0  585m 159m  30m S    4  4.0          5:25.06 firefox
 4455 xiefc      20   0  542m  124m  30m R    4  3.1         7:23.03 firefox
 6188 Liuxiaof  20   0  191m   17m  13m S    4  0.5          0:01.16 konsole
 




统计信息区前五行是系统整体的统计信息。第一行是任务队列信息,同 uptime  命令的执行结果。其内容如下:


01:06:48  当前时间  
up 1:22  系统运行 时间,格式为时:分  
1 user  当前登录用户 数  
load average: 0.06, 0.60, 0.48  系统负载 ,即任务队列的平均长度。
            三个数值分别为  1分钟、5分钟、15分钟前到现在的平均值。 




第二、三行为进程和CPU的信息。当有多个CPU时,这些内容可能会超过两行。内容如下:


Tasks: 29 total  进程总数  
1 running  正在运行的进程数  
28 sleeping  睡眠的进程数  
0 stopped  停止的进程数  
0 zombie  僵尸进程数  
Cpu(s): 0.3% us  用户空间占用CPU百分比  
1.0% sy  内核 空间占用CPU百分比  
0.0% ni  用户进程空间内改变过优先级的进程占用CPU百分比  
98.7% id  空闲CPU百分比  
0.0% wa  等待输入输出的CPU时间百分比  
0.0% hi     
0.0% si    




最后两行为内存 信息。内容如下:


Mem: 191272k total  物理内存总量  
173656k used  使用的物理内存总量  
17616k free  空闲内存总量  
22052k buffers  用作内核缓存 的内存量  
Swap: 192772k total  交换区总量  
0k used  使用的交换区总量  
192772k free  空闲交换区总量  
123988k cached  缓冲的交换区总量。
            内存中的内容被换出到交换区,而后又被换入到内存,但使用过的交换区尚未被覆盖,
            该数值即为这些内容已存在于内存中 的交换区的大小。
            相应的内存再次被换出时可不必再对交换区写入。 




进程信息区统计信息区域的下方显示了各个进程的详细信息。首先来认识一下各列的含义。


序号  列名  含义  
a  PID  进程id  
b  PPID  父进程id  
c  RUSER  Real user name  
d  UID  进程所有者的用户id  
e  USER  进程所有者的用户名  
f  GROUP  进程所有者的组名  
g  TTY  启动进程的终端名。不是从终端启动的进程则显示为 ?  
h  PR  优先级  
i  NI  nice值。负值表示高优先级,正值表示低优先级  
j  P  最后使用的CPU,仅在多CPU环境 下有意义  
k  %CPU  上次更新到现在的CPU时间占用百分比  
l  TIME  进程使用的CPU时间总计,单位秒  
m  TIME+  进程使用的CPU时间总计,单位1/100秒  
n  %MEM  进程使用的物理内存 百分比  
o  VIRT  进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES  
p  SWAP  进程使用的虚拟内存中,被换出的大小,单位kb。  
q  RES  进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA  
r  CODE  可执行代码占用的物理 内存大小,单位kb  
s  DATA  可执行代码以外的部分(数据 段+栈)占用的物理 内存大小,单位kb  
t  SHR  共享内存大小,单位kb  
u  nFLT  页面错误次数  
v  nDRT  最后一次写入到现在,被修改过的页面数。  
w  S  进程状态。
            D =不可中断的睡眠状态
            R =运行
            S =睡眠
            T =跟踪/停止
            Z =僵尸进程  
x  COMMAND  命令名/命令行  
y  WCHAN  若该进程在睡眠,则显示睡眠中的系统函数名  
z  Flags  任务标志,参考 sched.h 




默认情况下仅显示比较重要的  PID、USER、PR、NI、VIRT、RES、SHR、S、%CPU、%MEM、TIME+、COMMAND  列。可以通过下面的快捷键来更改显示内容。
更改显示内容通过 f 键可以选择显示的内容。按 f 键之后会显示列的列表,按 a-z  即可显示或隐藏对应的列,最后按回车键确定。
按 o 键可以改变列的显示顺序。按小写的 a-z 可以将相应的列向右移动,而大写的 A-Z  可以将相应的列向左移动。最后按回车键确定。
按大写的 F 或 O 键,然后按 a-z 可以将进程按照相应的列进行排序。而大写的  R 键可以将当前的排序倒转。
top命令使用过程中,还可以使用一些交互的命令来完成其它参数的功能。这些命令是通过快捷键启动的。
<空格>:立刻刷新。
P:根据CPU使用大小进行排序。
T:根据时间、累计时间排序。
q:退出top命令。
m:切换显示内存信息。
t:切换显示进程和CPU状态信息。
c:切换显示命令名称和完整命令行。
M:根据使用内存大小进行排序。
W:将当前设置写入~/.toprc文件中。这是写top配置文件的推荐方法。
可以看到,top命令是一个功能十分强大的监控系统的工具,对于系统管理员而言尤其重要。但是,它的缺点是会消耗很多系统资源。




居然查看了内存,以下指令也会用到:
查看CPU个数
# cat /proc/cpuinfo | grep "physical id" | uniq | wc -l
查看CPU核数
# cat /proc/cpuinfo | grep "cpu cores" | uniq
查看CPU型号
# cat /proc/cpuinfo | grep 'model name' |uniq
那么,该服务器有2个4核CPU,型号Intel(R) Xeon(R) CPU E5630 @ 2.53GHz


占用内存的测量
测量一个进程占用了多少内存,linux为我们提供了一个很方便的方法,/proc目录为我们提供了所有的信息,实际上top等工具也通过这里来获取相应的信息。
/proc/meminfo 机器的内存使用信息
/proc/pid/maps pid为进程号,显示当前进程所占用的虚拟地址。
/proc/pid/statm 进程所占用的内存