C中的Mmap和struct

时间:2014-12-29 22:55:46

标签: c struct mmap

我想用C语言读取和编写mmap的结构。
我有一个名为insert_med的函数,它允许在mmap中插入一个新的struct med ,并且每个struct(带有唯一的 key )必须写在不同的位置数组(当添加新结构时,必须将其添加到数组的最后一个空位置。) 两个结构 med 不能具有相同的,如下面的代码所示。 是唯一的 我的代码不起作用 - 带有变量“struct map”的错误消息:声明时和文件准备好mmapped时 - 但我不知道为什么。我想我可能做错了事。

#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))

struct med{
    int key;
    char name[25];
    int quant_min;
    int quant;
};

insert_med(int argc, char *argv[]){
    int i;
    int fd;
    int result;
    struct map*;  /* mmapped array of structs */

    /* 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 structs
     */
    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 = (struct*)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);
    }

    struct med m;
    struct med s;
    int a=0;

    printf("Key of med: ");
    scanf("%d",&m.key);

    //strcmp return "0" when the two strings are equal
    for (i = 0; i <=NUMINTS; ++i) {

    int a=0;
    map[i]=&s;

    read(fd,&s,1);

    if ((strcmp(&m.key,&s.key))==0){
        a++;    
        printf("Med %d already exits. \n",s.key);    
        break;
    }
    }

    if (a==0){
    printf("Name of med: ");
    scanf("%s",&m.name);
    printf("Quant. min. of med: ");
    scanf("%d",&m.quant_min);
    printf("Quant. of med: ");
    scanf("%d",&m.quant);

    map[i]=&m;

    write(fd,&m,1);
    printf("Med %d saved. \n",m.key);
    }

    /* 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);
}

1 个答案:

答案 0 :(得分:7)

在您的代码中,您有:

int result;
struct map*;  /* mmapped array of structs */

您之前定义了struct med(而不是struct map),并且您尝试定义struct map *类型的变量,但没有给它命名。编译器是抱怨的权利。你可能需要这样的东西:

int result;
struct med *map = 0;  /* mmapped array of struct med */

Julia commented

  

我现在有以下错误:

     
      
  • invalid use of undefined type "struct med"
  •   
  • deferencing pointer to incomplete type
  •   
     

map[i]=&smap[i]=&m

嗯,我看起来并不比第一个错误看得多,但是那里有一个,通常有很多。

例如:

map = (struct*)mmap(0, FILESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

需要将演员表更改为(struct med *)

map[i]=&s;

错了。在语义上,map[i] = s;是正确的(将数据从s复制到映射的数组中) - 但是,在第二个想法,分配是完全错误的。不要这样做;删除该行。

您的FILESIZE应该是sizeof(struct map)的倍数,而不是sizeof(int)的倍数。

read()上的fd充其量是可疑的 - 删除它; mmap()的重点是避免直接文件I / O.

关键值是整数;你没有使用strcmp()来比较它们,你只需使用:

if (m.key == map[i].key)

分配map[i]=&m;又错了,应该删除,而write()是错误的。你有一个数组map,你可以像访问任何其他数组一样访问它,系统会在后台处理I / O.请注意,您应该始终检查read()write()操作是否有效,但是当您删除这些操作时,这会变得毫无意义。

您需要查看如何为map数组分配值,并确保您不要尝试从map数组中读取未初始化的值。

可能还有其他问题;我没有编译,更不用说运行代码了。但是这些评论应该会帮助你。

请注意,编译器的错误消息也应该有所帮助;你需要学习如何解释它们。确保您正在编辑足够的警告并且您正在修复所有警告。如果你使用GCC,那么gcc -Wall -Wextra -Werror -std=c11是一个相当不错的开始(我通常会添加一些关于原型和函数定义的警告 - 一些,但不是全部,其中包含在上面)。请记住,C编译器对C的了解远远超过你职业生涯的这个阶段;小心聆听它的警告,因为你应该认为它是正确的并且你犯了一个错误。这就是我的工作,而且我已经在C中编写了30多年的编码。


工作代码mm7.c

创建的程序是mm7(记忆图7;除了它是素数之外没有任何意义,小而且使名称唯一)。

相当多的代码保留在问题中,而不是从头开始重写。请注意,代码要小心验证输入 - 例如,它会检测并剔除EOF,而不是轻率地继续并且不做任何有用的操作。它包括一个输出循环来打印存储的数据。它使用函数来管理输入的部分。如果所有输入都是在函数中处理的话会更好。

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

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

struct med
{
    int key;
    char name[25];
    int quant_min;
    int quant;
};

static void print_med(const char *tag, const struct med *med)
{
    printf("%s: %4d: Q(%2d, min %2d): %s\n",
           tag, med->key, med->quant, med->quant_min, med->name);
}

static int med_in_map(const struct med *map, int num_meds, int key)
{
    for (int i = 0; i < num_meds; ++i)
    {
        if (key == map[i].key)
        {
            printf("Med %d already exists.\n", key);
            return 1;
        }
    }
    return 0;
}

static int get_new_key(const struct med *map, int num_meds, int *key)
{
    while (printf("Key of med: ") > 0 && scanf("%d", key) == 1)
    {
        if (med_in_map(map, num_meds, *key) == 0)
            return 0;
    }
    return EOF;
}

int main(void)
{
    int fd;
    int result;
    struct med *map;  /* mmapped array of structs */

    /* 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 mmapping.
     */
    fd = open(FILEPATH, O_RDWR | O_CREAT | O_TRUNC, (mode_t)0600);
    if (fd == -1)
    {
        perror("Error opening file for writing");
        exit(EXIT_FAILURE);
    }

    /* NB: ftruncate(fd, FILESIZE); is simpler */
    /* Stretch the file size to the size of the (mmapped) array of structs */
    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 = (struct med *)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);
    }

    /* Input loop */
    int num_meds;
    for (num_meds = 0; num_meds < NUMINTS; num_meds++)
    {
        struct med m;
        memset(&m, '\0', sizeof(m));

        if (get_new_key(map, num_meds, &m.key) == EOF)
            break;

        printf("Name of med: ");
        if (scanf("%s", m.name) != 1)
            break;
        printf("Quant. min. of med: ");
        if (scanf("%d", &m.quant_min) != 1)
            break;
        printf("Quant. of med: ");
        if (scanf("%d", &m.quant) != 1)
            break;

        map[num_meds] = m;

        printf("Med %d saved.\n", m.key);
    }

    /* Output loop */
    printf("\nRecorded meds:\n");
    for (int i = 0; i < num_meds; i++)
    {
        char buffer[32];
        snprintf(buffer, sizeof(buffer), "M%.4d", i);
        print_med(buffer, &map[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-mmapping doesn't close the file, so we still need to do that.  */
    close(fd);

    /* Remove file? */
    /* unlink(FILEPATH); */
}

示例数据输入文件mm7.data

这会模拟终端输入并尝试重复编号。

1 Hydrocontin 3 5
1
2 Paxodontin 1 1
2
37 Ibuprofen 2 12
129 Butoxydione 12 29
4 Placebo 2 22
37
129
4
2
1
9231 Aspirin 99 99

使用数据文件运行示例:

$ ./mm7 < mm7.data
Key of med: Name of med: Quant. min. of med: Quant. of med: Med 1 saved.
Key of med: Med 1 already exists.
Key of med: Name of med: Quant. min. of med: Quant. of med: Med 2 saved.
Key of med: Med 2 already exists.
Key of med: Name of med: Quant. min. of med: Quant. of med: Med 37 saved.
Key of med: Name of med: Quant. min. of med: Quant. of med: Med 129 saved.
Key of med: Name of med: Quant. min. of med: Quant. of med: Med 4 saved.
Key of med: Med 37 already exists.
Key of med: Med 129 already exists.
Key of med: Med 4 already exists.
Key of med: Med 2 already exists.
Key of med: Med 1 already exists.
Key of med: Name of med: Quant. min. of med: Quant. of med: Med 9231 saved.
Key of med: 
Recorded meds:
M0000:    1: Q( 5, min  3): Hydrocontin
M0001:    2: Q( 1, min  1): Paxodontin
M0002:   37: Q(12, min  2): Ibuprofen
M0003:  129: Q(29, min 12): Butoxydione
M0004:    4: Q(22, min  2): Placebo
M0005: 9231: Q(99, min 99): Aspirin
$

每个实际药物的提示都在一行,因为输入是从文件中读取的,而不是从键盘读取的。

数据文件/tmp/mmapped.bin

的十六进制转储
$ odx /tmp/mmapped.bin
0x0000: 01 00 00 00 48 79 64 72 6F 63 6F 6E 74 69 6E 00   ....Hydrocontin.
0x0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0x0020: 03 00 00 00 05 00 00 00 02 00 00 00 50 61 78 6F   ............Paxo
0x0030: 64 6F 6E 74 69 6E 00 00 00 00 00 00 00 00 00 00   dontin..........
0x0040: 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00   ................
0x0050: 25 00 00 00 49 62 75 70 72 6F 66 65 6E 00 00 00   %...Ibuprofen...
0x0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0x0070: 02 00 00 00 0C 00 00 00 81 00 00 00 42 75 74 6F   ............Buto
0x0080: 78 79 64 69 6F 6E 65 00 00 00 00 00 00 00 00 00   xydione.........
0x0090: 00 00 00 00 00 00 00 00 0C 00 00 00 1D 00 00 00   ................
0x00A0: 04 00 00 00 50 6C 61 63 65 62 6F 00 00 00 00 00   ....Placebo.....
0x00B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0x00C0: 02 00 00 00 16 00 00 00 0F 24 00 00 41 73 70 69   .........$..Aspi
0x00D0: 72 69 6E 00 00 00 00 00 00 00 00 00 00 00 00 00   rin.............
0x00E0: 00 00 00 00 00 00 00 00 63 00 00 00 63 00 00 00   ........c...c...
0x00F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
* (2484)
0x9C40:
$

添加了memset()因为hex-dump在数据结构中显示了垃圾 - 无害的垃圾,但仍然是垃圾:

0x0000: 01 00 00 00 48 79 64 72 6F 63 6F 6E 74 69 6E 00   ....Hydrocontin.
0x0010: 2F 62 69 6E 3A 2F 75 73 72 2F 67 6E 75 2F 62 69   /bin:/usr/gnu/bi
0x0020: 03 00 00 00 05 00 00 00 02 00 00 00 50 61 78 6F   ............Paxo
0x0030: 64 6F 6E 74 69 6E 00 00 2F 62 69 6E 3A 2F 75 73   dontin../bin:/us
0x0040: 72 2F 67 6E 75 2F 62 69 01 00 00 00 01 00 00 00   r/gnu/bi........
…

使用以下命令初始化结构:

struct med m = { .key = 0, .name = "", .quant = 0, .quant_min = 0 };

没有完全消除垃圾,因为名字后的结构中有填充字节(让我感到惊讶!):

0x0000: 01 00 00 00 48 79 64 72 6F 63 6F 6E 74 69 6E 00   ....Hydrocontin.
0x0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 2F 62 69   ............./bi
0x0020: 03 00 00 00 05 00 00 00 02 00 00 00 50 61 78 6F   ............Paxo
0x0030: 64 6F 6E 74 69 6E 00 00 00 00 00 00 00 00 00 00   dontin..........
0x0040: 00 00 00 00 00 2F 62 69 01 00 00 00 01 00 00 00   ...../bi........
…

使用非空文件

创建文件时代码不再包含O_TRUNC,但这使得硬编码文件名不太合适(但我还没有修复)。 find_num_entries()函数只是对数据进行线性搜索,以查找列表上次停止的位置。更复杂的代码将通过数据进行二进制搜索,以查找空条目的开始位置。在查找键时还有线性搜索。最好将文件保持在排序顺序,以便也可以使用二进制搜索。实际上,在生产版本中有一个标题记录可能是明智的,它有一个神奇的数字来识别文件类型(如果文件没有以正确的幻数开头,那么它显然不是那个文件。这个程序写了,所以它的内容不应该被解释),并记录其他细节,如条目数和最小和最大键值。

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

#define FILEPATH "/tmp/mmapped.bin"
#define NUM_MEDS (1000)
#define FILESIZE (NUM_MEDS * sizeof(struct med))

struct med
{
    int key;
    char name[25];
    int quant_min;
    int quant;
};

static void print_med(const char *tag, const struct med *med)
{
    printf("%s: %4d: Q(%2d, min %2d): %s\n",
           tag, med->key, med->quant, med->quant_min, med->name);
}

static int med_in_map(const struct med *map, int num_meds, int key)
{
    for (int i = 0; i < num_meds; ++i)
    {
        if (key == map[i].key)
        {
            printf("Med %d already exists.\n", key);
            return 1;
        }
    }
    return 0;
}

static int get_new_key(const struct med *map, int num_meds, int *key)
{
    while (printf("Key of med: ") > 0 && scanf("%d", key) == 1)
    {
        if (med_in_map(map, num_meds, *key) == 0)
            return 0;
    }
    return EOF;
}

static int find_num_entries(const struct med *map, int max_meds)
{
    int i;
    for (i = 0; i < max_meds; i++)
    {
        if (map[i].key == 0)
            break;
    }
    return i;
}

int main(void)
{
    int fd;
    int result;
    struct med *map;

    fd = open(FILEPATH, O_RDWR | O_CREAT, (mode_t)0600);
    if (fd == -1)
    {
        perror("Error opening file for writing");
        exit(EXIT_FAILURE);
    }

    result = ftruncate(fd, FILESIZE);
    if (result == -1)
    {
        close(fd);
        perror("Error calling ftruncate() to 'set' the file size");
        exit(EXIT_FAILURE);
    }

    map = (struct med *)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);
    }

    /* Input loop */
    int num_meds;
    for (num_meds = find_num_entries(map, NUM_MEDS); num_meds < NUM_MEDS; num_meds++)
    {
        struct med m;
        memset(&m, '\0', sizeof(m));

        if (get_new_key(map, num_meds, &m.key) == EOF)
            break;

        printf("Name of med: ");
        if (scanf("%s", m.name) != 1)
            break;
        printf("Quant. min. of med: ");
        if (scanf("%d", &m.quant_min) != 1)
            break;
        printf("Quant. of med: ");
        if (scanf("%d", &m.quant) != 1)
            break;

        map[num_meds] = m;

        printf("Med %d saved.\n", m.key);
    }

    /* Output loop */
    printf("\nRecorded meds:\n");
    for (int i = 0; i < num_meds; i++)
    {
        char buffer[32];
        snprintf(buffer, sizeof(buffer), "M%.4d", i);
        print_med(buffer, &map[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-mmapping doesn't close the file, so we still need to do that.  */
    close(fd);
    return 0;
}

您还可以使用与err_syserr()类似的功能简化错误报告,您可以在我的答案中找到Creating a file with given size