分类:
2012-09-25 19:04:57
原文地址:DMA管理 作者:BENNYSNAKE
make menuconfig后,生成新的配置.config。make时候,把新配置文件转化为autoconf.h,使用时候一般应用为include/linux/autoconf.h->mmdebug.h->mm.h。可以直接引用。
kconfig文件为menu的配置选项文件,功能设置比较简单,如下:
config DMA_MEM
config DMA_MEM_MAX_COUNT
config DMA_MEM_RESERVER_COUNT
#define CONFIG_DMA_MEM
#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)
#define dma_cache_wback(start, size)
#define 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相关的内容。标记解释如下:
情形
进程上下文,可以睡眠
进程上下文,不可以睡眠
中断处理程序
软中断
tasklet
需要用于DMA的内存,可以调度
需要用于DMA的内存,不可以调度
4、 kmalloc根据MAX_ORDER定义,决定一次最多能分配的内存大小。修改MAX_ORDER,保证大块内存的申请
能利用原有的slab分配是比较稳定、快速的方式。其中GFP_DMA指明内存从保留的DMA内存区域中分配,这就很符和当前的需求。kmem_cache_init初始化系统内存时,根据ZONE_DMA的位置,分配给cs_dmacachep。而其他内存则标记在ZONE_NORMAL,分配给cs_cachep。这就能实现系统申请普通内存和DMA内存的区分。
综上所述,目前的策略定为:
1、
2、
3、
4、
动态库暂提供4个接口,dma_malloc、dma_free、mmap_iospace、munmap_iospace。
1、
2、
3、
4、
再仔细查找了一下内核代码,发现不少驱动默认带入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。