【问题标题】:Is the "mmap tutorial" incorrect, or does GCC miscompile it?“mmap 教程”不正确,还是 GCC 编译错误?
【发布时间】:2021-12-19 02:24:15
【问题描述】:

这个 15 年前的 mmap tutorial 在 Google 搜索中排名靠前,但它实际上在我的 Linux 系统上运行不正确。

mmap_write.c:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

#define FILEPATH "/tmp/mmapped.bin"
#define NUMINTS  (1000)
#define FILESIZE (NUMINTS * sizeof(int))

int main(int argc, char *argv[])
{
    int i;
    int fd;
    int result;
    int *map;  /* mmapped array of int's */

    /* Open a file for writing.
     *  - Creating the file if it doesn't exist.
     *  - Truncating it to 0 size if it already exists. (not really needed)
     *
     * Note: "O_WRONLY" mode is not sufficient when mmaping.
     */
    fd = open(FILEPATH, O_RDWR | O_CREAT | O_TRUNC, (mode_t)0600);
    if (fd == -1) {
    perror("Error opening file for writing");
    exit(EXIT_FAILURE);
    }

    /* Stretch the file size to the size of the (mmapped) array of ints
     */
    result = lseek(fd, FILESIZE-1, SEEK_SET);
    if (result == -1) {
    close(fd);
    perror("Error calling lseek() to 'stretch' the file");
    exit(EXIT_FAILURE);
    }
    
    /* Something needs to be written at the end of the file to
     * have the file actually have the new size.
     * Just writing an empty string at the current file position will do.
     *
     * Note:
     *  - The current position in the file is at the end of the stretched 
     *    file due to the call to lseek().
     *  - An empty string is actually a single '\0' character, so a zero-byte
     *    will be written at the last byte of the file.
     */
    result = write(fd, "", 1);
    if (result != 1) {
    close(fd);
    perror("Error writing last byte of the file");
    exit(EXIT_FAILURE);
    }

    /* Now the file is ready to be mmapped.
     */
    map = mmap(0, FILESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (map == MAP_FAILED) {
    close(fd);
    perror("Error mmapping the file");
    exit(EXIT_FAILURE);
    }
    
    /* Now write int's to the file as if it were memory (an array of ints).
     */
    for (i = 1; i <=NUMINTS; ++i) {
    map[i] = 2 * i; 
    }

    /* Don't forget to free the mmapped memory
     */
    if (munmap(map, FILESIZE) == -1) {
    perror("Error un-mmapping the file");
    /* Decide here whether to close(fd) and exit() or not. Depends... */
    }

    /* Un-mmaping doesn't close the file, so we still need to do that.
     */
    close(fd);
    return 0;
}

mmap_read.c:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

#define FILEPATH "/tmp/mmapped.bin"
#define NUMINTS  (1000)
#define FILESIZE (NUMINTS * sizeof(int))

int main(int argc, char *argv[])
{
    int i;
    int fd;
    int *map;  /* mmapped array of int's */

    fd = open(FILEPATH, O_RDONLY);
    if (fd == -1) {
    perror("Error opening file for reading");
    exit(EXIT_FAILURE);
    }

    map = mmap(0, FILESIZE, PROT_READ, MAP_SHARED, fd, 0);
    if (map == MAP_FAILED) {
    close(fd);
    perror("Error mmapping the file");
    exit(EXIT_FAILURE);
    }
    
    /* Read the file int-by-int from the mmap
     */
    for (i = 1; i <=NUMINTS; ++i) {
    printf("%d: %d\n", i, map[i]);
    }

    if (munmap(map, FILESIZE) == -1) {
    perror("Error un-mmapping the file");
    }
    close(fd);
    return 0;
}

如果文件不存在,则mmap_read的输出为

...
998: 1996
999: 1998
1000: 2000

但如果是的话,输出是

...
998: 1996
999: 1998
1000: 0

作者应该刷新write吗?还是 GCC 错误编译了代码?


编辑:我注意到文件的先前存在或不存在会有所不同,而不是编译标志。

【问题讨论】:

  • 我缺乏经验,但如果您尝试跳过文件写入缓存,O_DIRECT 不是必需的标志吗?
  • 也许它与 O_SYNC 和 fsync() 有关 stackoverflow.com/a/49462406/823282 (我只是扔飞镖,看看有没有东西粘住)
  • 应该for (i = 1; i &lt;=NUMINTS; ++i)for (i = 0; i &lt;NUMINTS; ++i)
  • 看看这个onlinegdb.com/dHQWvfFUi 我将大小更改为 10 而不是 1000,我更改了写入的值,因此它是字母字符而不是整数,我在文件的结尾。看看它写入文件的内容 - 它看起来像 for (i = 1; i &lt;=NUMINTS; ++i) 从整数 1 而不是整数 0 开始,但由于它转到 &lt;=NUMINTS 它会写入文件末尾 - 覆盖之前放在末尾的东西的文件。如果您更改 for 循环,它仍然会覆盖先前放在文件末尾的字节
  • 正如我在最近的回答中提到的:stackoverflow.com/questions/69813431/… 我会使用ftruncate 而不是lseek/write 来调整/放大文件的大小。这应该在mmap之前完成。执行write 之后mmap 可能 工作,但是,IMO,这是危险的[和不必要的]。

标签: c linux gcc mmap


【解决方案1】:

您从第二个元素开始,并在地图结束后写入 2000。

for (i = 1; i <=NUMINTS; ++i) {
    map[i] = 2 * i; 
}

应该是

for (i = 0; i < NUMINTS; ++i) {
    map[i] = 2 * ( i + 1 ); 
}

Demo

这不是缓冲问题。 write 是系统调用,所以数据直接传递给操作系统。这并不意味着当write 返回时数据已写入磁盘,但它在操作系统手中,因此就操作系统功能而言,就好像它在磁盘上,包括它的内存映射功能。

【讨论】:

  • 是的,我。没有这个变化,我得到了预期的 1998 年。有了这个变化,我得到了预期的 2000 年。
  • write 不缓冲。这是一个系统调用。并不意味着数据已写入磁盘,而是在操作系统手中。
【解决方案2】:

在 C 中,索引从零开始。写和读索引 1000 你调用了未定义的行为

改写为:

   for (i = 1; i <=NUMINTS; ++i) {
       map[i - 1] = 2 * i; 
   }

并阅读:

    for (i = 1; i <=NUMINTS; ++i) {
    printf("%d: %d\n", i, map[i-1]);
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-01
    • 2012-04-08
    • 2017-11-20
    • 1970-01-01
    • 1970-01-01
    • 2012-08-06
    相关资源
    最近更新 更多