1.1 kmalloc函数

Kmalloc分配的是连续的物理地址空间。如果需要连续的物理页,可以使用此函数,这是内核中内存分配的常用方式,也是大多数情况下应该使用的内存分配方式。

传递给函数的最常用的标志是GTP_ATOMIC和GTP_KERNEL。前面的标志表示进行不睡眠的高优先级分配。在中断处理程序和其他不能睡眠的代码段中需要。后面的标志可以睡眠,在没有持自旋锁的进程上下文中使用。此函数返回内核逻辑地址。

头文件:#include <linux/slab.h>

1.1.1 申请空间

代码语言:javascript

AI代码解释

static void *kmalloc(size_t size, gfp_t flags)

参数:
size_t size :申请的空间大小
gfp_t flags:申请的标志(模式)

返回值:申请的空间首地址。如果为NULL,表示分配失败!

一般填写的模式:

GFP_ATOMIC:用来从中断处理和进程上下文之外的其他代码中分配内存,分配内存优先级高,不会阻塞

GFP_KERNEL:内核内存的正常分配方式,可能会阻塞。
1.1.2 释放内存空间

代码语言:javascript

AI代码解释

void kfree(const void *block)

参数:

void *block:将要释放空间的首地址
1.1.3 示例

代码语言:javascript

AI代码解释

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 申请空间*/

buff=kmalloc(1024, GFP_KERNEL);




/*1.2 初始化空间*/

memset(buff,0x10,1024);




/*1.3 打印出空间的数据*/

int i;

for(i=0;i<1024;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.h>

1.2.1 申请空间

代码语言:javascript

AI代码解释

void *vmalloc(unsigned long size)

参数:

unsigned long size :分配空间的大小

返回值:申请的空间首地址

1.2.2 释放空间

代码语言:javascript

AI代码解释

void vfree(const void *addr)

参数:

const void *addr:释放的空间首地址

1.2.3 示例

代码语言:javascript

AI代码解释

#include <linux/init.h>

#include <linux/module.h>

#include <linux/vmalloc.h>

#include <linux/slab.h>

char *buff=NULL;

static int __init interrupt_init(void)

{

printk("init ok\n");

/*1.1 申请空间*/

buff=vmalloc(1024);




if(buff==NULL)

{

printk("内存空间分配失败!\n\n");

}




/*1.2 初始化空间*/

memset(buff,0x10,1024);




/*1.3 打印出空间的数据*/

int i;

for(i=0;i<1024;i++)

{

printk("0x%X \n",buff[i]);

}

printk("buff=0x%x\n",buff); //buff=0xf0537000

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 区别总结

代码语言:javascript

AI代码解释

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>

函数原型:

代码语言:javascript

AI代码解释

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

int munmap(void* start,size_t length);

映射函数

代码语言:javascript

AI代码解释

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,表示从文件头开始映射。

解除映射

代码语言:javascript

AI代码解释

int munmap(void *start, size_t length);

功能:取消参数start所指向的映射内存,参数length表示欲取消的内存大小。

返回值:解除成功返回0,否则返回-1

2.2 Linux内核的mmap接口

2.2.1 内核描述虚拟内存的结构体

Linux内核中使用结构体vm_area_struct来描述虚拟内存的区域,其中几个主要成员如下:

代码语言:javascript

AI代码解释

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函数的接口。原型如下:

代码语言:javascript

AI代码解释

int (*mmap) (struct file *, struct vm_area_struct *);

其中第二个参数struct vm_area_struct *相当于内核找到的,可以拿来用的虚拟内存区间。mmap内部可以完成页表的建立。

2.2.3 实现mmap映射

映射一个设备是指把用户空间的一段地址关联到设备内存上,当程序读写这段用户空间的地址时,它实际上是在访问设备。这里需要做的两个操作:

代码语言:javascript

AI代码解释

1.找到可以用来关联的虚拟地址区间。

2.实现关联操作。

mmap设备操作实例如下:

代码语言:javascript

AI代码解释

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函数实现。

代码如下:

代码语言:javascript

AI代码解释

buf = (char *)kmalloc(MM_SIZE, GFP_KERNEL);

//内核申请内存只能按页申请,申请该内存以便后面把它当作虚拟设备
2.2.4 remap_pfn_range函数

remap_pfn_range函数用于一次建立所有页表。函数原型如下:

代码语言:javascript

AI代码解释

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 :将内核虚拟地址转化为物理地址。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐