linux - 使用内存映射的IO时调用ioread函数有什么好处

标签 linux io linux-kernel x86 linux-device-driver

要使用内存映射的I/O,我们需要首先调用request_mem_region。

struct resource *request_mem_region(
                unsigned long start,
                unsigned long len,
                char *name);

然后,由于内核在虚拟地址空间中运行,因此我们需要通过运行ioremap函数将物理地址映射到虚拟地址空间。
void *ioremap(unsigned long phys_addr, unsigned long size);

那为什么我们不能直接访问返回值。

从Linux设备驱动程序书

Once equipped with ioremap (and iounmap), a device driver can access any I/O memory address, whether or not it is directly mapped to virtual address space. Remember, though, that the addresses returned from ioremap should not be dereferenced directly; instead, accessor functions provided by the kernel should be used.



谁能用ioread32iowrite8()这样的访问器函数解释其背后的原因或优势?

最佳答案

您需要ioread8/iowrite8或至少转换为volatile*的任何内容,以确保优化仍然导致精确的1次访问(而不是0或大于1)。实际上,他们在处理字节序方面做得比这还要多(它们还处理字节序,以little-endian的形式访问设备内存。或者big-endian的ioread32be)以及Linux选择在这些函数中包括的编译时重新排序内存屏障语义。甚至由于DMA,读取后的运行时障碍。使用_rep版本仅通过一个屏障即可从设备内存中复制一个块。

在C中,数据竞争是UB(未定义行为)。这意味着允许编译器假定通过non-volatile指针访问的内存在两次访问之间不发生变化。而且可以将转换的if (x) y = *ptr;转换为tmp = *ptr; if (x) y = tmp;,即编译时的推测性负载。

MMIO寄存器甚至在读取时也会有副作用,因此您必须停止编译器执行源代码中没有的加载,并且必须强制编译器一次执行源代码中的所有加载。

商店也一样。 (甚至不允许编译器发明对非 Volatile 对象的写操作,但它们可以删除无效存储。例如,*ioreg = 1; *ioreg = 2;通常与*ioreg = 2;编译相同。由于认为不可见的一面,第一个存储被删除为“无效”。影响。

C volatile语义是MMIO的理想选择,但是Linux围绕它们包装的内容不止是易变的。

快速浏览谷歌搜索ioread8并浏览https://elixir.bootlin.com/linux/latest/source/lib/iomap.c#L11后,我们发现Linux I/O地址可以对IO地址空间(端口I/O,又名PIO; x86上的in/out指令)与内存地址空间(正常负载/存储到特殊地址)。 ioread*函数实际上会检查并相应地调度。

/*
 * Read/write from/to an (offsettable) iomem cookie. It might be a PIO
 * access or a MMIO access, these functions don't care. The info is
 * encoded in the hardware mapping set up by the mapping functions
 * (or the cookie itself, depending on implementation and hw).
 *
 * The generic routines don't assume any hardware mappings, and just
 * encode the PIO/MMIO as part of the cookie. They coldly assume that
 * the MMIO IO mappings are not in the low address range.
 *
 * Architectures for which this is not true can't use this generic
 * implementation and should do their own copy.
 */


例如实现,这里是ioread16。 (IO_COND是一个宏,用于根据预定义的常量检查地址:低地址是PIO地址)。

unsigned int ioread16(void __iomem *addr)
{
    IO_COND(addr, return inw(port), return readw(addr));
    return 0xffff;
}


如果仅将ioremap结果转换为volatile uint32_t*会怎样?

例如如果您使用的READ_ONCE/WRITE_ONCE只是转换为volatile unsigned char*或其他内容,并且用于原子访问共享变量。 (在Linux手动使用的volatile +内联asm原子实现中,它使用的不是C11 _Atomic)。

如果编译时重新排序不是问题,那么这实际上可能适用于x86等一些低端的ISA,但是其他的则需要更多的障碍。如果查看definition of readl (ioread32用于MMIO,而不是PIO的inl),它将在volatile指针的取消引用周围使用障碍。

(此代码及其使用的宏在与此相同的io.h中定义,或者您可以使用LXR链接进行导航:每个标识符都是超链接。)
static inline u32 readl(const volatile void __iomem *addr) {
    u32 val;
    __io_br();
    val = __le32_to_cpu(__raw_readl(addr));
    __io_ar(val);
    return val;
}

通用__raw_readl只是volatile取消引用;一些ISA可能会提供自己的。

__io_ar() 在读取后使用rmb()barrier()/* prevent prefetching of coherent DMA data ahead of a dma-complete */。读取前的障碍只是barrier()-在没有asm指令的情况下阻止编译时重新排序。



对错误问题的旧答案:以下文本回答了为什么需要调用ioremap的原因。

因为它是一个物理地址,并且内核内存未标识到物理地址(virt = phys)。

而且返回虚拟地址不是一种选择:并非所有系统都具有足够的虚拟地址空间,甚至无法将所有物理地址空间直接映射为连续的虚拟地址范围。 (但是当有足够的空间时,Linux会这样做,例如x86-64 Linux的虚拟地址空间布局记录在x86_64/mm.txt

值得注意的是,具有超过1或2GB RAM的系统上的32位x86内核(取决于内核的配置方式:2:2或1:3内核:虚拟地址空间的用户划分)。借助用于36位物理地址空间的PAE,32位x86内核可以使用比一次映射要多得多的物理内存。 (这非常可怕,并且使内核难以生存:一些随机博客取代了Linus Torvald关于PAE really really sucks的评论。)

其他ISA也可能具有此功能,并且IDK在需要字节访问时Alpha对IO存储器的作用;可能较早处理了将字加载/存储映射到字节加载/存储的物理地址空间区域,因此您请求正确的物理地址。 (http://www.tldp.org/HOWTO/Alpha-HOWTO-8.html)

但是32位x86 PAE显然是Linux关心的一个ISA,即使在Linux的早期也是如此。

关于linux - 使用内存映射的IO时调用ioread函数有什么好处,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59113831/

相关文章:

linux - 使用 shell 脚本将文件从一个文件夹复制到另一个文件夹并在目标文件夹重命名

linux - 命令在脚本中不起作用

c# - 配置 filesystemwatcher 使其仅在文件被完全复制时引发创建的事件

c++ - C++ 中的字符串操作和文件 IO?

linux - 可以从 Linux 内核模式写入 BIOS 吗?

linux - 如何增加 OS X 上的 Docker 基本卷大小?

linux - 自动更改 C 代码中某些内容的脚本或命令?

java - 使用 Java 读取非常大的文件

linux - 转换表行走时同步外部中止

linux - 如何在内核启动时添加 printk 函数