我正在开发一个Linux内核驱动程序,它可以为用户空间提供一大块物理内存 . 我有一个工作版本的驱动程序,但它目前非常慢 . 所以,我已经退回了几步,尝试制作一个小而简单的驱动程序来重现问题 .
我使用内核参数 memmap=2G$1G
在启动时保留内存 . 然后,在驱动程序的 __init
函数中,我将一些内存初始化为已知值 . 我还提供了一些代码来测量时序:
#define RESERVED_REGION_SIZE (1 * 1024 * 1024 * 1024) // 1GB
#define RESERVED_REGION_OFFSET (1 * 1024 * 1024 * 1024) // 1GB
static int __init memdrv_init(void)
{
struct timeval t1, t2;
printk(KERN_INFO "[memdriver] init\n");
// Remap reserved physical memory (that we grabbed at boot time)
do_gettimeofday( &t1 );
reservedBlock = ioremap( RESERVED_REGION_OFFSET, RESERVED_REGION_SIZE );
do_gettimeofday( &t2 );
printk( KERN_ERR "[memdriver] ioremap() took %d usec\n", usec_diff( &t2, &t1 ) );
// Set the memory to a known value
do_gettimeofday( &t1 );
memset( reservedBlock, 0xAB, RESERVED_REGION_SIZE );
do_gettimeofday( &t2 );
printk( KERN_ERR "[memdriver] memset() took %d usec\n", usec_diff( &t2, &t1 ) );
// Register the character device
...
return 0;
}
我加载驱动程序,并检查dmesg . 它报告:
[memdriver] init
[memdriver] ioremap() took 76268 usec
[memdriver] memset() took 12622779 usec
这是memset的12.6秒 . 这意味着memset在 81 MB/sec 运行 . 为什么它这么慢?
这是Fedora 13上的内核2.6.34,它是一个x86_64系统 .
编辑:
此方案背后的目标是获取一大块物理内存并使其可用于PCI设备(通过内存的总线/物理地址)和用户空间应用程序(通过调用 mmap
,由驱动程序支持) . 然后,PCI设备将不断用数据填充此内存,用户空间应用程序将读取它 . 如果 ioremap
是一个不好的方法(如下面Ben所建议的那样),我允许我获得可由硬件和软件直接访问的任何大块内存 . 我也可以使用更小的缓冲区 .
请参阅下面的最终解决方案 .
4 回答
ioremap
分配不可缓存的页面,因为您需要访问内存映射的io设备 . 这可以解释你的糟糕表现 .你可能想要
kmalloc
或vmalloc
. usual reference materials将解释每个人的能力 .我不认为
ioremap()
是你想要的 . 您应该只使用readb
,readl
,writeb
,memcpy_toio
等访问结果(您称之为reservedBlock
) . 甚至不能保证返回虚拟映射(尽管它显然在您的平台上) . 我猜这个区域被映射为未缓存(适用于IO寄存器),导致可怕的性能 .已经有一段时间了,但我正在更新,因为我最终找到了这个ioremap问题的解决方法 .
由于我们将自定义硬件直接写入内存,因此将其标记为不可缓存可能更为正确,但它无法忍受缓慢且无法用于我们的应用程序 . 我们的解决方案是只有在有足够的新数据填充我们架构上的整个缓存行时才读取该内存(环形缓冲区)(我认为这是256字节) . 这保证了我们从来没有过时的数据,而且速度非常快 .
我试过用
memmap
做一个巨大的内存块保留这个块的
ioremap
ping给了我一个映射的内存地址空间,超过几个tera字节 .当您要求从64 GB开始保留128GB内存时 . 你在/ proc / vmallocinfo中看到以下内容
因此地址空间从0xffffc9001f3a8000开始(这太大了) .
其次,你的观察是正确的 . 甚至
memset_io
导致触摸所有这些内存的极大延迟(几十分钟) .因此,所花费的时间主要是地址空间转换和非可缓存页面加载 .