注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

蓝瑟

人生苦短,及时行乐

 
 
 

日志

 
 

ucLinux的存储管理  

2010-01-16 19:24:42|  分类: 默认分类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

bfin-uclinux内核的内存管理主要涉及三种算法,bootmem,buddy和slab。其中bootmem在内核启动的初期发挥作用,它将系统的可用内存以页的形式组织起来。然后Buddy算法接管这些页,将之分成不同大小的页块,这个工作完成后,bootmem退出舞台且不再出现。而slab算法则从buddy中取一些页面出来进行小对象的分配,内核的实际对象分配都是使用它来完成的。在这三种算法的配合下,内核得以高效利用内存。

bootmem:在内核启动完成前提供一种简单的内存管理策略,它用于系统启动的时候,在buddy等内存分配系统初始化完成以后就不再使用。其基本思想是将整个内存区域分成许多页,每页大小为4K,在分配时以页为单位,分配方法是从低向高找,直到找到一块或者连续多块符合要求的内存区域。

bootmem使用内核代码结束后的第一个页面来保存所有其他SDRAM的使用情况,bootm试图使用一个二进制位来表示一个页面是否被使用,0表示空闲,1表示被使用。对于64M的内存,页面大小是4K,则一共有64*1024/4 = 16Kb = 2KB 的空间

Memory初始化

setup_arch--->memory_setup

设置内存映像像如下的结构:

 *  [_rambase   ,    _ramstart     ]:  kernel image
 *  [memory_start, memory_end]:  dynamic memory managed by kernel
 *  [memory_end  ,  _ramend     ]:  reserved memory
 *  [memory_mtd_start(memory_end),     memory_mtd_start + mtd_size]:  rootfs (if any)
 *  [_ramend - DMA_UNCACHED_REGION,    _ramend]:   uncached DMA region
 *  [_ramend, physical_mem_end]: memory not managed by kernel

函数完成以后会将表示内存的几个变量赋值,其中memory_start是_ramstart的page对齐地址都表示内核代码后面的第一个可用地址;_rambase表示内核的起始地址;memory_end指向可用物理内存的最高位置;_ramend是可以通过用户配置来修改。

bootmem初始化

setup_arch--->setup_bootmem_allocator--->add_memory_region
将memory_start到memory_end之间内核可控制的可用物理空间加到bfin_memmap中,使其成为可用空间。bfin_memmap是用于ucLinux启动时候的内存使用情况,记录着可用空间的数目,每个可用空间段的起始地址,大小,类型。

setup_arch--->setup_bootmem_allocator--->sanitize_memmap
检查并移除bfin_memmap中的地址重叠的部分,调整bfin_memmap中的顺序,使其每个可用间段的起始地址按从小到大的顺序排列起来。

setup_arch--->setup_bootmem_allocator--->find_min_max_pfn
根据前面已经排好序的bfin_memmap找到可用空间的最高地址页号和最低地址页号分别记录为全局变量max_pfn和min_low_pfn

setup_arch--->setup_bootmem_allocator--->init_bootmem_node
start_pfn = PAGE_OFFSET >> PAGE_SHIFT;
end_pfn = memory_end >> PAGE_SHIFT;
bootmap_size = init_bootmem_node(NODE_DATA(0),  memory_start >> PAGE_SHIFT,   start_pfn,  end_pfn);
其中 PAGE_OFFSET = 0,PAGE_SHIFT = 12,NODE_DATA(0)被定义为返回一个全局的pglist_data结构的地址,而且内核中也只会存在一个的变量contig_page_data。(注:在Linux系统中存储区域被分为两个管理区[zone];ZONE_DMA和 ZONE_NORMAL,在NUMA系统中分配若干连续的物理页面要求分配中同样的“质地”物理内存中[node],所以由于node的出现,管理区不再是高层机构,而是每个存储节点[node]有两个管理区,从而又增加了一个数据结构pglist_data用来管理每个节点的存储区域)
ucLinux的存储管理 - 蓝瑟 - 蓝瑟

在BF561平台中只存在一个节点,所以只有一个pglist_data结构变量即全局变量contig_page_data,而且也只会存在一个管理区即低位的管理区ZONE_DMA。在pglist_data结构中有两个变量值得注意:
struct zone node_zones[MAX_NR_ZONES] 用来存储本节点所有管理区的信息
struct zonelist node_zonelists[MAX_ZONELISTS] 用来存储所有节点的管理区集合
在分配内存的时候先在本节点的管理区中分配,如找不到连续足够的页面,再在其他的节点的管理区中分配,所以会有上面的第二个变量。

setup_arch--->setup_bootmem_allocator--->init_bootmem_node--->init_bootmem_core
初始化contig_page_data变量中的成员struct bootmem_data *bdata,而bdata结构中的成员node_bootmem_map用于记录bootmem可用页面的map数据,本函数将node_bootmem_map放在内核以后的第一个可用页面,并为每一页准备一位二进制来表示使用与否,node_bootmem_map区域全部赋值为1。

setup_arch--->setup_bootmem_allocator--->free_bootmem(unsigned long addr, unsigned long size)
标志一组连续的页面为可用,通过对bfin_memmap所有可用区域的遍历,将所用可用页面标志为可用(将contig_page_data ->bdata->node_bootmem_map中对应为清零)特别的会对memory_start到memory_end区域进行处理

setup_arch--->setup_bootmem_allocator--->reserve_bootmem(unsigned long addr, unsigned long size,int flags)
将内存区域中memory_start之前的kernel image区域中的所有页面标志为保留,即在node_bootmem_map中页面对应的相应二进制位置1。

存储区域管理

setup_arch--->paging_init
在函数中初始化zones_size[MAX_NR_ZONES],该数组表示节点中各个管理区的页面数量,其中ZONE_DMA的管理区为60M,所以zones_size[ZONE_DMA] = 60*1024*1024 / 4*1024 = 0x3C00,ZONE_DMA被定义为一个enum值,其值为0,而在其中还有一个值ZONE_NORMAL被定义为1,此时的zones_size[ZONE_DMA] = 0;所以全部的可用空间都映射到一个节点的一个管理区里面了。

setup_arch--->paging_init--->free_area_init(zones_size)---->free_area_init_node(0, zones_size,   __pa(PAGE_OFFSET) >> PAGE_SHIFT, NULL);
 pg_data_t *pgdat = NODE_DATA(nid);   得到节点0的全局pg_data_t变量即唯一的contig_page_data用来管理节点全局内存
 
pgdat->node_id = nid; 初始化该节点内存管理结构的节点编号,此时的nid=0
 pgdat->node_start_pfn = node_start_pfn;初始化管理结构的起始的页面编号,此时因PAGE_OFFSET=0,所以为0

此函数只初始化了节点0的相关信息,从这里我们也可以推断系统中只有一个节点。

setup_arch--->paging_init--->free_area_init(zones_size)---->free_area_init_node--->calculate_node_totalpages
通过继续调用zone_spanned_pages_in_node和zone_absent_pages_in_node来得到管理结构pgdat的总页数(包括空洞页面)和真实页数(不包括空洞页面),即初始化了两个字段pgdat->node_spanned_pages和node_present_pages

setup_arch--->paging_init--->free_area_init(zones_size)---->free_area_init_node--->alloc_node_mem_map(pgdat)
功能很简单:为pgdat->node_mem_map这个page指针分配存储空间,空间的大小等于sizeof( struct page) * page的数量。在函数最后将全局变量mem_map赋值为节点0的node_mem_map,也就是说全局的mem_map和全局pgdat->node_mem_map相同,都指向同一个页面的page数组(mem_map指向page数组的第一个元素,这块内存空间使用bootmem进行分配,但与其他使用bootmem不同的是,该内存不会被回收)

setup_arch--->paging_init--->free_area_init(zones_size)---->free_area_init_node--->free_area_init_core
函数功能:初始化该节点的zone结构即pgdat->node_zones结构数组,数组的每个元素都是一个zone结构变量,其实在BF561中只会用到两个zone,一个是ZONE_DMA大小为0x3C00,另一个是ZONE_NORMAL大小是0。标志所有页面为reserved,标志所有内存链表为空,将内存的bitmap全部清零,表示未使用。在这个函数中首先循环遍历所有的zone,从中取得每个zone结构的页面数量(除去了page数组占用,为DMA保留的页面和空洞),初始化每个zone结构中的的页面总数和可用页面总数(页面总数减去page数组所占用的页面)并将所有的可用的页面数量值来初始化全局变量nr_kernel_pages和nr_all_pages。

setup_arch--->paging_init--->free_area_init(zones_size)---->free_area_init_node--->free_area_init_core--->zone_pcp_init(zone)
这个函数通过对每个cpu调用setup_pageset来初始化zone结构中的struct per_cpu_pageset *pageset[NR_CPUS]成员。首先将pageset[i]全部清零,然后初始化pageset结构里面的又一结构成员struct per_cpu_pages pcp,在pcp中记录了page链表,该链表中的page数量以及bacth值,初始的时候将链表置为空,page数量置为0,设置适当的batch值,batch值是Buddy系统增加和移除时一次移动的page数量。

setup_arch--->paging_init--->free_area_init(zones_size)---->free_area_init_node--->free_area_init_core--->init_currently_empty_zone
本函数通过调用zone_wait_table_init来初始化zone结构中的三个
 wait_queue_head_t * wait_table;
 unsigned long  wait_table_hash_nr_entries;
 unsigned long  wait_table_bits;
这些成员的用途是当有多个任务需要访问同一个区域(不是为每一页都设置一个wait table)的时候,如果该页面暂时不可用,则可以将该页面经过hash加入到这个zone的wait_table(结构里有个成员task_list)里面,当页面变得可用的时候再将这些等待的任务全部唤醒。这可能存在就是多个页面会有同样的hash值,也就是说等待不同页面的任务可能睡眠在同一个hash值上面,而其中一个页面可用的时候又必须唤醒整个队列中的任务,所以任务在唤醒的时候一定要再次检查时候是自己等待的页面是否已经变得可用了。
再通过调用zone_init_free_lists(zone)来为Buddy系统初始化内存链表。在zone结构中有一个结构变量struct free_area free_area[MAX_ORDER],MAX_ORDER被定义为11,即在Buddy系统中存在11种大小的链表,每种链表分别包含1,2,4,8,16,32,64,128,256,512,1024个连续的内存页面。而结构struct free_area记录了该种链表中的空闲的单位个数个空闲空间的链表,在此函数初始化的时候,将该链表置为空,将空闲的单位个数置为0。

setup_arch--->paging_init--->free_area_init(zones_size)---->free_area_init_node--->free_area_init_core--->memmap_init
这个函数的功能是为内存的每个页面page结构进行初始化

至此,paging_init函数调用完成了,将系统的zone,page数组初始化完成。随后setup_arch中还会调用关于中断向量和cache的初始化,这里略过。

start-kernel在setup_arch调用完成以后,随后调用的关于内存方面的函数是build_all_zonelists,这个函数是完成pglist_data(全局变量contig_page_data)中结构成员struct zonelist node_zonelists[MAX_ZONELISTS]的初始化。在NUMA系统中,MAX_ZONELISTS被设置为2,

build_all_zonelists--->set_zonelist_order
函数功能是设置zonelists中的排列顺序所依据的原则:
 *  ZONELIST_ORDER_DEFAULT   0 = automatic detection of better ordering.
 *  ZONELIST_ORDER_NODE        1 = order by ([node] distance, -zonetype)
 *  ZONELIST_ORDER_ZONE         2 = order by (-zonetype, [node] distance)
current_zonelist_order = ZONELIST_ORDER_ZONE; 将全局变量current_zonelist_order设置成为按照管理区排列。zonelist中记录了MAX_ZONES_PER_ZONELIST = MAX_NUMNODES * MAX_NR_ZONES个node的引用,也就是说zonelist中记录了系统中所有zone的情况,根据起排列顺序进行内存的分配,所以才需要上面的排序原则。在BF561中MAX_NUMNODES = 1,MA_NR_ZONES=2也就是说一个zonelist包含了两个zone,如果在多节点的系统中,一个zonelist就可能包含更多的zone。

build_all_zonelists--->__build_all_zonelists--->build_zonelists(pgdat)
通过几次调用build_zonelists_node函数将系统中的所有zone都加入到本pgdat->zonelists中。在build_zonelists_node中首先根据传进来的某个节点的pgdat变量得到该节点的zone数组,对每一个zone的大小进行检查,如果大小不为0的话就加入到本节点的zonelist当中。在加入了本节点的所有zone后,在按照node id的大小顺次加入其他node的所有zone本节点的zonelist当中。build_zonelists(pgdat)执行完成以后,系统中的所有节点信息都计入到了某节点的pgdat结构当中,而在__build_all_zonelists函数中对每个节点都调用了一次build_zonelist,所以,这里就使得所有节点的zonelist都得到初始化,而其值都是系统中所有节点的所有管理区的引用。

build_all_zonelists--->__build_all_zonelists--->build_zonelist_cache
这个函数的功能很简单就是将zonelist结构中的zlcache_ptr指针为空,没有其他的功能。

build_all_zonelists---->nr_free_pagecache_pages---->nr_free_zone_pages(gfp_zone(GFP_HIGHUSER_MOVABLE))
函数的返回值用来初始化变量全局变量vm_total_pages(由VM 控制的所有页面数量)。gfp_zone(GFP_HIGHUSER_MOVABLE)返回GFP_HIGHUSER_MOVABLE所在的内存区域,在BF561中只有一个zone,即ZONE_DMA,所以这个调用是返回ZONE_DMA(0),nr_free_zone_pages(0)返回系统中所有可用的页面数量。所以全局变量vm_total_pages便是系统中所有可用的页面。

  评论这张
 
阅读(593)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017