由于Linux系统提供了复杂的内存管理功能,所以内存的概念在Linux系统中的相对复杂,有常规的内存、高端的内存、虚拟地址、逻辑地址、总线地址、物理地址、I/O内存、设备内存、预留内存等概念
指令格式:IN 累加器,{端口号|DX}
OUT {端口号|DX},累加器
虚拟内存机制可以让用户感觉好像程序可以使用非常大的内存空间
MMU操作原理:
__get_free_pages(unsigned int flags,unsigned int order);
get_zeroed_page(unsigned int flags);
__get_free_page(unsigned int flags);
struct page*alloc_pages(int gfp_mask,unsigned long order);
//__get_free_pages()函数对应的释放函数
void free_page(unsigned long addr);
void free_pages(unsigned long addr,unsigned long order);
void *vmalloc(unsigned long size);
void vfree(void *addr);
//1.创建slab缓存
struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags,
void (*ctor)(void*, struct kmem_cache*, unsigned long),
void (*dtor)(void*,struct kmem_cache*, unsigned long));
//2.分配slab缓存
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);
//3.释放slab函数
void kmem_cache_free(struct kmem_cache *cachep, void objp);
//4.收回slab缓存
int kmem_cache_destroy(struct kmem_cache *cachep);
//slab缓存使用范例:
//创建slab缓存
static kmem_cache_t *xxx_cachep;
xxx_cachep = kmem_cache_create("xxx", sizeof(struct xxx), 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
//分配slab缓存
struct xxx *ctx;
ctx = kmem_cache_alloc(xxx_cachep, GFP_KERNEL);
...
//释放slab缓存
kmem_cache_free(xxx_cachep, ctx);
kmem_cache_destroy(xxx_cachep);
//1.创建内存池
mempool_t *mempool_create(int min_nr, menpool_alloc_t *alloc_fn, menpool_free_t * free_fn, void *pool_data);
//内存池机制提供的标准对象分配和回收函数指针的原型分别为
typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);
和
typedef void (mempool_free_t)(void *element, void *pool_data);
//2.分配和回收对象
void *mempool_alloc(mempool_t, int gfp_mask);
void mempool_free(void *element, mempool_t *pool);
//3.回收内存池
void mempool_destroy(mempool_t *pool);
设备通常提供一组寄存器来控制设备,读写设备和获取设备状态,及控制寄存器、数据寄存器和状态寄存器。这些寄存器可能位于I/O空间中,也可能位于内存空间中。当位于I/O空间时候,通常称为I/O端口;但位于内存空间时,对应的内存空间称为I/O内存
//1.读写字节端口(8位)
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
//2.读写字端口
unsigned inw(unsined port);
void outw(unsigned short word, unsigned port);
//3.读写长字端口
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
//4.读写一串字节
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
//5.insb()从端口port开始读count个字节端口,并将读取结果接入addr指向的内存;outsb()将addr指向的内存中的count个字节连续的写入以port开始的端口
//6.读写一串字
void insw(unsigned port, void *addr unsigned long count);
void outse(unsigned port, void *addr, unsignd long count);
//7.读写一串长字
void insl(unsigned port, void *addr, unsigned log count);
void outsl(unsigned port, void *addr, unsigned long count);
I/O内存通常是芯片内部的哥哥I2C、SPI、USB等控制器的寄存器后者外部内存总线的设备。在访问I/O内存之前首先要做的是将设备所处的物理地址映射到虚拟地址。
//将物理地址映射到虚拟地址
void *ioremap(unsigned long offset,unsigned long size)
//释放
void ionumap(void * addr);
//ioremap()函数的变体函数devm_ioremap(),它不需要再驱动退出或者出错的时候进行iounmap().
void __iomem *devm_ioremap(struct device* dev, resource_size_t offset, unsigned long size);
在设备地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地址,但是Linux内核推荐使用一组标准的API开完成设备内存映射的虚拟地址的读写
//1.读I/O内存
#define readb(c) ({u8 __v = readb_relaxed(c);__iomb();__v;})
#define readw(c) ({u16 __v = readb_relaxed(c);__iomb();__v;})
#define readl(c) ({u32 __v = readb_relaxed(c);__iomb();__v;})
//2.写I/O内存
#define writeb(v,c) ({u8 __v = readb_relaxed(c);__iomb();__v;})
#define readw(v,c) ({u16 __v = readb_relaxed(c);__iomb();__v;})
#define readl(v,c) ({u32 __v = readb_relaxed(c);__iomb();__v;})
struct resource *request_region(unsigned long first,unsigned long n,const char*name);
void release_region(unsigned long start,unsigned long n);
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
void release_mem_region(unsigned long start, unsigned long len);
一般情况下用户空间是不能也不应该直接访问设备但是我们可以通过在设备驱动程序中实现mmap()函数,这个函数可以使得用户空间能直接访问物理设备
//驱动中mmap()函数原型:
int (*mmap)(struct file *,struct vm_area_struct *);
//用户空间的mmap()函数原型:
caddr_t mmap(caddr_t addr,size_t len,int prot,int flags,int fd,off_t offset);
struct vm_area_struct
{
struct mm_struct *vm_mm;/*所处的地址空间*/
unsigned long vm_start;/*开始虚拟地址*/
unsigned long vm_end;/*结束虚拟地址*/
pgprot_t vm_page_prot;/*访问权限*/
unsigned long vm_flags;/*标识,VM_READ,WM_WRITE,VM_EXEC,VM_SHARED*/
...
/*VMA的函数的指针*/
struct vm_operations_struct *vm_ops;
unsigned long vm_pgoff;/*偏移(页帧号)*/
struct file *vm_file;
void *vm_private_data;
....
};
struct vm_operations_struct{
void(*open)(struct vm_area_struct *area);/*打开vma的操作*/
void (*close)(..)
struct page*(nopage)(...)/*访问的页不存在时调用*/
...
};
static int xxx_map(struct file*filp,struct vm_area_struct *vma)
{
/*建立页表*/
if(remap_pfn_range(vma,vma->start,vm->vm_pgoff,
vm->vm_end-vma->start,vma->page_prot))
return -EAGAIN;
vma->ops = &xxx_remap_vmops;
xxx_vma_open(vma);
return 0;
}
/*vma打开函数*/
void xxx_vm_open(struct vm_area_struct *vma)
{
...
printk(KERNEL "xxx VMA open,virt %1x,phys %1x\n",vma->vm_start,
vma->vm_pgoff<...
printk(KERN_NOTICE "xxx VMA close.\n");
}
static struct vm_operations_struct xxx_remap_vm_ops = {
/*VMA操作结构体*/
.open = xxx_vm_open,
.close = xxx_vma_close,
...
};
//remap_pfn_range函数原型
remap_pfn_range(struct vm_area_struct *vma,unsigned long addr,unsigned long pfn,unsigned long size,pgprot_t prot);
/*内核模块加载函数*/
int __init kmalloc_map_init(void)
{
...
/*申请设备号
添加cdev结构体*/
buffer = kmalloc(BUFSIZE,GFP_KERNEL);//申请buffer
/*virt_to_page,获取对应的虚拟页*/
for(page = virt_to_page(buffer);
page/*设置为保留页*/
}
/*mmap()函数*/
static int kmalloc_map_mmap(struct file*filp,struct vm_area_struct *vma)
{
unsigned long page,pos;
unsigned long start = (unsigned long)vma->vm_start;
unsigned long size = (unsigned long)(vma->vm_end-vma->vm_start);
printk(KERNEL_INFO "mmaptest_mmap called\n");
/*用户要映射的区域太大*/
if(size>BUFSIZE)
return -EINVAL;
pos = (unsigned long)buffer;
/*映射buffer中的所有页*/
while(size > 0){
/*每次映射一页*/
page = virt_to_phys((void *)pos);//先将在内核中用malloc分配的空间转换为对应的物理页地址
if(remap_page_range(start,page,PAGE_SIZE,PAGE_SHARED));/*将物理页地址映射到vma,并且每次只映射一页*/
return - EAGAIN;
start += PAGE_SIZE;
pos +=PAGE_SIZE;
size -=PAGE_SIZE;
}return 0;
}
简介:除了remap_pfn_range函数以外,在驱动程序中实现VMA的fault()函数可以为设备提供,更加灵活的映射途径,当访问的页不存在(发生缺页异常)时,fault()会被内核自动调用
static int xxx_fault(struct vm_area_struct *vm, struct vm_fault *vmf)
{
unsigned log paddr;
unsigned long pn;
pgoff_t index = vmf->pgoff;
struct vma_data *vdata = vma->vm_private_data;
...
pfn = paddr >> PAGE_SHIFT;
vm_inset_pfn(vam, (unsigned long)vmf->virtual_address, pfn);
return VM_FAULT_NOPAGE;
}
简介:假如我们已经做好目标电路板,而要将Linux移植到目标电路板,此时通常会建立外I/O内存物理地址到虚拟地址的静态映射,这个映射通过在与电路板对应的map_desc结构体数组中添加新的成员来完成
struct map_desc{
unsigned long virtual;//虚拟地址
unsigned long pfn; //__phys_to_phn(phy_addr)
unsigned long length; //大小
unsigned int type; //类型
};
DMA:是一种无序CPU帮助就可以让外设与系统之间进行双向数据传输的硬件机制简单点说就是这个样子 外设<———->内存
而不是传统的 外设<—-cpu—–>内存
假设DMA针对内存的目的地址与Cache缓存的对象有重叠区域,那么经过DMA操作后,Cache缓存对应的内存的数据就会被修改,而CPU却并不知道,它仍然会认为Cache中的数据就是内存中的数据,此时会产生Cache与内存之间的数据”不一致”错误
在采用Cache的系统中,同样一个数据可能存在于Cache中,也可能存在于主存中,当Cache中的数据与主存中的一样时则具有一致性,否则数据具有不一致性
内存中用于与外设交互数据的一块区域被称为DMA缓冲区,一般情况下DMA在物理上连续的
对于X86系统的ISA设备而言,DMA操作只能在16MB一下的内存中使用,因此在用kmalloc()和__get_free_pages()及类似的函数申请DMA缓冲区时应使用GFP_DMA标志,这样获得的DMA
区域是具备DMA能力的
#define __get_dma_pages(gfp_mask,order)\
__get_free_pages((gfp_mask)|GFP_DMA,(order))
static unsigend long dma_mem_alloc(int size);
基于DMA的硬件使用的是总线地址而不是物理地址
总线地址:是从设备的角度上看到的内存地址
物理地址:是从CPU MMU控制器外围角度上看到的内存地址
unsigned long virt_to_bus(volate void *address);
void *bus_to_virt(unsigned long address);
int dma_set_mask(struct device*dev,u64 mask);
- 例如:对于只能在24位地址上执行DMA操作的设备,就应该使用如下方法 dma_set_mask(dev,0xffffff)
void *ama_alloc_coherent(struct device *dev,size_t size,dma_addr_t handle,gfp_t gfp)
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);
void * dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);
#definedma_free_writecombine(dev,size,cpu_addr,handle) dma_free_coherent(dev,size,cpu_addr,handle)
void * pci_alloc_consistent(struct pci_dev *pdev, size_t size, dma_addr_t *dma_addrp);
void pci_free_consistent(struct pci_dev *pdev, size_t size, void *cpu_addr, dma_addr_t dma_addr);
注意:dma_alloc_xxx()函数虽然是以dma_alloc_开头的,但是其申请 的区域比一定在DMA区域里面,例如,以32ARM为例,当coherent_dma_mask小于0xfffffff时,才会设置GFP_DMA标志,并从DMA区域去申请内存
CMA机制:不预留内存,这些内存平时是可用的,只有当需要的时候,才被分配给camera、HDMI等设备使用
并不是所有的DMA缓冲区的申请都是驱动申请的,如果是驱动申请的,用一致性的DMA缓冲区自然最方便,这直接考虑了DMA的一致性问题。但是,在许多情况下,缓冲区来自内核的上层(如网卡驱动中的网络报文、块设备驱动中要写入的数据等),上层很可能用普通的kmalloc()、__get_free_pages()等方法来申请,这时候就要使用流式DMA映射
流式DMA本质上是进行Cache的使无效或清除操作,以解决Cache的一致性问题
流式DMA映射的接口
dma_addr_t dma_map_single(struct device *dev,void *bufer, size_t size, enum dma_data_direction direction);
void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction);
Linux内核目前推荐使用dmaengine的驱动来编写DMA控制器的驱动,同时外设的驱动使用标准的dmaengine API进行DMA的准备、发起和完成时的回调工作
struct dma_chan *dma_request_slave_channel(struct device *dev, const char *name);
struct dma_chan *__dma_request_channel(const dma_cap_mask_t *mask, dma_filter_fn fn, void *fn_param);
void dma_release_chammel(struct dma_chan* chan);
static void xxx_dma_fini_callback(void *data)
{
struct completion *dma_complete = data;
complete(dma_complete);
}
issue_xxx_dma(...)
{
rx_desc = dmaengine_prep_slave_single(xxx->rx_chan,xxx->dst_start, t->len, DMA_DEV_TO_MEN, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
rx_desc->callback = xxx_dma_fini_callback;
rx_desc-。callback-param = &xxx->rx_done;
dmaengine_submit(rx_desc);
dma_async_issue_pending(xxx->rx_chan);
}