内存与IO访问

2021/11/23 7:10:38

本文主要是介绍内存与IO访问,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

提 纲

  • LINUX内存管理
  • 内存存取
  • I/O内存访问接口
  • 将设备地址映射到用户空间
  • I/O内存的静态映射
  • DMA

1 LINUX内存管理

  1. 用户空间 0~3GB 内核空间
  2. 3~4GB 每个进程的用户空间是完全独立、互不相干的,用户进程各自有不同的页表。
  3. 内核空间是由内核负责映射,不会跟着进程改变。
  4. 用户程序需要通过系统调用才能访问内核空间。
  5. 当系统的物理内存大于4GB,则需要使用CPU的扩展分页(需要CPU支持)。

2 内存存取

2.1 用户空间内存动态申请

char *p = (char *)malloc(SIZE);
if(p==NULL)
	return 0;
function(p);
…
free(p);
p=NULL;

2.2 内核空间内存动态申请

1 void *kmalloc (size_t size,int flags);
其中flags主要有:
GFP_KERNEL 若暂时不存在空闲页可供申请,则休眠。所以不能在中断上下文或持有自旋锁时使用。
GFP_ATOMIC若暂时不存在空闲页可供申请,则返回。可在中断上下文或持有自旋锁时使用。
释放:kfree(void *p);
2 get_free_pages 系列,以页为单位分配内存,很少使用。
3 void *vmalloc(unsigned long size);
适用于分配较大的内存。
释放:void vfree(void *p);
4 内存池—操作系统自身使用。

2.3 虚地址与物理地址的关系

1 虚拟地址转物理地址

unsigned long virt_to_phys(volatile void *address);

2 物理地址转虚拟地址

unsigned long phys_to_virt (volatile void *address);

虚拟地址通常=物理地址-某个值+3GB (在内核空间)

unsigned long l=virt_to_phys((volatile void *)S3C2410_GPBDAT);
unsigned long l=phys_to_virt((volatile void *)0x56000010);

具体由任何一个寄存器一直追溯其虚拟地址。看mini2440驱动笔记_su .txt_7

3 I/O端口和I/O内存访问接口

3.1 I/O端口访问函数—ARM、PowerPC等不提供I/O空间。不适用。
3.2 I/O内存(自2.6.12后,一般采用下面2种方式读写寄存器)
方法一:使用系统定义好的API访问定义好的虚地址

 static inline u32 __raw_readl(const volatile void __iomem *addr)
 static inline void __raw_writel(u32 b, volatile void __iomem *addr);

如:__raw_readl(S3C2410_GPFCON);
       __raw_writel(0x12,S3C2410_GPFDAT);
    (函数定义在2.6.32 include/asm-generic/io.h)

方法二:采用ioremap()函数将设备的物理地址映射到虚拟地址再直接访问。如

#define rGPFCON (*((volatile unsigned  *)(ioremap((volatile unsigned *)0x56000050,4))))
regval = rGPFCON;  读
rGPFCON = regval;  写

使用ioremap()获得的虚地址应该被iounmap()释放。

3.3 检查将要申请的I/O内存是否可用。

 void request_mem_region(unsigned start,unsigned long len,char *name);
 void release_mem_region(unsigned start,unsigned long len);

申请成功,则标志为已经使用,其他驱动想再申请就会失败。
在这里插入图片描述
实际上对一般的特殊寄存器,大家都可访问,所以无需检查将要申请的I/O内存是否可用。只有专用来放资源的空间才需要。

4 将设备地址映射到用户空间

实现mmap()函数,使得用户空间能直接访问设备的物理地址。----用在显示适配器类的设备。
驱动中函数原型:

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

实现机制(想进一步了解,则请看代码清单11.8):
1 建立页表
2 填充vm_area_struct中的vm_operations_struct指针

I/O内存的静态映射

外设I/O内存物理地址到虚拟地址的静态映射。使用map_desc结构体。

struct map_desc {
	unsigned long virtual;  //虚拟地址
	unsigned long physical;//物理地址
	unsigned long length;//大小
	unsigned int type;//类型
};

请参考/arch/arm/mach-s3c2440/mach-smdk2440.c

DMA

DMA 一种无需CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。
Cache 是CPU针对内存的缓存。
当DMA的目的地址与cache的对象存在重叠区域时,容易使cache的数据与主存中的数据不一致。所以应当禁止DMA的目的地址范围内的cache功能。
在这里插入图片描述
1 分配/释放一个写合并的DMA缓冲区

 void *dma_alloc_writecombine(struct device *dev,size_t size,dma_addr_t *handle,gfp_t gfp);

其中:函数通过参数handle返回DMA缓冲区的总线地址
struct device: 用于描述设备相关的信息设备之间的层次关系,以及设备与总线、驱动的关系。

gfp一般为GFP_KERNEL
 void *dma_free_writecombine(struct device *dev,size_t size,void *cpuaddr,dma_addr_t *);
如:fbi->screen_base = dma_alloc_writecombine(sfb->dev, size,						  &map_dma, GFP_KERNEL);

参数解释

A = dma_alloc_writecombine(B,C,D,GFP_KERNEL);
A: 内存的虚拟起始地址,在内核要用此地址来操作所分配的内存
B: struct device指针,可以平台初始化里指定,主要是dma_mask之类,可参考framebuffer
C: 实际分配大小,传入dma_map_size即可
D: 返回的内存物理地址,dma就可以用。
所以,A和D是一一对应的,只不过,A是虚拟地址,而D是物理地址。
2 申请/释放DMA通道

int request_dma(unsigned int dmanr, const char *device_id);
void free_dma(unsigned int dmanr);

如: err = request_dma(chn, deviceID)

Linux 中DMA使用流程

在这里插入图片描述



这篇关于内存与IO访问的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程