您的位置:首页 > 其它

DMA传输中的内存对齐

2016-05-18 13:47 330 查看
本文主要介绍内存对齐的概念和方法,并讲述在host与device之间DMA传输时,一个与“内存对齐”相关的,并最终引起系统概率性蓝屏的bug。

一、“内存对齐”的概念

内存对齐,又称为字节对齐,是一个数据类型所能存放的内存地址的属性。这个属性实际上就是内存地址,它需要符合一定的规范。这个规范就是内存地址值必须是2的N次方。当我们说一个数据类型的内存对齐为8时,(或按8字节对齐时),实际上指的是它的内存地址值可以被8整除。

内存对齐的要求,是由CPU处理内存的方式决定的(或者说寻址方式,它与地址总线的宽度相关)。详细参考我链接的博文2。在此简单的总结一下内存对齐的基本规范:

1)标准类型:自然对齐(Naturally Aligned)即可。对齐属性和它的类型大小相等,或整数倍。

2)数组:按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。

3)联合:按其包含的长度最大的数据类型对齐。

4)结构体:结构体中每个数据类型都要对齐。

下面通过一段代码来看看在VS调试器中,内存的对齐和填充

<span style="font-size:14px;">	struct stu
{
char sex;
int length;
char name[10];
};

stu a{ 'g', 256, "jaojido" };
cout << "sizeof(stu) = " << sizeof(stu) << endl;
cout << "sizeof(a) = " << sizeof(stu) << endl;</span>


它的输出是:20和20。再看内存布局:



先看左边的“即时窗口”,进行了两项运算:

1)取变量a的地址:0x0018F900

2)对变量a进行sizeof运算:20

再看右边的“内存窗口”:

1)内存窗口的显示格式是:little-endian字节序的16进制的字节(00-FF,即0-255)。

2)内存地址0x0018F900处是:”67 cc cc cc“四个字节,其中低字节在前,即“67”,表示字符“g"。而”cc“是填充字符。

3)VC编译器,用”cc“作为字节对齐的填充字节。另外,数组默认填充”00“,故name[10]数组不足10个元素,后面被填充了3个”00“。

4)数组在内存中按元素顺序排列。

5)length = 256,或者”0x100"。故,它在内存中表示为“00 01 00 00”,低字节在前。

6)最后看中间区域,如果是普通字符,则直接显示,如“g”;如果是填充符,则显示“?”;如果是数字,则显示为“."。

二、C/C++提供的一些“内存对齐”相关方法

1,伪指令,适用于gcc和VC编译器

<span style="font-size:14px;">#pragma pack(n)        // 按n字节对齐</span>


<span style="font-size:14px;">#pragma pack()    //取消自定义对齐规则</span>


2,alignas,指定自定义类型的对齐字节

<span style="font-size:14px;">struct alignas(32) MyStruct</span>


3,alignof(xx),获取自定义类型的对齐字节

<span style="font-size:14px;">std::cout << alignof(MyStruct) << std::endl;</span>


4,std::max_align_t,获取本机最大对齐字节数

<span style="font-size:14px;">std::cout << alignof(std::max_align_t) << std::endl;</span>


5,分配按指定字节对齐的内存空间

<span style="font-size:14px;">void* __cdecl _aligned_malloc(
_In_ _CRT_GUARDOVERFLOW size_t _Size,
_In_                    size_t _Alignment
);</span>


6,释放_aligned_malloc分配的空间

<span style="font-size:14px;">void __cdecl _aligned_free(
_Pre_maybenull_ _Post_invalid_ void* _Block
);</span>


三、DMA传输中的内存对齐

1,创建CommonBuffer

DMA传输中需要跨越Host和Device的CPU之间进行数据传输,必然会涉及到不同的CPU的寻址方式不同的问题。故此,必须要求待传输的数据地址和数据大小都必须按某种规则对齐。

微软的KMDF框架中,提供了一整套解决方案,参考:Wdf CommonBuffer Object Reference。主要用到以下函数:

<span style="font-size:14px;">FORCEINLINE
WdfDeviceSetAlignmentRequirement(
_In_
WDFDEVICE Device,
_In_
ULONG AlignmentRequirement
)</span>


如,sample中,在创建DMA Enabler instance之前调用

//
// PLx PCI9656 DMA_TRANSFER_ELEMENTS must be 16-byte aligned
//
WdfDeviceSetAlignmentRequirement( DevExt->Device,
PCI9656_DTE_ALIGNMENT_16 );


该函数能设置分配出来的commonbuffer的对齐字节。

2,配置Out Bound Window

要进行DMA传输,第一步就是在host上创建一块Common Buffer,第二步就是将这块Common Buffer的Logic Address(或者叫PCI地址)传递给device,并用该地址配置device的out bound window(地址映射)。后续就可以建立host与device对这个Common Buffer的读写。这就是DMA。

在创建Common Buffer和配置out bound window时,就牵涉到”内存对齐“的问题了。

最初的时候,我们参考微软的sample,设置的是”PCI9656_DTE_ALIGNMENT_16“,即16字节对齐,并开辟了”1M“大小的Common Buffer,用于DMA Read。设备加电后,系统按16字节对齐,分配Common Buffer。但是,在进行DMA传输的时候,概率的发现传输回来的数据多了”1024“个0。且一当出现这种现象后,每次DMA都会存在,多次DMA之后,系统回蓝屏。重启电脑后,概率性的可以恢复正常。

经过多次试验观察和分析,发现:如果某次设备加电,系统分配的Common Buffer的Logic Address的low part的后5位为0(16进制,low part共8位16进制数,即4个字节),形如:”0x57d00000“,此时DMA传输正常,否则DMA数据异常并最终导致系统蓝屏。

事实上,该款device的out bound window可以分为多个Page,每个Page的大小是1M,即0x100000。后5位是0。在配置device的out bound window的时候,它实际上是要求按1M字节对齐的。如果host分配的logic address刚好是1M字节对齐的,DMA正常,否则,异常。

bug的原因找到了,怎么解决?两个办法:

1)host创建Common Buffer的时候,指定按1M字节对齐;

2)device在配out bound window时,进行偏移。原本1M的buffer,映射到两个相邻的page,根据其实际的logic address进行偏移,并对齐。

参考博文:

参考博文1

参考博文2

参考博文3
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: