对于这个函数,其实是很多宏定义调用的函数,其中alloc_bootmem_low_pages(x)是其中一个调用它的宏,大家可以认为这些宏只是把这个__alloc_bootmem实质性的函数进行了封装。#define alloc_bootmem_low_pages(x)相当于__alloc_bootmem((x), PAGE_SIZE, 0),就是从0地址开始的低端内存分配按页大小对齐的内存。好了,为了了解这个函数是怎么运行的,我们就从它的源头开始讲起吧。

 我们回到paging_init()这个函数,在上诉的文章的bootmem_init()函数返回之后,就接着回到paging_init()。

 memcpy(&meminfo, mi, sizeof(meminfo));//这里起到更新的作用。
 /*
  * allocate the zero page.  Note that we count on this going ok.
  */

 zero_page = alloc_bootmem_low_pages(PAGE_SIZE);//从0地址低端内存开始分配1页空闲内存,将该页的页帧位码表置1,并将这也得得内容全部清0.我们还是仔细点来分析这段代码吧。

 void * __init __alloc_bootmem (unsigned long size, unsigned long align, unsigned long goal)//size是申请的内存大小,align是决定的起始地址开始的内存对齐方式,goal是分配内存的起始地址,如果不行的话就从0地址开始。
{
 pg_data_t *pgdat = pgdat_list;//这个就是那个指向discontig_node_data[0]的链表。
 void *ptr;

 for_each_pgdat(pgdat)//这是一个宏定义:#define for_each_pgdat(pgdat)  for (pgdat = pgdat_list; pgdat; pgdat = pgdat->pgdat_next)。其实很简单,就是通过这个链表从0号内存node开始遍历所有的discontig_node_data[n]。
  if ((ptr = __alloc_bootmem_core(pgdat->bdata, size,align, goal)))//调用分配内存核心函数,我们在下面紧接着的位置介绍。
   return(ptr);

 /*
  * Whoops, we cannot satisfy the allocation request.
  */
 printk(KERN_ALERT "bootmem alloc of %lu bytes failed!/n", size);
 panic("Out of memory");
 return NULL;
}

 static void * __init __alloc_bootmem_core(struct bootmem_data *bdata, unsigned long size,unsigned long align, unsigned long goal)//这里和前面的__alloc_bootmem()不同的地方就是在第一项,对于这一项我们是比较熟悉的。

{

  unsigned long offset, remaining_size, areasize, preferred;
  unsigned long i, start = 0, incr, eidx;
  void *ret;

  if(!size) {

       printk("__alloc_bootmem_core(): zero-sized request/n");//如果要申请的内存是0的话,系统就会崩溃。

       BUG();

   }

  BUG_ON(align & (align-1));//如果不按2exp(n)对齐,则系统崩溃。

  eidx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT);//计算包括内存孔洞在内的,内存节点的内存空间的总页数。

  offset = 0;//预先设定从本内存node的起始地址开始分配。

  if (align &&(bdata->node_boot_start & (align - 1UL)) != 0)//如果align>0并且内存node的物理起始基址又不是按align对齐。

     offset = (align - (bdata->node_boot_start & (align - 1UL)));//求出内存node的物理起始基址到首个align的偏移字节数。

  offset >>= PAGE_SHIFT;//求出偏移页数。

  /*
  * We try to allocate bootmem pages above 'goal'
  * first, then we try to allocate lower pages.
  */

  if (goal && (goal >= bdata->node_boot_start) && ((goal >> PAGE_SHIFT) < bdata->node_low_pfn)){//如果分配的起始地址不再0开头处的话,但是在本内存node的物理地址范围内

               preferred = goal - bdata->node_boot_start;//计算这个偏移量。

               if (bdata->last_success >= preferred)

                      preferred = bdata->last_success;//如果上次成功分配的内存起始位置,大于这次申请的地址话,就把上次的地址赋给这次的地址。

   } else

        preferred = 0;

   preferred = ((preferred + align - 1) & ~(align - 1)) >> PAGE_SHIFT;//把分配起始位置相对于本内存node起始位置的偏移量转换成相对于本内存node的偏移页数。

   preferred += offset;//offset起始是本内存node按align对齐后的偏移页数,preferred是按align对齐后的分配内存位置相对于本内存node起始位置的偏移页数。

   areasize = (size+PAGE_SIZE-1)/PAGE_SIZE;//计算要分配的页数,不足一页按一页计算。

   incr = align >> PAGE_SHIFT ? : 1;//对齐页数至少为一页的大小,这里是求出一个对齐所占的页数。

restart_scan:

   for (i = preferred; i < eidx; i += incr) {//查看本内存node的所有内存页。

              unsigned long j;

              i = find_next_zero_bit(bdata->node_bootmem_map, eidx, i);//在本内存node的页帧位码表的第i位开始到第eidx位之间,寻找第一位为0的位号。

              i = ALIGN(i, incr);//#define ALIGN(x,a) (((x)+(a)-1)&~((a)-1))由宏定义可以看出,又是把位号进行对齐。是按页数进行对齐,可以看到这位是属于哪个对齐内存页的。其实这一部是非常重要的。我在这里举个例子:假设,我们是从0页开始进行检测的,我们这里分两种情况。第一:当我们一上来就发现0号页是空闲的,我们可以拿来分配,这个是靠上条语句得来的。接着我们在这里对齐的时候,我们的‘i’还是0,这样我们就初步定下来在这个内存页内分配空间。如果我们的incr是4的话,我们就会通过下面的for来为我们对后面的1,2,3页进行测试是否空闲。第二种情况:如果我们等到1,2,3三者其中一页才检测到时空闲的话,我们来到这条语句的时候,会发现'i'会增加一个档位,如果对于incr=4时,i=4,这样我们就发现,如果发现连续4个页的第一个页并非空闲的话,我们就放弃这个档位(4页)。

              if (test_bit(i, bdata->node_bootmem_map))continue;//其实这语句只对上面的第二种情况有实际意义的,为什么?自己体会一下吧。

              for (j = i + 1; j < i + areasize; ++j) {//这里就是我上面提到的for循环,就是为了确保整个档位(4页)是空闲的,只要有一点瑕疵,就马上进行档位调整,如果顺利的话,我们就可以申请到我们需要的空间。

                     if (j >= eidx)goto fail_block;

                     if (test_bit (j, bdata->node_bootmem_map))goto fail_block;

               }

               start = i;//如果可以找到连续的内存,就把相对的内存页号赋给start。

               goto found;

     fail_block:

                i = ALIGN(j, incr);//修改一下巡查页号,进行下一轮修改。

     }

     if (preferred > offset) {

              preferred = offset;

              goto restart_scan;

     }//这个if肯定会被执行的,peferred+=offset这条语句可以看出,我们不是从偏移首align开始检测是否有空闲空间的,这里我们从新scan时,是从offset的页号开始的,对于是PAGE_SIZE对齐格式,我们就会从0号开始!

      return NULL;    

found:

      bdata->last_success = start << PAGE_SHIFT;//把这次成功分配的空闲空间的起始虚拟地址,这是地址是按页对齐的。

      BUG_ON(start >= eidx);//如果大于得花,说明已经超过了指定的低端内存页数,这样系统会崩溃的。

      if (align < PAGE_SIZE &&bdata->last_offset && bdata->last_pos+1 == start) {//先要清楚bdata->last_offset是上次分配成功空间的结束地址相对于其页的页内偏移量,如果结束地址刚好是按页对齐的话,last_offset=0。可以看出if里面的判断是说明对于小于PAGE_SIZE对齐方式的,如果上次成功分配空间的结束位置的所在页正好在start的上一页。

     offset = (bdata->last_offset+align-1) & ~(align-1);//按align格式来对齐偏移量,对于对齐后是一页的大小时,就不能用这些剩余空间来为本次分配内存,只能用上面找到的本内存node的第start页开始。

     BUG_ON(offset > PAGE_SIZE);//如果大于PAGE_SIZE时,就出现系统崩溃。

     remaining_size = PAGE_SIZE-offset;//计算上次分配的内存最后页中能用于本次分配的空闲内存大小。

    if (size < remaining_size) {//如果本次分配size小于剩余空间的话,就执行以下语句。

        areasize = 0;//由于剩余的空间够本次的分配,则这次分配不用新的一页来分配了。

        bdata->last_offset = offset+size;//重新更新最后地址相对于本页的偏移量。

        ret = phys_to_virt(bdata->last_pos*PAGE_SIZE + offset +bdata->node_boot_start);//bdata->last_pos是上次内存申请结束地址所在的页号(由于页号是从0号开始的),这样很明显括号里面求得的就是本次申请内存的起始物理地址,最后通过phys_to_virt宏定义求出虚拟地址。

    } else {//如果上次分配剩下的空间足以为本次分配所用。

         remaining_size = size - remaining_size;//计算还要多大的新的空闲内存空间。

         areasize = (remaining_size+PAGE_SIZE-1)/PAGE_SIZE;//看看需要多少页,不够一页也要算用一页。

         ret = phys_to_virt(bdata->last_pos*PAGE_SIZE + offset +bdata->node_boot_start);//同上。

         bdata->last_pos = start+areasize-1;//从新统计本次申请空闲空间结束位置的所在页号。

         bdata->last_offset = remaining_size;//重新更新。

    }

    bdata->last_offset &= ~PAGE_MASK;//最后把last_offset转换成页内偏移量。

    }else{如果对其值大于等于页大小,或者上次分配的内存的物理结束地址是按页对齐的,或是上次分配的内存的结束地址不在本次分配的起始空闲内存页start的前一页内。

         bdata->last_pos = start + areasize - 1;

         bdata->last_offset = size & ~PAGE_MASK;

         ret = phys_to_virt(start * PAGE_SIZE + bdata->node_boot_start);

     }//else里面的三条语句就不多说了。

     for (i = start; i < start+areasize; i++)

              if (unlikely(test_and_set_bit(i, bdata->node_bootmem_map)))BUG();//对本次的申请的空间的每页对应的页帧位码表中的每一位都置1,如果本身就是1的话,系统崩溃。

     memset(ret, 0, size);//对申请的这段空间进行清零。

     return ret;//返回申请到的虚拟地址。

 }

好的,这样就过了一遍linux的内存分配的核心函数__alloc_bootmem_core。我们下一篇文章就会开始介绍有关内存管理的释放函数。

        

 

         

   

 

 

       

             

 

  

Logo

更多推荐