1.1 kmalloc函数Kmalloc分配的是连续的物理地址空间。如果需要连续的物理页可以使用此函数这是内核中内存分配的常用方式也是大多数情况下应该使用的内存分配方式。传递给函数的最常用的标志是GTP_ATOMIC和GTP_KERNEL。前面的标志表示进行不睡眠的高优先级分配。在中断处理程序和其他不能睡眠的代码段中需要。后面的标志可以睡眠在没有持自旋锁的进程上下文中使用。此函数返回内核逻辑地址。头文件#include linux/slab.h1.1.1 申请空间代码语言javascriptAI代码解释static void *kmalloc(size_t size, gfp_t flags) 参数 size_t size 申请的空间大小 gfp_t flags申请的标志(模式) 返回值申请的空间首地址。如果为NULL表示分配失败 一般填写的模式 GFP_ATOMIC用来从中断处理和进程上下文之外的其他代码中分配内存分配内存优先级高不会阻塞 GFP_KERNEL内核内存的正常分配方式可能会阻塞。1.1.2 释放内存空间代码语言javascriptAI代码解释void kfree(const void *block) 参数 void *block将要释放空间的首地址1.1.3 示例代码语言javascriptAI代码解释1.1.3 示例 #include linux/init.h #include linux/module.h #include linux/slab.h char *buff; static int __init interrupt_init(void) { printk(init ok\n); /*1.1 申请空间*/ buffkmalloc(1024, GFP_KERNEL); /*1.2 初始化空间*/ memset(buff,0x10,1024); /*1.3 打印出空间的数据*/ int i; for(i0;i1024;i) { printk(0x%X \n,buff[i]); } return 0; } static void __exit interrupt_exit(void) { /*1.4 释放申请的空间*/ kfree(buff); printk(exit ok\n); } module_init(interrupt_init); /*驱动入口*/ module_exit(interrupt_exit); /*驱动出口*/ MODULE_LICENSE(GPL);1.2 vmalloc 函数分配的空间是线性的在物理地址上不连续最多分配1GB的空间。定义文件\mm\vmalloc.c头文件#include linux/vmalloc.h1.2.1 申请空间代码语言javascriptAI代码解释void *vmalloc(unsigned long size)参数unsigned long size 分配空间的大小返回值申请的空间首地址1.2.2 释放空间代码语言javascriptAI代码解释void vfree(const void *addr)参数const void *addr释放的空间首地址1.2.3 示例代码语言javascriptAI代码解释#include linux/init.h #include linux/module.h #include linux/vmalloc.h #include linux/slab.h char *buffNULL; static int __init interrupt_init(void) { printk(init ok\n); /*1.1 申请空间*/ buffvmalloc(1024); if(buffNULL) { printk(内存空间分配失败!\n\n); } /*1.2 初始化空间*/ memset(buff,0x10,1024); /*1.3 打印出空间的数据*/ int i; for(i0;i1024;i) { printk(0x%X \n,buff[i]); } printk(buff0x%x\n,buff); //buff0xf0537000 return 0; } static void __exit interrupt_exit(void) { /*释放申请的空间*/ vfree(buff); printk(exit ok\n); } module_init(interrupt_init); /*驱动入口*/ module_exit(interrupt_exit); /*驱动出口*/ MODULE_LICENSE(GPL);1.3 区别总结代码语言javascriptAI代码解释1. kmalloc和vmalloc是分配的是内核的内存,malloc分配的是用户的内存 2. kmalloc保证分配的内存在物理上是连续的,vmalloc保证的是在虚拟地址空间上的连续 3. kmalloc能分配的大小有限,vmalloc能分配的大小相对较大 4. 内存只有在要被DMA访问的时候才需要物理上连续 5. vmalloc比kmalloc要慢二、 MMAP驱动实现2.1 应用层mmap函数介绍mmap函数用于将一个文件或者其它对象映射进内存通过对这段内存的读取和修改来实现对文件的读取和修改而不需要再调用read,write等操作。头文件sys/mman.h函数原型代码语言javascriptAI代码解释void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset); int munmap(void* start,size_t length);映射函数代码语言javascriptAI代码解释void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);1 addr: 指定映射的起始地址通常设为NULL由系统指定。2 length:映射到内存的文件长度。3 prot:映射的保护方式可以是PROT_EXEC映射区可被执行PROT_READ映射区可被读取PROT_WRITE映射区可被写入PROT_NONE映射区不能存取4 Flags:映射区的特性可以是MAP_SHARED写入映射区的数据会复制回文件且允许其他映射该文件的进程共享。MAP_PRIVATE对映射区的写入操作会产生一个映射区的复制(copy_on_write)对此区域所做的修改不会写回原文件。5 fd:由open返回的文件描述符代表要映射的文件。6 offset:以文件开始处的偏移量必须是分页大小的整数倍通常为0表示从文件头开始映射。解除映射代码语言javascriptAI代码解释int munmap(void *start, size_t length);功能取消参数start所指向的映射内存参数length表示欲取消的内存大小。返回值解除成功返回0否则返回-12.2 Linux内核的mmap接口2.2.1 内核描述虚拟内存的结构体Linux内核中使用结构体vm_area_struct来描述虚拟内存的区域其中几个主要成员如下代码语言javascriptAI代码解释unsigned long vm_start 虚拟内存区域起始地址 unsigned long vm_end 虚拟内存区域结束地址 unsigned long vm_flags 该区域的标志该结构体定义在linux/mm_types.h头文件中。该结构体的vm_flags成员赋值的标志为VM_IO和VM_RESERVED。其中VM_IO表示对设备IO空间的映射M_RESERVED表示该内存区不能被换出在设备驱动中虚拟页和物理页的关系应该是长期的应该保留起来不能随便被别的虚拟页换出取消。2.2.2 mmap操作接口在字符设备的文件操作集合struct file_operations中有mmap函数的接口。原型如下代码语言javascriptAI代码解释int (*mmap) (struct file *, struct vm_area_struct *);其中第二个参数struct vm_area_struct *相当于内核找到的可以拿来用的虚拟内存区间。mmap内部可以完成页表的建立。2.2.3 实现mmap映射映射一个设备是指把用户空间的一段地址关联到设备内存上当程序读写这段用户空间的地址时它实际上是在访问设备。这里需要做的两个操作代码语言javascriptAI代码解释1.找到可以用来关联的虚拟地址区间。 2.实现关联操作。mmap设备操作实例如下代码语言javascriptAI代码解释static int tiny4412_mmap(struct file *myfile, struct vm_area_struct *vma) { vma-vm_flags | VM_IO;//表示对设备IO空间的映射 vma-vm_flags | VM_RESERVED;//标志该内存区不能被换出在设备驱动中虚拟页和物理页的关系应该是长期的应该保留起来不能随便被别的虚拟页换出 if(remap_pfn_range(vma,//虚拟内存区域即设备地址将要映射到这里 vma-vm_start,//虚拟空间的起始地址 virt_to_phys(buf)PAGE_SHIFT,//与物理内存对应的页帧号物理地址右移12位 //说明: 向后面移动12位相当于除4096 为了得到页编号 vma-vm_end - vma-vm_start,//映射区域大小一般是页大小的整数倍 vma-vm_page_prot))//保护属性 { return -EAGAIN; } printk(tiny4412_mmap\n); return 0; }其中的buf就是在内核中申请的一段空间。使用kmalloc函数实现。代码如下代码语言javascriptAI代码解释buf (char *)kmalloc(MM_SIZE, GFP_KERNEL); //内核申请内存只能按页申请申请该内存以便后面把它当作虚拟设备2.2.4 remap_pfn_range函数remap_pfn_range函数用于一次建立所有页表。函数原型如下代码语言javascriptAI代码解释int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot);其中vma是内核为我们找到的虚拟地址空间addr要关联的是虚拟地址pfn是要关联的物理地址size是关联的长度是多少。ioremap与phys_to_virt、virt_to_phys的区别ioremap是用来为IO内存建立映射的 它为IO内存分配了虚拟地址这样驱动程序才可以访问这块内存。phys_to_virt只是计算出某个已知物理地址所对应的虚拟地址。将内核物理地址转化为虚拟地址。virt_to_phys 将内核虚拟地址转化为物理地址。