Linux内核中内存相关的操作函数
1、kmalloc()/kfree()
static __always_inline void *kmalloc(size_t size, gfp_tflags)
内核空间申请指定⼤⼩的内存区域,返回内核空间虚拟地址。在函数实现中,如果申请的内存空间较⼤的话,会从buddy系统申请若⼲内存页⾯,如果申请的内存空间⼤⼩较⼩的话,会从slab系统中申请内存空间。
gfp_t flags 的选项较多。参考内核⽂件gfp.h.
在函数kmalloc()实现中,如果申请的空间较⼩,会根据申请空间的⼤⼩从slab中获取;如果申请的空间较⼤,如超过⼀个页⾯,会直接从buddy系统中获取。
2、vmalloc()/vfree()
void *vmalloc(unsigned long size)
函数作⽤:从⾼端(如果存在,优先从⾼端)申请内存页⾯,并把申请的内存页⾯映射到内核的动态映射
空间。vmalloc()函数的功能和alloc_pages(_GFP_HIGHMEM)+kmap()的功能相似,只所以说是相似⽽不是相同,原因在于⽤vmalloc()申请的物理内存页⾯映射到内核的动态映射区(见下图),并且,⽤vmalloc()申请的页⾯的物理地址可能是不连续的。⽽alloc_pages(_GFP_HIGHMEM)+kmap()申请的页⾯的物理地址是连续的,被映射到内核的KMAP区。
vmalloc分配的地址则限于vmalloc_start与vmalloc_end之间。每⼀块vmalloc分配的内核虚拟内存都对应⼀个vm_struct结构体(可别和vm_area_struct搞混,那可是进程虚拟内存区域的结构),不同的内核虚拟地址被4k⼤⼩的空闲区间隔,以防⽌越界--见下图)。与进程虚拟地址的特性⼀样,这些虚拟地址与物理内存没有简单的位移关系,必须通过内核页表才可转换为物理地址或物理页。它们有可能尚未被映射,在发⽣缺页时才真正分配物理页⾯。
如果内存紧张,连续区域⽆法满⾜,调⽤vmalloc分配是必须的,因为它可以将物理不连续的空间组合后分配,所以更能满⾜分配要求。vmalloc可以映射⾼端页框,也可以映射底端页框。vmalloc的作⽤只是为了提供逻辑上连续的地址…
注意:在申请页⾯时,如果注明_GFP_HIGHMEM,即从⾼端申请。则实际是优先从⾼端内存申请,顺序为(分配顺序是
HIGH,NORMAL, DMA )。
3、alloc_pages()/free_pages()
内核空间申请指定个数的内存页,内存页数必须是2^order个页。
alloc_pages(gfp_mask, order) 中,gfp_mask 是flag标志,其中可以为__GFP_DMA、_GFP_HIGHMEM 分别对应DMA和⾼端内存。
注:该函数基于buddy系统申请内存,申请的内存空间⼤⼩为2^order个内存页⾯。
参见《linux内核之内存管理。doc》
通过函数alloc_pages()申请的内存,需要使⽤kmap()函数分配内核的虚拟地址。
4、__get_free_pages()/__free_pages()
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned intorder)
作⽤相当于alloc_pages(NORMAL)+kmap(),但不能申请⾼端内存页⾯。
__get_free_page()只申请⼀个页⾯。
5、kmap()/kunmap()
返回指定页⾯对应内核空间的虚拟地址。
#include
void *kmap(struct page *page);
void kunmap(struct page *page);
kmap 为系统中的任何页返回⼀个内核虚拟地址。
对于低端内存页,它只返回页的逻辑地址;
对于⾼端内存页, kmap在"内核永久映射空间"中创建⼀个特殊的映射。 这样的映射数⽬是有限,因此最好不要持有过长的时间。
使⽤ kmap 创建的映射应当使⽤ kunmap 来释放;
kmap 调⽤维护⼀个计数器, 因此若2个或多个函数都在同⼀个页上调⽤kmap也是允许的。
通常情况下,"内核永久映射空间"是 4M ⼤⼩,因此仅仅需要⼀个页表即可,内核通过来 pkmap_page_table寻这个页表。
注意:不⽤时及时释放。
kmalloc()和vmalloc()相⽐,kmalloc()总是从ZONE_NORMAL(下图中的直接映射区)申请内存。kmalloc()分配的内存空间通常⽤于linux内核的系统数据结构和链表。因内核需要经常访问其数据结构和链表,使⽤固定映射的ZONE_NORMAL空间的内存有利于提⾼效率。
使⽤vmalloc()可以申请⾮连续的物理内存页,并组成虚拟连续内存空间。vmalloc()优先从⾼端内存(下图中的动态映射区)申请。内核在分配那些不经常使⽤的内存时,都⽤⾼端内存空间(如果有),所谓不经常使⽤是相对来说的,⽐如内核的⼀些数据结构就属于经常使⽤的,⽽⽤户的⼀些数据就属于不经常使⽤的。
alloc_pages(_GFP_HIGHMEM)+kmap()⽅式申请的内存使⽤内核永久映射空间(下图中的KMAP区),空间较⼩(通常4M线性空间),不⽤时需要及时释放。另外,可以指定alloc_pages()从直接映射区申请内存,需要使⽤_GFP_NORMAL属性指定。
__get_free_pages()/__free_pages()不能申请⾼端内存页⾯,操作区域和kmalloc()相同(下图中的动态映射区)。
6、virt_to_page()
其作⽤是由内核空间的虚拟地址得到页结构。见下⾯的宏定义。
#define virt_to_pfn(kaddr) (__pa(kaddr) 》 PAGE_SHIFT)
#define pfn_to_virt(pfn) __va((pfn) 《 PAGE_SHIFT)
#define virt_to_page(addr)pfn_to_page(virt_to_pfn(addr))
#define page_to_virt(page)pfn_to_virt(page_to_pfn(page))
#define __pfn_to_page(pfn) (mem_map + ((pfn) -ARCH_PFN_OFFSET))
#define __page_to_pfn(page) ((unsigned long)((page) -mem_map) + \
ARCH_PFN_OFFSET)
7、物理地址和虚拟地址之间转换
#ifdef CONFIG_BOOKE
#define __va(x) ((void *)(unsigned long)((phys_addr_t)(x) +VIRT_PHYS_OFFSET))
#define __pa(x) ((unsigned long)(x) -VIRT_PHYS_OFFSET)
#else
#define __va(x) ((void *)(unsigned long)((phys_addr_t)(x) +PAGE_OFFSET - MEMORY_START))
#define __pa(x) ((unsigned long)(x) - PAGE_OFFSET +MEMORY_START)
#endif
8、ioremap()/iounmap()
ioremap()的作⽤是把device寄存器和内存的物理地址区域映射到内核虚拟区域,返回值为内核的虚拟地址。
注明:在内核中操作内存空间时使⽤的都是内核虚拟地址,必须把device的空间映射到内核虚拟空间。
#include
void *ioremap(unsigned long phys_addr, unsigned longsize);
void *ioremap_nocache(unsigned long phys_addr, unsigned longsize); 映射⾮cache的io内存区域
void iounmap(void * addr);
为了增加可移植性,最好使⽤下⾯的接⼝函数读写io内存区域,
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
如果你必须读和写⼀系列值到⼀个给定的 I/O 内存地址, 你可以使⽤这些函数的重复版本:
void ioread8_rep(void *addr, void *buf, unsigned longcount);
void ioread16_rep(void *addr, void *buf, unsigned longcount);
void ioread32_rep(void *addr, void *buf, unsigned longcount);
void iowrite8_rep(void *addr, const void *buf, unsigned longcount);
void iowrite16_rep(void *addr, const void *buf, unsignedlong count);
void iowrite32_rep(void *addr, const void *buf, unsignedlong count);
这些函数读或写 count 值从给定的 buf 到 给定的 addr. 注意 count 表达为在被写⼊的数据⼤⼩;ioread32_rep 读取 count 32-位值
从 buf 开始。
9、request_mem_region()
本函数的作⽤是:外设的io端⼝映射到io memoryregion中。在本函数实现中会检查输⼊到本函数的参数所描述的空间(下⾯成为本io 空间)是否和io memoryregion中已存在的空间冲突等,并设置本io空间的parent字段等(把本io空间插⼊到io 空间树种)。
write的返回值 注明:io memory region空间中是以树形结构组织的,默认的根为iomem_resource描述的io空间,其name为"PCI mem".
request_mem_region(start,n,name)输⼊的参数依次是设备的物理地址,字节长度,设备名字。函数返回类型如下
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
10、SetPageReserved()
随着linux的长时间运⾏,空闲页⾯会越来越少,为了防⽌linux内核进⼊请求页⾯的僵局中,Linux内核采⽤页⾯回收算法(PFRA)从⽤户进程和内核⾼速缓存中回收内存页框,并根据需要把要回收页框的内容交换到磁盘上的交换区。调⽤该函数可以使页⾯不被交换。
#define SetPageReserved(page) set_bit(PG_reserved,&(page)->flags)
PG_reserved 的标志说明如下。
* PG_reserved is set for special pages, which can never beswapped out. Some
* of them might not even exist (eg empty_bad_page)…
11、do_mmap()/do_ummap()
内核使⽤do_mmap()函数为进程创建⼀个新的线性地址区间。但是说该函数创建了⼀个新VMA并不⾮常准确,因为如果创建的地址区间和⼀个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并为⼀个。如果不能合并,那么就确实需要创建⼀个新的VMA了。但⽆论哪种情况,do_mmap()函数都会将⼀个地址区间加⼊到进程的地址空间中--⽆论是扩展已存在的内存区域还是创建⼀个新的区域。
同样,释放⼀个内存区域应使⽤函数do_ummap(),它会销毁对应的内存区域。
12、get_user_pages()
作⽤是在内核空间获取⽤户空间内存的page 描述,之后可以通过函数kmap() 获取page对应到内核的虚拟地址。
int get_user_pages(struct task_struct *tsk, struct mm_struct*mm,
unsigned long start, int len, int write, int force,
struct page **pages, struct vm_area_struct **vmas)
参数说明
参数tsk:指⽰⽤户空间对应进程的task_struct数据结构。只是为了记录错误信息⽤,该参数可以为空。
参数mm:从该mm struct中获取start 指⽰的若⼲页⾯。
参数start:参数mm空间的起始地址,即⽤户空间的虚拟地址。
参数len:需要映射的页数。
参数write:可以写标志。
参数force:强制可以写标志。
参数pages:输出的页数据结构。
参数vmas:对应的需要存储区,(没有看明⽩对应的代码)
返回值:数返回实际获取的页数,貌似对每个实际获取的页都是给页计数值增1,如果实际获取的页不等于请求的页,要放弃操作则必须对已获取的页计数值减1.
13、copy_from_user()和copy_to_user()
主要应⽤于设备驱动中读写函数中,通过系统调⽤触发,在当前进程上下⽂内核态运⾏(即当前进程通过系统调⽤触发)。
copy_from_user的⽬的是防⽌⽤户程序欺骗内核,将⼀个⾮法的地址传进去,如果没有它,这⼀⾮法地址就检测不到,内和就会访问这个地址指向的数据。因为在内核中访问任何地址都没有保护,如果不幸访问⼀个错误的内存地址会搞死内核或发⽣更严重的问题
copy_from_user调⽤了access_ok,所以才有"⾃⼰判断功能"
access_ok(),可以检查访问的空间是否合法。
注意:中断代码时不能⽤copy_from_user,因为其调⽤了might_sleep()函数,会导致睡眠。
unsigned long copy_to_user(void __user *to, const void*from, unsigned long n)
通常⽤在设备读函数或ioctl中获取参数的函数中:其中"to"是⽤户空间的buffer地址,在本函数中将内核buffer"from"除的n个字节拷贝到⽤户空间的"to"buffer.
unsigned long copy_from_user(void *to, const void __user*from, unsigned long n)
通常⽤在设备写函数或ioctl中设置参数的函数中:"to"是内核空间的buffer指针,要写⼊的buffer;"from"是⽤户空间的指针,数据源buffer.
14、get_user(x, ptr)
本函数的作⽤是获取⽤户空间指定地址的数值并保存到内核变量x中,ptr为⽤户空间的地址。⽤法举例如下。
get_user(val, (int __user *)arg)
注明:函数⽤户进程上下⽂内核态,即通常在系统调⽤函数中使⽤该函数。
15、put_user(x, ptr)
本函数的作⽤是将内核空间的变量x的数值保存到⽤户空间指定地址处,prt为⽤户空间地址。⽤法举例如下。
put_user(val, (int __user *)arg)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论