Linux内存管理详解.docx

上传人:b****6 文档编号:7269041 上传时间:2023-01-22 格式:DOCX 页数:23 大小:61.72KB
下载 相关 举报
Linux内存管理详解.docx_第1页
第1页 / 共23页
Linux内存管理详解.docx_第2页
第2页 / 共23页
Linux内存管理详解.docx_第3页
第3页 / 共23页
Linux内存管理详解.docx_第4页
第4页 / 共23页
Linux内存管理详解.docx_第5页
第5页 / 共23页
点击查看更多>>
下载资源
资源描述

Linux内存管理详解.docx

《Linux内存管理详解.docx》由会员分享,可在线阅读,更多相关《Linux内存管理详解.docx(23页珍藏版)》请在冰豆网上搜索。

Linux内存管理详解.docx

Linux内存管理详解

13. 内存管理

13.1. 引言

Linux对物理内存的描述机制有两种:

UMA和NUMA。

Linux把物理内存划分为三个层次来管理:

存储节点(Node)、管理区(Zone)和页面(Page)。

UMA对应一致存储结构,它只需要一个Node就可以描述当前系统中的物理内存,但是NUMA的出现打破了这种平静,此时需要多个Node,它们被统一定义为一个名为discontig_node_data的数组。

为了和UMA兼容,就将描述UMA存储结构的描述符contig_page_data放到该数组的第一个元素中。

内核配置选项CONFIG_NUMA决定了当前系统是否支持NUMA机制。

此时无论UMA还是NUMA,它们都是对应到一个类型为pg_data_t的数组中,便于统一管理。

图 71. NodeZone和Page的关系

上图描述Linux管理物理内存的三个层次之间的拓扑关系。

从图中可以看出一个存储节点由pg_data_t描述,一个UMA系统中只有一个Node,而在NUMA中则可以存在多个Node。

它由CONFIG_NODES_SHIFT配置选项决定,它是CONFIG_NUMA的子选项,所以只有配置了CONFIG_NUMA,该选项才起作用。

UMA情况下,NODES_SHIFT被定义为0,MAX_NUMNODES也即为1。

include/linux/numa.h

#ifdefCONFIG_NODES_SHIFT

#defineNODES_SHIFTCONFIG_NODES_SHIFT

#else

#defineNODES_SHIFT0

#endif

#defineMAX_NUMNODES(1<

这里主要介绍UMA机制。

contig_page_data被定义如下:

mm/page_alloc.c

structpglist_data__refdatacontig_page_data={.bdata=&bootmem_node_data[0]};

EXPORT_SYMBOL(contig_page_data);

structpglist_data即是pg_data_t的原型。

了解pg_data_t中的结构成员对于了解内存管理是必经之路:

enumzone_type{

ZONE_DMA,

ZONE_NORMAL,

ZONE_MOVABLE,

......

__MAX_NR_ZONES

};

typedefstructpglist_data{

structzonenode_zones[MAX_NR_ZONES];

structzonelistnode_zonelists[MAX_ZONELISTS];

intnr_zones;

#ifdefCONFIG_FLAT_NODE_MEM_MAP/*means!

SPARSEMEM*/

structpage*node_mem_map;

#ifdefCONFIG_CGROUP_MEM_RES_CTLR

structpage_cgroup*node_page_cgroup;

#endif

#endif

structbootmem_data*bdata;

....../*forCONFIG_MEMORY_HOTPLUG*/

unsignedlongnode_start_pfn;

unsignedlongnode_present_pages;/*totalnumberofphysicalpages*/

unsignedlongnode_spanned_pages;/*totalsizeofphysicalpage

range,includingholes*/

intnode_id;

wait_queue_head_tkswapd_wait;

structtask_struct*kswapd;

intkswapd_max_order;

}pg_data_t;

∙node_zones:

当前节点中包含的最大管理区数。

MAX_NR_ZONES在include/linux/bounds.h定义,该文件是在编译过程中根据管理区类型定义中的__MAX_NR_ZONES变量自动生成的。

∙node_zonelists:

内存分配器所使用的管理区链表数组,MAX_ZONELISTS的值在配置CONFIG_NUMA时为2,否则为1。

索引为0的链表表示后援(Fallback)链表,也即当该链表中的第一个不满足分配内存时,依次尝试链表的其他管理区。

索引为1,的链表则用来针对GFP_THISNODE的内存申请,此时只能申请指定的该链表中的管理区。

∙nr_zones:

指定当前节点中的管理区数,也即node_zones中实际用到的管理区数。

它的取值范围为[1,MAX_NR_ZONES]。

对于UMA来说,它的值为1。

∙node_mem_map:

节点中页描述符数组首地址。

∙node_page_cgroup:

∙bdata:

系统引导时用的Bootmem分配器。

∙node_start_pfn:

节点中第一个页框的下标。

∙node_present_pages:

节点中的页面数,不包含孔洞。

∙node_spanned_pages:

节点中的页面总数,包含孔洞。

∙node_id:

节点标识符,在节点数组中唯一存在。

∙kswapd_wait:

kswapd页换出守护进程使用的等待队列。

∙kswapd:

指针指向kswaps内核线程的进程描述符。

∙kswapd_max_order:

kswapd将要创建的空闲块大小取对数的值。

注意到zonelist中的_zonerefs元素,它用来实现分配器分配内存时候的管理区后援功能。

MAX_ZONES_PER_ZONELIST被定义为所有节点中包含的最多管理区的和并加上1,加1的目的是在后援链表中,可以检测是否遍历到最后一个节点了,如果是说明申请失败。

/*Maximumnumberofzonesonazonelist*/

#defineMAX_ZONES_PER_ZONELIST(MAX_NUMNODES*MAX_NR_ZONES)

structzonelist{

structzonelist_cache*zlcache_ptr;//NULLor&zlcache

structzoneref_zonerefs[MAX_ZONES_PER_ZONELIST+1];

#ifdefCONFIG_NUMA

structzonelist_cachezlcache;//optional...

#endif

};

节点中的管理区都在free_area_init_core函数中初始化。

调用关系如下所示:

start_kernel->setup_arch->paging_init->bootmem_init->bootmem_free_node->free_area_init_node->free_area_init_core

在理想的计算机体系结构中,一个物理页框就是一个内存存储单元,可用于任何事情:

存放内核数据和用户数据,磁盘缓冲数据等。

热河中磊的数据页都可以存放在任何页框中,没有什么限制。

但是,实际的计算机体系结构有硬件的制约,这制约页框可以使用的方式。

尤其是Linux内核必须处理80x86体系结构的两种硬件约束:

∙ISA总线的直接内存存取DMA访问控制器只能对RAM的低16MB寻址。

∙在具有大容量RAM的现代32位计算机中,由于线性地址空间的限制,CPU不能直接访问所有的物理内存。

最后一种限制不仅存在于80x86,而存在于所有的体系结构中。

为了应对这两种限制,Linux把每个内存节点的物理内存划分为多个(通常为3个)管理区(zone)。

在80x86UMA体系结构中的管理区为:

∙ZONE_DMA,包含低于16MB的内存页框。

∙ZONE_NORMAL,包含高于16MB且低于896MB的内存页框。

∙ZONE_HIGHMEM,包含从896MB开始的内存页框。

对于ARM来说,ZONE_HIGHMEM被名为ZONE_MOVABLE的宏取代,而ZONE_DMA也不会仅限于最低的16MB,而可能对应所有的内存区域,此时只有内存节点ZONE_DMA有效,所以ZONE_DMA并不一定名副其实的用来作为DMA访问之用。

ZONE_DMA和ZONE_NORMAL区包含内存的"常规"页框,通过把它们线性的映射到线性地址的第4个GB(0xc0000000-0xcfffffff),内核就可以直接访问。

相反ZONE_HIGHMEM或者ZONE_MOVABLE区包含的内存页不能由内核直接访问,尽管它们也线性地映射到了线性地址空间的第4个GB。

每个内存管理区都有自己的描述符structzone。

它用来保存管理区的跟踪信息:

内存使用统计,空闲区,锁定区等。

include/linux/mmzone.h

structzone{

/*Fieldscommonlyaccessedbythepageallocator*/

unsignedlongpages_min,pages_low,pages_high;

unsignedlonglowmem_reserve[MAX_NR_ZONES];

structper_cpu_pagesetpageset[NR_CPUS];

structfree_areafree_area[MAX_ORDER];

ZONE_PADDING(_pad1_)

/*Fieldscommonlyaccessedbythepagereclaimscanner*/

spinlock_tlru_lock;

struct{

structlist_headlist;

unsignedlongnr_scan;

}lru[NR_LRU_LISTS];

unsignedlongrecent_rotated[2];

unsignedlongrecent_scanned[2];

unsignedlongpages_scanned;/*sincelastreclaim*/

unsignedlongflags;/*zoneflags,seebelow*/

/*Zonestatistics*/

atomic_long_tvm_stat[NR_VM_ZONE_STAT_ITEMS];

};

∙pages_min,记录管理区中空闲页的数目。

∙pages_low,回收页框使用的下届,同时也被管理区分配器作为阈值使用。

∙pages_high,回收页框使用的上届,同时也被管理区分配器作为阈值使用。

∙lowmem_reserve,指明在处理内存不足的临界情况下每个管理区必须保留的页框数目。

∙pageset,单一页框的特殊告诉缓存。

在申请内存时,会遇到两种情况:

如果有足够的空闲页可用,请求就会被立刻满足;否则,必须回收一些内存,并且将发出请求的内核控制路径阻塞,直到有内存被释放。

不过有些内存请求不能被阻塞。

这种情况发生在处理中断或在执行临界区内的代码时。

在这些情况下,一条内核控制路径应使用原子内存分配请求(GFP_ATOMIC)。

原子请求从不被阻塞;如果没有足够的空闲页,则仅仅是分配失败而已。

内核为了尽可能保证一个原子内存分配请求成功,它为原子内存分配请求保留了一个页框池,只有在内存不足时才使用。

保留内存的数量存放在min_free_kbytes变量中,单位为KB。

mm/page_alloc.c

intmin_free_kbytes=1024;

.....

/*min_free_kbytes=sqrt(lowmem_kbytes*16);*/

lowmem_kbytes=nr_free_buffer_pages()*(PAGE_SIZE>>10);

min_free_kbytes=int_sqrt(lowmem_kbytes*16);

min_free_kbytes由当前直接映射区的物理内存数量决定。

也即ZONE_DMA和ZONE_NORMAL内存管理区的可用页框数决定,这可以通过nr_free_buffer_pages获取。

尽管可以通过/proc/sys/vm/min_free_kbytes来修改该它的大小,但是min_free_kbytes的初始值范围必须是[128K,64M]。

管理区描述符中的pages_min成员存储了管理区内保留页框的数目。

这个字段与pages_low和pages_high字段一起被用在内存分配和回收算法中。

pages_low字段总是被设为pages_min的值的5/4,而pages_high则总是被设为pages_min的值的3/2。

这些值在模块快初始化module_init调用的init_per_zone_pages_min中被设置。

表 27. 页面分配控制

名称

大小

pages_min

min_free_kbytes>>(PAGE_SHIFT-10)

pages_low

pages_min*5/4

pages_high

pages_min*3/2

free_area_init_core中对管理区初始化的代码部分如下,后续章节将对该函数进一步分析。

zone->spanned_pages=size;

zone->present_pages=realsize;

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->prev_priority=DEF_PRIORITY;

zone_pcp_init(zone);

for_each_lru(l){

INIT_LIST_HEAD(&zone->lru[l].list);

zone->lru[l].nr_scan=0;

}

zone->recent_rotated[0]=0;

zone->recent_rotated[1]=0;

zone->recent_scanned[0]=0;

zone->recent_scanned[1]=0;

zap_zone_vm_stats(zone);

zone->flags=0;

13.2. page管理项

structpage{

unsignedlongflags;/*Atomicflags,somepossibly

*updatedasynchronously*/

atomic_t_count;/*Usagecount,seebelow.*/

union{

atomic_t_mapcount;/*Countofptesmappedinmms,

*toshowwhenpageismapped

*&limitreversemapsearches.

*/

struct{/*SLUB*/

u16inuse;

u16objects;

};

};

每一个物理页框都需要一个对应的page结构来进行管理:

记录分配状态,分配和回收,互斥以及同步操作。

对该结构成员的解释如下:

∙flag域存放当前页框的页标志,它存储了体系结构无关的状态,专门供Linux内核自身使用。

该标志可能的值定义在include/linux/page-flags.h中。

∙原子计数成员_count则指明了当前页框的引用计数,当该值为0时,就说明它没有被使用,此时在新分配内存时它就可以被使用。

内核代码应该通过page_count来访问它,而非直接访问。

∙原子计数成员_mapcount表示在页表中有多少页指向该页框。

在SLUB中它被inuse和objects代替。

include/linux/page-flags.h

enumpageflags{

PG_locked,/*Pageislocked.Don'ttouch.*/

PG_error,

PG_referenced,

PG_uptodate,

PG_dirty,

PG_lru,

PG_active,

......

__NR_PAGEFLAGS,

......

}

以上是页标志位的可能取值,通常不应该直接使用这些标志位,而应该内核预定义好的宏,它们在相同的头文件中被定义,但是它们是被间接定义的,也即通过##连字符来统一对它们进行定义。

#defineTESTPAGEFLAG(uname,lname)\

staticinlineintPage##uname(structpage*page)\

{returntest_bit(PG_##lname,&page->flags);}

......

TESTPAGEFLAG(Locked,locked)

PAGEFLAG(Error,error)

PAGEFLAG(Referenced,referenced)TESTCLEARFLAG(Referenced,referenced)

表 28. 页标志宏函数

扩展函数/宏

用途

TESTPAGEFLAG(uname,lname)

Page##uname

测试PG_##lname位

SETPAGEFLAG(uname,lname)

SetPage##uname

设置PG_##lname位

CLEARPAGEFLAG(uname,lname)[a]

ClearPage##uname

清除PG_##lname位

TESTSETFLAG(uname,lname)

TestSetPage##uname

测试并设置PG_##lname

TESTCLEARFLAG(uname,lname)

TestClearPage##uname

测试并清除PG_##lname

PAGEFLAG(uname,lname)[b]

TESTPAGEFLAG

SETPAGEFLAG

CLEARPAGEFLAG

当于同时扩展了三个宏,也即三个函数

PAGEFLAG_FALSE(uname)

Page##uname

永远返回0

TESTSCFLAG(uname,lname)

TESTSETFLAG

TESTCLEARFLAG

当于同时扩展了两个宏,也即两个函数

SETPAGEFLAG_NOOP(uname)

SetPage##uname

空操作

CLEARPAGEFLAG_NOOP(uname)

ClearPage##unam

空操作

__CLEARPAGEFLAG_NOOP(uname)

__ClearPage##uname

空操作

TESTCLEARFLAG_FALSE(uname)

TestClearPage##uname

永远返回0

[a]以上三个宏分别对应test_bit,set_bit和clear_bit,是原子操作,与它们对应的是有三个开头

为下划线的同名函数__SETPAGEFLAG等与它们相对应,但不是原子操作,这里不再列出。

[b]与此对应也有__PAGEFLAG的宏存在。

flags实际上为两部分:

标志区(FlagsArea)从最低处向上扩展到第__NR_PAGEFLAGS位;字段区(FieldsArea)则从最高位向低位扩展。

字段区用来实现管理区,内存节点和稀疏内存的映射。

|FIELD|...|FLAGS|

N-1^0

(__NR_PAGEFLAGS)

_count引用计数不应被直接引用,内核提供了一系列的内联函数来操作它,通常它们被定义在include/linux/mm.h中。

表 29. 页引用计数函数

函数名

用途

page_count

读取引用计数

get_page

引用计数加1

init_page_count

初始化引用计数为1

_mapcount与_count引用计数类似,不应被直接引用,内核提供了一系列的内联函数来操作它,它们也被定义在include/linux/mm.h中。

表 30. 页引用计数函数

函数名

用途

reset_page_mapcount

初始化引用计数为-1[a]

page_mapcount

读取引用计数并加1的值

page_mapped

该函数根据引用计数值是否大于等于0,判断该页框是否被映射。

[a]没有初始化为0是因为atomic_inc_and_test和atomic_add_negative的操作,对该引用计数的加减是由这两个函数完成的。

union{

struct{

unsignedlongprivate;/*Mapping-privateopaquedata:

*usuallyusedforbuffer_heads

*ifPagePrivateset;usedfor

*swp_entry_tifPageSwapCache;

*indicatesorderinthebuddy

*systemifPG_buddyisset.

*/

structaddress_space*mapping;/*Iflowbitclear,pointsto

*inodeaddress_space,orNULL.

*Ifpagemappedasanonymous

*memory,lowbitisset,and

*itpointstoanon_vmaobject:

*seePAGE_MAPPING_ANONbelow.

*/

};

#ifUSE_SPLIT_PTLOCKS

spinlock_tptl;

#endif

structkmem_cache*slab;/*SLUB:

Pointertoslab*/

structpage*first_page;/*Compoundtailpages*/

};

union{

pgoff_tindex;/*Ouroffsetwithinmapping.*/

void*freelist;

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 求职职场 > 面试

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1