linux dma 开发例子,linux开发笔记

make

menuconfig后,生成新的配置.config。make时候,把新配置文件转化为autoconf.h,使用时候一般应用为include/linux/autoconf.h->mmdebug.h->mm.h。可以直接引用。

kconfig文件为menu的配置选项文件,功能设置比较简单,如下:

config DMA_MEM

tristate "DMA Reserver Memory for SA600A"

---help---

This option supports the DMA

options for SA600A.

config DMA_MEM_MAX_COUNT

int "Max

RAM(Mbytes)"

default "64"

depends on DMA_MEM

help

The default value is 64M RAM.

Change this if you know what you

are doing.

config DMA_MEM_RESERVER_COUNT

int

"Reserver RAM(Mbytes)"

default "24"

depends on DMA_MEM

help

The default reserver value is

24M RAM. Change this if you know what you

are

doing.这样autoconf.h就会生成3个宏:

#define CONFIG_DMA_MEM 1

#define CONFIG_DMA_MEM_MAX_COUNT 64

#define CONFIG_DMA_MEM_RESERVER_COUNT 24

linux内核内部提供3个刷新cache函数,使用前需要打开CONFIG_DMA_NONCOHERENT宏,这个宏内嵌在各个芯片默认的config文件中,如linux-2.6.27.28\arch\mips\configs\malta_defconfig

#define dma_cache_wback_inv(start,

size) _dma_cache_wback_inv(start, size)

#define dma_cache_wback(start,

size) _dma_cache_wback(start,

size)

#define dma_cache_inv(start,

size) _dma_cache_inv(start,

size)

对外只通过_sys_sysmips带入FLUSH_CACHE参数,调用__flush_cache_all函数。如有需要可在syscall.c中增加新的用户态接口

系统共享内存的2种方式:

shm_open和open类似,通过一个文件(类似驱动)作为内存共享的中介(mmap)。所以系统必须要有/dev/shm才能使用shm_open。shm_open是posix的实现,可以实现无亲缘关系的进程共享内存。使用上较为常见。Shm_open的第一个参数,默认为在/dev/shm/下的文件,如:”mytemp”=/dev/shm/mytemp,Man帮助文档说明中指出,shm_open的名字必须以/开头,但是中间不能再有/

shmget是system实现,不依赖其他驱动。可以为无亲缘、亲缘进程共享,其特点是需要key。这个key生产可以通过ftok查找一个真实存在文件,通过此路径及进程内共享内存id号,生产特定的key值。也可以是亲缘进程通过IPC_PRIVATE共享(和shm_open不一样的是,这个文件并不是实现的中介,只是使用文件的节点信息)。Shmget使用上没有shm_open常见,但是更普遍的存在各个版本linux中(如各种BSD,只能使用shmget)

ftok原型如下:

key_t ftok( char * fname, int id )fname就时你指定的文件名,id是子序号。

在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。

如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。

查询文件索引节点号的方法是: ls -i

当删除重建文件后,索引节点号由操作系统根据当时文件系统的使用情况分配,因此与原来不同,所以得到的索引节点号也不同。如果要确保key_t值不变,要目确保ftok的文件不被删除,要么不用ftok,指定一个固定的key_t值。

shmget()是用来开辟/指向一块共享内存的函数。参数定义如下:

key_t shmkey

是这块共享内存的标识符。如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来代替。如两个进程没有任何关系,就用ftok()算出来一个标识符使用。

1、内核提供了remap_page_range函数,可以实现物理、内核高端地址,映射到用户态空间。但需要注意的是,对于内存而言,2.4内核必须设置PG_Reserved标记,防止内存被交换出去。因此2.4版本内核做这样的工作是比较繁琐的,需要一页页的做。到了2.6时代,PG_Reserved已经放弃了,而是使用vm_reserved标记,这是针对一片区域的属性,所以目前的实现可以比较简单。除了通过remap_pfn_range函数外,还可以使用零页方式实现。零页:延后建表功能。不过实现较为复杂。

2、内核提供了/dev/mem的设备,已经实现了部分功能。这里指的部分功能是有限的、不完整的。VM对于内存和寄存器应该有不同的区分。因此VM结构体有对应的VM_IO做区分。remap_page_range需要判断查看所请求的偏移量(保存在vma->vm_pgoff中)是否超出了物理内存。如果是,则设置VMA的VM_IO标志,以标志该区域为I/O内存。非内存的寄存器空间要标示为IO空间,防止core

dump。(core dump:如程序运行时当掉,这时操作系统就会把程序当掉 时的内存内容 dump

出来,做为debugg参考。这个动作就叫作 core dump)

LDD2上提到,通过kmalloc和__get_free_pages得到的物理地址,不应该通过mmap进行用户态访问。原因是/dev/mem设备的实现中,并没有做VM_RESERVED和VM_IO的标记。因此对于内存对象而言,并不保证不置换出去(普通读写应无问题,DMA问题可能比较大)。对于IO地址而言,又不保证不做core

dump。而此要利用mem设备,必须进行一定的修改。目前所见的显卡驱动中(与需求很类似),它们代码中都明确的写入此2标记,用作显存和显卡寄存器的访问(因均不为物理内存地址之内)。

PS:mem设备应该在bootmem_init函数,通过boot_mem_map数组来指定可访问的各个物理地址空间。对于mips而言是4G大小,而其他芯片有不同的限制。实际直接使用当前mips的编写也不完全正确。因为我们的芯片确实还有许多未定义的物理地址空间,应对此类地址加以检查,或需要上层使用时额外注意,否则可能会导致使用的exception。

3、kmalloc最终通过__get_free_pages函数获取物理连续页,但据说是kmalloc是不产生syscall的,而且申请大小限制在128K。

Kmalloc包含了许多工作,其中有分中断、非中断模式,还有smp相关的内容。标记解释如下:

情形 相应标志

进程上下文,可以睡眠 使用GFP_KRENEL

进程上下文,不可以睡眠 使用GFP_ATOMIC,在你睡眠之前或之后用GFP_KERNEL执行内存分配

中断处理程序 使用GFP_ATOMIC

软中断 使用GFP_ATOMIC

tasklet 使用GFP_ATOMIC

需要用于DMA的内存,可以调度 使用(GFP_DMA|GFP_KERNALE)

需要用于DMA的内存,不可以调度 使用(GFP_DMA|GFP_ATOMIC),在调度之前执行内存分配完毕

4、 kmalloc根据MAX_ORDER定义,决定一次最多能分配的内存大小。修改MAX_ORDER,保证大块内存的申请

能利用原有的slab分配是比较稳定、快速的方式。其中GFP_DMA指明内存从保留的DMA内存区域中分配,这就很符和当前的需求。kmem_cache_init初始化系统内存时,根据ZONE_DMA的位置,分配给cs_dmacachep。而其他内存则标记在ZONE_NORMAL,分配给cs_cachep。这就能实现系统申请普通内存和DMA内存的区分。

综上所述,目前的策略定为:

1、 使用ZONE_DMA段,作为大片内存的保留。此片内存的申请、释放的细节,通过kmalloc带入(GFP_DMA|GFP_ATOMIC)实现。如新建一个/dev/dmamem设备,并增加一个动态库做申请释放的接口。

2、 申请函数返回物理、用户态虚拟地址,并通过mmap进行用户态地址的映射。

3、 用户态驱动要访问物理寄存器,通过/dev/mem设备获取访问地址,也由动态库的提供的接口实现。mem设备要做一定的修改

4、 Mem设备和dmamem设备功能上有一定的重复,可以考虑合并。

动态库暂提供4个接口,dma_malloc、dma_free、mmap_iospace、munmap_iospace。

1、 dma_malloc返回物理、虚拟地址。

2、 dma_free传入物理、虚拟地址,进行内存unmap和释放动作。

3、 mmap_iospace接口传入寄存器的物理地址,则通过mem设备做IO映射。如传入内存物理地址,做reserved映射。

4、 munmap_iospace做虚拟地址的unmap动作。

再仔细查找了一下内核代码,发现不少驱动默认带入GFP_DMA标记。当系统有ZONE_DMA,则从DMA区域分配。没有则从ZONE_NORMAL分配。因此想利用ZONE_DMA需要排除其他驱动不再使用此标记,这样的可移植性较差。

因而改为保留区域是系统看不见的区域,使用mem设备在用户态mmap映射出3段虚拟地址空间,分别对应write though/write

bak/uncache。这三段区域其实都是映射到同一段物理地址上,那么管理模块就可以根据需要,利用地址偏移就可以快速给出申请的虚拟、物理地址。

由于内核只提供了phys_mem_access_prot函数,做uncache内存的建表功能,所以要新增2个函数。参照pgprot_noncached实现,其实也很简单,只是把对应芯片的tag标记加上即可,如:prot

= (prot & ~_CACHE_MASK) |

_CACHE_WRITEBACK;。当然_CACHE_WRITEBACK没有定义的,查找一下芯片手册就能得出。

内存管理使用bitmap映射方式,内核提供了一套查找方法find_next_bit和find_next_zero_bit。对应大段、小端机器进行了算法改进。同时如果芯片支持bit检测功能,内核也加入对应的实现了。

值得一提的是,内核还提供了海明距离的优化函数:hweight32/hweight16/hweight8。能快速得出一个字段内有多少个bit是1。

在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。

例如:

1011101 与 1001001 之间的汉明距离是 2。

2143896 与 2233796 之间的汉明距离是 3。

"toned" 与 "roses" 之间的汉明距离是 3。

汉明重量是字符串相对于同样长度的零字符串的汉明距离,也就是说,它是字符串中非零的元素个数:对于二进制字符串来说,就是 1 的个数,所以

11101 的汉明重量是 4。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值