当 offset 为非负数(但是是 sysconf(_SC_PAGE_SIZE) 的倍数)时,为什么 mmap 会因 EINVAL 而失败?

Why is mmap failing with EINVAL when offset is non-negative (but is a multiple of sysconf(_SC_PAGE_SIZE))?

首先介绍一下我正在使用的机器(这是一个带有 SoC 的 SOM,包括一个 FPGA 和两个 CPU,其中一个有一个 linux OS 运行).

特点:

我有一个在 FPGA 上实现的自定义外设,它提供一个标准的 AXI4 接口到 BRAM 存储器(感谢 this component). This peripheral is registered in my device-tree and is accessible through the generic-uio driver of the kernel (more documentation 关于这个)。

BRAM内存的大小是32kB。以下是 /sys/class/uio/uioX/maps/map0/ 中出现的映射特征:

我正在编写一个 C 代码,它的目的只是读取此 BRAM 内存并将其中的内容记录在文本文件中。为此,我使用 mmap(这里是它的 man page)为位于 /dev/uioX(对应于我的设备)的文件创建一些内存投影。

我只想使用我需要的文件页,更精确地映射每页的文件页。这是我用来创建一页映射的函数:

/**
 * @brief Get a pointer to the corresponding file page
 * 
 * @param filePage file page number
 * @param fd pointer where the file descriptor will be stored
 * @return int* 
 */
int *getFilePagePointer(int *fd, unsigned int filePage)
{
    // open driver file
    *fd = open("/dev/uio1", O_RDWR);
    if (*fd == -1)
    {
        int errsv = errno;
        fprintf(stderr, "Error while opening /dev/uio1 : errno %d (%s).\n", errsv, strerror(errsv));
        exit(EXIT_FAILURE);
    }

    // choose projection parameters
    int length = sysconf(_SC_PAGE_SIZE);
    int offset = filePage * sysconf(_SC_PAGE_SIZE);

    // create mapping
    void *filePagePointer = mmap((void *)NULL, (size_t)length, PROT_READ, MAP_PRIVATE, *fd, (off_t)offset);
    if (filePagePointer == MAP_FAILED)
    {
        int errsv = errno;
        fprintf(stderr, "Error while mapping file page : errno %d (%s).\n", errsv, strerror(errsv));
        exit(EXIT_FAILURE);
    }

    return (int *)filePagePointer;
}

然后是问题:如果 filePage(即偏移量)是 0,它工作得很好,但如果它是严格正的,它会失败并显示 errno = 22

除非我在手册页中遗漏了什么,否则所有参数都是有效的(offsetsysconf(_SC_PAGE_SIZE) 的倍数,addrNULLlength 是严格正的并且不太大,并且标志是正确的)。 那么,为什么要使用 EINVAL?...

kernel driver 确实

static int uio_find_mem_index(struct vm_area_struct *vma)
{
    struct uio_device *idev = vma->vm_private_data;

    if (vma->vm_pgoff < MAX_UIO_MAPS) {
        if (idev->info->mem[vma->vm_pgoff].size == 0)
            return -1;
        return (int)vma->vm_pgoff;
    }
    return -1;
}

看来,offset 是地图索引(如 map0),但不是内存中的位置。

你必须mmap()整个内存。