DSP

三读bootmem

2019-07-13 17:03发布

  快乐虾 http://blog.csdn.net/lights_joy/ lights@hb165.com      本文适用于 ADI bf561 DSP 优视BF561EVB开发板 uclinux-2008r1-rc8 (移植到vdsp5) Visual DSP++ 5.0    欢迎转载,但请保留作者信息     bootmem是内核中使用的一种较简单的内存分配策略,它用于在系统启动时使用,在buddy等内存分配系统初始化完成后将不再使用。其基本思想是将SDRAM的可用存储空间分成许多页,每页的大小为4K,在分配时以页为单位分配,分配方法是从低往高找直到找到一块或连续多块满足大小要求的空闲页面为止。 还必须记住:内核支持所谓的NUMA结构,它将整个系统的存储空间分成几个不连续的节点,每个节点用一个pglist_data进行描述,再将这些节点放在一个链表中,但在BF561系统内核中实际只有一个节点。在此节点下,内核又将之分为几个管理区(zone),对于BF561而言,DMA将可以访问整个内存区域,所以实际只使用了一个内存区,ZONE_DMA,在这个内存区中,包含了所有的内存范围。

1.1.1   相关数据结构

1.1.1.1             bootmem_data

这个结构体的定义在include/linux/bootmem.h /*  * node_bootmem_map is a map pointer - the bits represent all physical  * memory pages (including holes) on the node.  */ typedef struct bootmem_data {      unsigned long node_boot_start;      unsigned long node_low_pfn;      void *node_bootmem_map; /* 下面三个值将用于内存的快速分配 */      unsigned long last_offset;      unsigned long last_pos;      unsigned long last_success; /* Previous allocation point.  To speed                         * up searching */      struct list_head list; } bootmem_data_t; l        node_boot_start 取值为0 l        node_low_pfn 指向SDRAM中最后一个页的页序号。 l        node_bootmem_map node_bootmem_map初始化为内核结束后第一个页的页面地址,而不是页号。 bootmem试图用一个二进制位表示一页是否占用,如果空闲则该位为0,如果已经使用则该位为1对于64MSDRAM来讲,其页面大小为4K,因此其总共的页数为0x3fff(16K),所需要的字节数为0x800 bootmem用内核代码结束后的第1页来保存所有的SDRAM页面使用情况,即node_bootmem_map指向的页面。

1.1.2   相关全局变量

1.1.2.1             bdata_list

在内核中有一个bdata_list的链表中,它的定义在bootmem.c中: static LIST_HEAD(bdata_list); 内核将所有的bootmem_data放在这个链表里,但是对于BF561来讲,bdata_list的链表将只有一个元素!其定义在mm/page_alloc.c中: static bootmem_data_t contig_bootmem_data;

1.1.3   初始化

1.1.3.1             setup_arch

内核启动时,将调用setup_arch函数 (arch/blackfin/kernel/setup.c)。在此函数中与bootmem相关的代码有: ……………..        /*       * give all the memory to the bootmap allocator,  tell it to put the       * boot mem_map at the start of memory       */      bootmap_size = init_bootmem_node(NODE_DATA(0), memory_start >> PAGE_SHIFT, /* map goes here */                         PAGE_OFFSET >> PAGE_SHIFT,                         memory_end >> PAGE_SHIFT);      /*       * free the usable memory,  we have to make sure we do not free       * the bootmem bitmap so we then reserve it after freeing it :-)       */      free_bootmem(memory_start, memory_end - memory_start);        reserve_bootmem(memory_start, bootmap_size); 此时,内存区域管理的初始化函数paging_init尚未调用,由此可知bootmem的作用是在内核启动完成前提供一种简单的内存管理策略。 其中PAGE_OFFSET的定义位于include/asm/page.h #define PAGE_OFFSET         (PAGE_OFFSET_RAW) PAGE_OFFSET的定义位于include/asm/page_offset.h #ifdef CONFIG_BLACKFIN #define PAGE_OFFSET_RAW     0x00000000 #endif

1.1.3.2             init_bootmem_node

setup_arch函数中调用了init_bootmem_node,这个函数的实现在mm/bootmem.c中:   /*  * Called once to set up the allocator itself.  */ static unsigned long __init init_bootmem_core(pg_data_t *pgdat,      unsigned long mapstart, unsigned long start, unsigned long end) {      bootmem_data_t *bdata = pgdat->bdata;      unsigned long mapsize;        bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart));      bdata->node_boot_start = PFN_PHYS(start);      bdata->node_low_pfn = end;      link_bootmem(bdata);        /*       * Initially all pages are reserved - setup_arch() has to       * register free RAM areas explicitly.       */      mapsize = get_mapsize(bdata);      memset(bdata->node_bootmem_map, 0xff, mapsize);        return mapsize; }   unsigned long __init init_bootmem_node(pg_data_t *pgdat, unsigned long freepfn,                    unsigned long startpfn, unsigned long endpfn) {      return init_bootmem_core(pgdat, freepfn, startpfn, endpfn); } 当参数传递到init_bootmem_core时,各个参数的对应值为: mapstart取的是memory_start >> PAGE_SHIFT,即内核代码结束后的第一个页序号 start取的是PAGE_OFFSET >> PAGE_SHIFT,即0 end取的是memory_end >> PAGE_SHIFT,即SDRAM的最后一个页号。 请记住,pgdat指向的是全局唯一的一个变量contig_page_data,它的bdata成员指向全局唯一的一个变量contig_bootmem_data。从这个初始化函数可以大致看出pg_data_t结构体中几个成员的意义。 l        node_bootmem_map      bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart)); 在这行语句中,mapstart是内核代码结束后的第一个页序号,PFN_PHYS定义为: #define PFN_PHYS(x)    ((x) << PAGE_SHIFT) phys_to_virt则定义为: #define phys_to_virt(vaddr) ((void *) (vaddr)) 因此node_bootmem_map保存了内核结束后第一个页的页面地址,而不是页号 l        node_boot_start      bdata->node_boot_start = PFN_PHYS(start); 取值为0 l        node_low_pfn      bdata->node_low_pfn = end; 指向SDRAM中最后一个页的页序号。 从最后两行可以看出,初始化时将所有的页面都标记为已经使用。

1.1.3.3             link_bootmem

init_bootmem_core函数中调用了link_bootmem,这个函数实现为:   /*  * link bdata in order  */ static void __init link_bootmem(bootmem_data_t *bdata) {      bootmem_data_t *ent;        if (list_empty(&bdata_list)) {          list_add(&bdata->list, &bdata_list);          return;      }      /* insert in order */      list_for_each_entry(ent, &bdata_list, list) {          if (bdata->node_boot_start < ent->node_boot_start) {               list_add_tail(&bdata->list, &ent->list);               return;          }      }      list_add_tail(&bdata->list, &bdata_list); } 它的作用就是将bdata放到一个叫bdata_list的链表中,bdata_list的定义在bootmem.c中: static LIST_HEAD(bdata_list); 对于BF561来讲,这个函数将只调用一次,用红 {MOD}标出的代码将不会执行。因此bdata_list的链表也将只有一个元素!

1.1.3.4             get_mapsize

init_bootmem_core函数中调用了get_mapsize实现为:   /*  * Given an initialised bdata, it returns the size of the boot bitmap  */ static unsigned long __init get_mapsize(bootmem_data_t *bdata) {      unsigned long mapsize;