1.页
     内核把物理页作为内存管理的基本单位。
 
     内核用struct page 结构表示系统中的每个物理页,定位于<linux/mm_types.h>
 
 1      struct page{
 2          unsigned long  flages;         //存放页的状态,可同时支持32种不同的状态
 3          atomic_t          _count;         //引用计数,为-1,则内核没有引用
 4          atomic_t          _mapcount;
 5          unsigned long  private;
 6          struct address_space *mapping;          //该页所在地址空间描述结构指针
 7          pgoff_t               index;
 8          struct list_head  lru;
 9          void                    *virtual;     //页的虚拟地址
10      }
 
     flages :用来存放页的状态,这写状态包括页是不是脏的,是不是被锁在内存中,可表示32种状态。
     _count :存在页的引用计数,也就是这一页被引用了多少次。计数值为-1,表示内核没有引用
                 通过调用page_count(struct page)来检查该域,返回0表示页空闲,返回正整数,则在使用
   
 
2.区
     由于硬件的限制,内核不能对所有的页一视同仁,有些页位于内存中特定的物理地址上,所以不能将其用于其它的任务;由于这些设置,所以内核把这些页划分为不同的区。
 
     硬件存在缺陷而引起的内存寻址问题:
              a) 一些硬件只能用某些特定的内存地址来执行DMA
              b) 一些体系结构的内存的物理寻址范围比虚拟寻址范围大的多,这就由一些内存不能永久的映射到内核空间上。
 
     由于存在这些制约条件,linux使用了四个区:
              
              .ZONE_DMA         ---用来执行DMA操作 ( <16M )
              .ZONE_DMA32     ---和第一种类似,不同的就是这写页面只能用于32位设备访问
              .ZONE_NORMAL  ---正常映射的页 ( 16~896MB)
              .ZONE_HIGHEM   ---高端内存,其中的页不能永久的映射在内核地址空间( >896MB)

3.获得页
        内核提供了一种请求内存的底层机制,应提供了对它进行访问的几个接口,所有的接口都以页为单位分配内存,定义与<linux/gfp.h> 
       
 
 1        struct page * alloc_pages(gfp_t gfl_mask,unsigned int order)
 2   //该函数分配2的order次方个连续的物理页,并返回一个指针,该指针指向第一页的page结构体,如果出差就返回NULL。
 3 
 4          void * page_address(struct page *page)
 5         // 该函数返回一个指针,指向给定物理页当前所在的逻辑地址。
 6  
 7          unsigned long __get_free_pages(gft_t gft_mask, unsigned int order)
 8          这与alloc_pages()作用相同,不同返回所请求的第一页的逻辑地址。
 9  
10          如果只需要一页,可以使用下面两个封装好的函数:
11          
12          struct page * alloc_page(gfp_t gfp_mask)
13          unsigned long __get_free_page(gfp_t gfp_mask)
14          这两个函数与其兄弟函数工作方式相同,只不过传递给order的值为0
15  
16          unsigned long get_zeroed_page( unsigned int gfp_mask)
17          与__get_free_page()工作方式相同,只是把分配好的页都填充成了0

 



4.释放页
         释放页时要谨慎,只能释放你自己的页。传递了错误的struct page或地址,用了错误的order值,都可能导致系统奔溃。
 
1          void __free_pages(struct page * page, unsigned int order)
2          void free_pages(unsigned long addr, unsigned int order)
3          void free_page(unsigned long addr)     
 
    // 例子:获得8个页
1            unsigned long page;
2             page = __get_free_pages(GFP_KERNEL,3) ;   //GFP_KERNEL参数是gfp_mask标志的一个例子
3             if(!page){
4                    /* 没有足够的内存,返回错误 */
5                   return -ENOMEN;
6              }
7             // 释放8个页
8               free_pages(page,3);     //已经被释放,就应该再访问它

 


              
          在__get_free_pages()之后需要注意进行错误检查,防止内核分配内存失败。
 
     kmalloc()函数与用户空间的malloc()函数非常的类似,只多了一个flags参数,声明在<linux/slab.h>中
    
     void * kmalloc(size_t size,gfp_t flags);
     这个函数返回一个指向内存块的指针,内存至少为size大小,分配的内存区在物理上是连续的。在出错时返回NULL
  例:
     struct dog *p;
     p=kmalloc(sizeof(struct dog),GFP_KERNEL);
     if(!p)
         /* 处理错误... */
     kfree(p)    
 
     kfree()声明在<linux/slab.h>中:
 
     void kfree(const void *ptr)
 
     kfree()释放由kmalloc()分配的内存块    

 
5.gfp_mask标志
 
     这些标志分为三类:行为修饰符,区修饰符和类型
 
     行为修饰符:表示内核应该如何分配所需的内存,在某些特殊情况下,只能使用某些特定的方法分配内存,入中断处理程序要求内核分配内存的过程中不能睡眠。
 
     区修饰符:表示内存应当从何处分配,上面已经直到内核把物理内存分为多个区,每个区用于不同的目的。
 
     类型:组合了行为修饰符和区修饰符,将各种可能用到的组合归纳为不同的类型,简化修饰符的使用,这样只需指定一个类型标志就可以了。
 
标志 描述
GFP_ATOMIC 用在中断处理程序,下半部、持有自旋锁以及其它不能睡眠的地址
GFP_NOWAIT 与GFP_ATOMIC类似,但调用不会退给紧急内存池,添加内存分配失败的可能性
GFP_NOIO 这中分配可以阻塞,但不会启动磁盘I/O
GFP_NOFS 这种分配必要时可以阻塞,也会启动磁盘I/O,但不会启动文件系统操作
GFP_KERNEL 一种常规分配方式,可能会阻塞。这个标志在睡眠安全时用在进程上下文中。这个标志应当是首选项
GFP_USER 一种常规分配方式,可能会阻塞。用于为用户空间进程分配内存
GFP_HIGHUSER 从ZONE_HIGHMEM进行分配,可能会阻塞,用于用户空间进程分配内存
GFP_DMA

6.vmalloc()
     vmalloc()函数的工作方式类似于kmalloc(),不同在与前者分配的内存虚拟地址是连续的,而物理地址则无须连续。这和用户空间分配函数的工作方式相同,malloc().
 
     vmalloc()函数声明在<linux/vmalloc.h>中,定义在<mm/vmalloc.c>中
 
     void vmalloc(unsigned long size)
     该函数返回一个指针,指向逻辑上连续的一块内存区,其大小为size,发生错误返回NULL,函数可能睡眠
 
     void vfree(const void *addr)
      该函数释放从addr开始的内存块,addr为前面vmalloc()分配的内存块地址。
 
     例:
1      char *buf;    
2      buf = vmalloc(16 * PAGE_SIZE);    //分配内存,大小至少为16 * PAGE_SIZE
3      if(!buf)
4          /* 错误,不能分配内存 */
5      vfree(buf);                                       //释放内存

 


7.每个CPU的分配
    
     支持SMP的现代操作系统使用每个CPU上的数据,对于给定的处理器其数据是唯一的。一般来说,每个CPU的数据存放在一个数组中。数组中的每一项对应着系统上一个存在的处理器。按当前处理器号确定这个数组的每一个元素。
    
1      unsigned long my_percpu[NR_CPUS];
2      int cpu;            
3      cpu= get_cpu();    /* 获得当前CPU,并禁止内核抢断*/
4      my_percpu[cpu]++;
5      printk("my_percpu on cpu=%d is %lu\n",cpu,my_percpu[cpu]);
6      put_cpu();          /* 激活内核抢断 */
 
     上面的代码被没有lock,这是因为所操作的数据对当前处理器来说是唯一的,不存在并发访问的问题。
 
     内核抢占成为了唯一需要关注的问题:
         1)如果代码被其它处理器抢占并重新调度,那么这时CPU变量就会无效,因为它指向了一个错误的处理器。
         2)如果另一个任务抢占了你的代码,那么就由可能并发访问变量my_percpu.
     所以需要使用get_cpu()禁止内核抢断,最后使用put_cpu()激活内核抢断。
         
 
 
posted on 2014-10-28 10:27  main_ryan  阅读(418)  评论(0编辑  收藏  举报