当 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 运行).
特点:
- 董事会:Microzed
- SoC:Zynq 7020
- OS : 佩塔linux 2019.2
- 内核版本:4.19.0-xilinx-v2019.2
- 处理器类型:ARMv7 处理器版本 0 v7l (armv7l)
我有一个在 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/
中出现的映射特征:
- 地址 = 0x40000000
- 偏移量 = 0x0
- 大小 = 0x00008000
我正在编写一个 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
。
除非我在手册页中遗漏了什么,否则所有参数都是有效的(offset
是 sysconf(_SC_PAGE_SIZE)
的倍数,addr
是 NULL
,length
是严格正的并且不太大,并且标志是正确的)。
那么,为什么要使用 EINVAL?...
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()
整个内存。
首先介绍一下我正在使用的机器(这是一个带有 SoC 的 SOM,包括一个 FPGA 和两个 CPU,其中一个有一个 linux OS 运行).
特点:
- 董事会:Microzed
- SoC:Zynq 7020
- OS : 佩塔linux 2019.2
- 内核版本:4.19.0-xilinx-v2019.2
- 处理器类型:ARMv7 处理器版本 0 v7l (armv7l)
我有一个在 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/
中出现的映射特征:
- 地址 = 0x40000000
- 偏移量 = 0x0
- 大小 = 0x00008000
我正在编写一个 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
。
除非我在手册页中遗漏了什么,否则所有参数都是有效的(offset
是 sysconf(_SC_PAGE_SIZE)
的倍数,addr
是 NULL
,length
是严格正的并且不太大,并且标志是正确的)。
那么,为什么要使用 EINVAL?...
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()
整个内存。