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()激活内核抢断。