Linux C语言 在mmap映射里面玩链表

一、mmap的作用

mmap函数其实就是把物理地址映射到虚拟地址,可以由用户空间的进程进行操作,起到一个虚实地址转换的作用;

举个简单的例子,有一些记录很容易变,并且在程序退出后又想保留下来,那么这时候就可以用到mmap了,重启之后直接加载mmap中的数据就可以了。

二、mmap函数的使用

大家可以直接在ubantu下面 man mmap进行查看

如下为部分内容

NAME
mmap, munmap - map or unmap files or devices into memory

SYNOPSIS

       #include <sys/mman.h>
       void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
       int munmap(void *addr, size_t length);

mmap函数整理

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数void *addr
//地址
null //创建的映射地址由内核选择
不为null //如果 addr 不是 null,那么内核将其作为映射位置的提示; 在 linux 上,映射将在附近的页面边界创建
size_t length
//映射空间大小
int prot
//描述映射所需的内存保护
PROT_EXEC :页内容可以被执行
PROT_READ :页内容可以被读取
PROT_WRITE :页可以被写入
PROT_NONE :页不可访问
int flags
见flag列表
int fd
有效的文件描述词
off_t offset
被映射对象内容的起点
返回值成功执行时,mmap()返回被映射区的指针。失败时,mmap()返回MAP_FAILED[其值为(void *)-1]
错误值EACCES访问出错
EAGAIN文件已被锁定,或者太多的内存已被锁定
EBADF fd不是有效的文件描述词
EINVAL一个或者多个参数无效
ENFILE已达到系统对打开文件的限制
ENODEV指定文件所在的文件系统不支持内存映射
ENOMEM内存不足,或者进程已超出最大内存映射数量
EPERM权能不足,操作不允许
ETXTBSY已写的方式打开文件,同时指定MAP_DENYWRITE标志
SIGSEGV试着向只读区写入
SIGBUS试着访问不属于进程的内存区
标记含义
MAP_FIXED//使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
MAP_SHARED//与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
MAP_PRIVATE//建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
MAP_DENYWRITE//这个标志被忽略。
MAP_EXECUTABLE//同上
MAP_NORESERVE//不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
MAP_LOCKED//锁定映射区的页面,从而防止页面被交换出内存。
MAP_GROWSDOWN//用于堆栈,告诉内核VM系统,映射区可以向下扩展。
MAP_ANONYMOUS//匿名映射,映射区不与任何文件关联。
MAP_ANON//MAP_ANONYMOUS的别称,不再被使用。
MAP_FILE//兼容标志,被忽略。
MAP_32BIT//将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
MAP_POPULATE//为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
MAP_NONBLOCK//仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。

munmap函数

int munmap( void * addr, size_t len )

成功执行时,munmap()返回0。失败时,munmap返回-1,error返回标志和mmap一致;

该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小;

当映射关系解除后,对原来映射地址的访问将导致段错误发生。

三、mmap编码

1、应用说明

mmap只能开劈连续的空间进行映射,那么如何利用链表的方式进行存储呢?

用普通的指针肯定是不行的,原因如下,
如果结构体里面有一个存储地址的指针,在不重启软件的情况下都是没有问题的,但是一旦重启了程序,之前指针所存的地址就无效了,那么链表也就失效了。(本人亲测)

但是有时候又必须要用到链表,所以我经过一番思考,进行了一些自己的设计

在结构体中加入三个int值,为什么是int值看完就知道了。

//存储结构体
struct mmap_st {
    int value; //存储内容
    char text[20]; //存储内容
    int node; //当前节点
    int prev; //上一个节点
    int next; //下一个节点
};

结构体中的value text可以随意修改,但是要注意的是 字符串不能是指针,一定要是固定大小的。

如果你的设计是单向链表,那么只需要node和next就可以了。由于我的应用是双向链表所以就多了prev。

这里有另一个在使用过程中用到的结构体

struct mmap_attribute {
    struct mmap_st* mmap_addr; //mmap存放的首地址
    struct mmap_st* front; //头结点
    struct mmap_st* now; //当前结点
    struct mmap_st* rear; //尾结点
    int mmap_num; //队列数目
};

为什么存int值呢,首先我们需要先看一下mmap的创建

#define MMAP_FILE "/tmp/mmap" //mmap文件路径
#define MAX_LENGTH 200 //存储结构体数量

/******************************************* 
 * @描述: 创建mmap
 * @输入: 数据结点
 * @输出: 数据结点
 * @返回: 成功0/失败-1
 * @其他: 
 * @param {mmap_attribute*} mmap_info
 *******************************************/
int create_mmap(struct mmap_attribute* mmap_info)
{
    int fd = -1;
    int ret = -1;
    fd = open(MMAP_FILE, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU);
    if (-1 == fd) {
        printf("can not open %s", MMAP_FILE);
        return -1;
    }

    lseek(fd, MAX_LENGTH * sizeof(struct mmap_st), SEEK_SET);
    ret = write(fd, "\0", 1);
    if (ret == -1) {
        printf("the file %s write error", MMAP_FILE);
        return -1;
    }

    lseek(fd, 0, SEEK_SET);
    mmap_info->mmap_addr = (struct mmap_st*)mmap(NULL, MAX_LENGTH * sizeof(struct mmap_st) + 1, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if ((struct mmap_st*)-1 == mmap_info->mmap_addr) {
        printf("mmap failed");
        close(fd);
        return -1;
    }

    first_initialize_mmap(mmap_info);

    printf("create mmap success");
    close(fd);
    return 0;
}

/******************************************* 
 * @描述: 创建文件时对mmap文件进行初始化
 * @输入: 补遗队列信息
 * @输出: 无
 * @返回: 无
 * @其他: 内部函数不提供接口
 * @param {struct} mmap_attribute
 *******************************************/
static void first_initialize_mmap(struct mmap_attribute* mmap_info)
{
    int i = 0;
    mmap_info->front = mmap_info->rear = mmap_info->mmap_addr;
    mmap_info->now = NULL;
    mmap_info->mmap_num = 0;
    //将所有的 prev node next 都置为-1
    for (i = 0; i < MAX_LENGTH; i++) {
        (mmap_info->mmap_addr + i)->prev = -1;
        (mmap_info->mmap_addr + i)->node = -1;
        (mmap_info->mmap_addr + i)->next = -1;
    }
}

我们可以看到mmap函数创建时返回的是映射的首地址假设为 addr ,其实就是结构体数组的第一个结构体的地址,那么mmap中的第n个元素其实就存在 (addr+n)中,所以其实不管我们打开几次文件,不管文件的逻辑地址如何变化,结构体元素中的地址相较于首地址都不变。那么我们完全只需要用一个int值来存储偏移量就足够了。

在这里的 node 为 -1 时表示当钱地址没有存储数据,如果不为 -1 存储的是当前位置相对于首地址的偏移量。
next 为 -1 时表示没有下一个节点,不为-1时表示下一个节点相对于首地址的偏移量
prev为-1时表示没有上一个节点,不为-1时表示上一个节点相对于首地址的偏移量

至此三个参数的作用描述完毕,这样就可以在mmap中使用链表了。

2、部分功能实现函数

先说一下这个可以访问全部变量的结构体

struct mmap_attribute {
    struct mmap_st* mmap_addr; //mmap存放的首地址
    struct mmap_st* front; //头结点
    struct mmap_st* now; //当前结点
    struct mmap_st* rear; //尾结点
    int mmap_num; //队列数目
};

有了mmap_addr就可以访问映射里的参数
上面已经有了创建的函数,下面是读取的函数

读 read_mmap

/******************************************* 
 * @描述: 加载列表数据
 * @输入: 映射文件首地址,数据头结点,文件大小
 * @输出: 映射文件首地址,数据头结点
 * @返回: 成功0/失败-1
 * @其他: 
 * @param {struct} mmap_attribute
 * @param {size_t} file_size
 *******************************************/
int read_mmap(struct mmap_attribute* mmap_info, size_t file_size)
{
    int fd = -1;
    int ret = -1;
    fd = open(MMAP_FILE, O_RDWR | O_CREAT, S_IRWXU);
    if (-1 == fd) {
        printf("can not open %s", MMAP_FILE);
        return -1;
    }

    lseek(fd, file_size - 1, SEEK_SET);
    ret = write(fd, "\0", 1);
    if (ret == -1) {
        printf("the file %s write error", MMAP_FILE);
        return -1;
    }
    lseek(fd, 0, SEEK_SET);

    mmap_info->mmap_addr = (struct mmap_st*)mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if ((struct mmap_st*)-1 == mmap_info->mmap_addr) {
        printf("mmap mmap failed");
        close(fd);
        return -1;
    }

    read_initalize_mmap(mmap_info);

    printf("read mmap mmap success");
    close(fd);
    return 0;
}

/******************************************* 
 * @描述: 读取文件初始化mmap队列
 * @输入: 队列信息
 * @输出: 初始化队列信息
 * @返回: 无
 * @其他: 内部函数不提供接口
 * @param {struct} mmap_attribute
 *******************************************/
static void read_initalize_mmap(struct mmap_attribute* mmap_info)
{
    struct mmap_st* pnode;
    //找到头结点
    mmap_info->front = find_front_node(mmap_info->mmap_addr);

    if (mmap_info->front->node == -1) {
        mmap_info->mmap_num = 0;
    } else {
        for (pnode = mmap_info->front; pnode; pnode = ((pnode->next == -1) ? NULL : (mmap_info->mmap_addr + pnode->next))) {
            mmap_info->mmap_num++;
        }
    }
    mmap_info->now = NULL;
    mmap_info->rear = find_rear_node(mmap_info->mmap_addr);
}

/******************************************* 
 * @描述: 获取mmap队列中头结点
 * @输入: mmap首地址
 * @输出: mmap队列头结点
 * @返回: mmap队列头结点
 * @其他: 
 * @param {conststruct} mmap_st
 *******************************************/
struct mmap_st* find_front_node(struct mmap_st* mmap_addr)
{
    int i = 0;
    struct mmap_st* front = NULL;
    for (i = 0; i < MAX_LENGTH; i++) {
    	//没有上一个节点并且当前节点不为空的就是头结点
        if ((mmap_addr + i)->prev == -1 && (mmap_addr + i)->node != -1) {
            front = mmap_addr + i;
        }
    }
    //如果没找到头节点就是首地址
    if (!front) {
        front = mmap_addr;
    }
    return front;
}

/******************************************* 
 * @描述: 获取mmap队列中尾结点
 * @输入: mmap首地址
 * @输出: mmap队列尾结点
 * @返回: mmap队列尾结点
 * @其他: 
 * @param {conststruct} mmap_st
 *******************************************/
struct mmap_st* find_rear_node(struct mmap_st* mmap_addr)
{
    int i = 0;
    struct mmap_st* rear = NULL;
    for (i = 0; i < MAX_LENGTH; i++) {
    	//没有下一个节点并且当前节点不为空的就是尾结点
        if ((mmap_addr + i)->next == -1 && (mmap_addr + i)->node != -1) {
            rear = mmap_addr + i;
        }
    }
    //如果没找到尾结点就是首地址
    if (!rear) {
        rear = mmap_addr;
    }
    return rear;
}

mmap_num 是一个很有用的参数,如以下函数可以用到

/******************************************* 
 * @描述: 判断队列是否满
 * @输入: 补遗队列属性结构体
 * @输出: 队列是否满的信息
 * @返回: 是ture/否false
 * @其他: 
 * @param {conststruct} mmap_attribute
 *******************************************/
bool mmap_is_full(const struct mmap_attribute* mmap_info)
{
    return mmap_info->mmap_num == MAX_LENGTH;
}

/******************************************* 
 * @描述: 判断队列是否为空
 * @输入: 队列属性结构体
 * @输出: 队列是否空的信息
 * @返回: 是ture/否false
 * @其他: 
 * @param {conststruct} mmap_attribute
 *******************************************/
bool mmap_is_empty(const struct mmap_attribute* mmap_info)
{
    return mmap_info->mmap_num == 0;
}

/******************************************* 
 * @描述: 输出队列中数量
 * @输入: 队列属性结构体
 * @输出: 队列剩余数量
 * @返回: 数量值
 * @其他: 
 * @param {conststruct} mmap_attribute
 *******************************************/
int mmap_count(const struct mmap_attribute* mmap_info)
{
    return mmap_info->mmap_num;
}

/******************************************* 
 * @描述: 添加mmap链表
 * @输入: mmap_item映射结构 mmap_info映射信息
 * @输出: 
 * @返回: 成功ture/失败false
 * @其他: 
 * @param {struct mmap_st} mmap_item
 * @param {struct} mmap_attribute
 *******************************************/
bool add_mmap(struct mmap_st mmap_item, struct mmap_attribute* mmap_info)
{
    //最小空地址
    int minimum_empty_address = -1;
    minimum_empty_address = find_minimum_empty_address(mmap_info);
    printf("minimum_empty_address = %d\n", minimum_empty_address);
    if (minimum_empty_address == -1) {
        printf("add_mmap fail");
        return false;
    }
    copy_to_node(mmap_item, mmap_info->mmap_addr + minimum_empty_address);
    (mmap_info->mmap_addr + minimum_empty_address)->prev = -1;
    if (mmap_info->rear->node != -1) {
        mmap_info->rear->next = minimum_empty_address;
        (mmap_info->mmap_addr + minimum_empty_address)->prev = mmap_info->rear->node;
    }
    (mmap_info->mmap_addr + minimum_empty_address)->node = minimum_empty_address;
    (mmap_info->mmap_addr + minimum_empty_address)->next = -1;
    //更新尾节点
    mmap_info->rear = mmap_info->mmap_addr + minimum_empty_address;

    mmap_info->mmap_num++;

    return true;
}

/******************************************* 
 * @描述: 获取队列最小的空地址
 * @输入: 队列信息
 * @输出: 最小空地址值
 * @返回: -1队列已满
 * @其他: 
 * @param {struct} mmap_attribute
 *******************************************/
int find_minimum_empty_address(struct mmap_attribute* mmap_info)
{
    int i = 0;
    int minimum_empty_address = -1;
    if (mmap_is_full(mmap_info)) {
        printf("supply mmap is full");
        return -1;
    }
    for (i = 0; i < MAX_LENGTH; i++) {
        if ((mmap_info->mmap_addr + i)->node == -1) {
            minimum_empty_address = i;
            break;
        }
    }
    return minimum_empty_address;
}

/******************************************* 
 * @描述: 给节点赋值
 * @输入: mmap_item 赋值结构体,mmap_info 赋值节点
 * @输出: 
 * @返回: 无
 * @其他: 内部函数,不提供接口
 * @param {struct mmap_st} mmap_item
 * @param {struct} mmap_st
 *******************************************/
static void copy_to_node(struct mmap_st mmap_item, struct mmap_st* mmap_info)
{
	//这里函数用static是不需要在头文件里面声明直接在上面声明,但是本文章为了大家使用方便都放在一个c文件中了
    mmap_info->value = mmap_item.value;
    strcpy(mmap_info->text, mmap_item.text);
}

增加一条随机的队列(测试用)

const unsigned char allChar[63] ="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";


/******************************************* 
 * @描述: 添加一条随机记录
 * @输入: 
 * @输出: 
 * @返回: 
 * @其他: 
 * @param {mmap_attribute*} mmap_info
 * @param {time_t} time_now
 *******************************************/
void add_one_random_schedule(struct mmap_attribute* mmap_info, time_t time_now)
{
    struct mmap_st mmap_item;
    srand(time_now);
    mmap_item.value = rand() % 500;
    generateString(mmap_item.text, 15, time_now);
    add_mmap(mmap_item, mmap_info);
}

/******************************************* 
 * @描述: 获取随机字符串
 * @输入: 字符串地址,字符串长度,时间(种子)
 * @输出: 随机字符串
 * @返回: 
 * @其他: 
 * @param {unsigned char*} dest
 * @param {unsigned int} len
 * @param {time_t} time_now
 *******************************************/
void generate_string(unsigned char* dest, const unsigned int len, time_t time_now)
{
    unsigned int cnt, randNo;
    srand(time_now);

    for (cnt = 0; cnt < len; cnt++) {
        randNo = rand() % 62;
        *dest = allChar[randNo];
        dest++;
    }

    *dest = '\0';
}

/******************************************* 
 * @描述: 清空mmap
 * @输入: 
 * @输出: 
 * @返回: 
 * @其他: 
 * @param {mmap_attribute*} mmap_info
 *******************************************/
void delete_all_node(struct mmap_attribute* mmap_info)
{
    while (mmap_info->front->node != -1) {
        delete_node(mmap_info->front, mmap_info);
    }
}

/******************************************* 
 * @描述: 删除第一条
 * @输入: 
 * @输出: 
 * @返回: 
 * @其他: 
 * @param {mmap_attribute*} mmap_info
 *******************************************/
void delete_front_node(struct mmap_attribute* mmap_info)
{
    struct mmap_st* mmap_node;
    int node = 0;
    mmap_node = mmap_info->front;
    if (mmap_is_empty(mmap_info)) {
        printf("mmap中无数据\n");
    } else {
        node = mmap_node->node;
        delete_node(mmap_node, mmap_info);
        printf("删除了mmap中第 %d 条数据\n", node + 1);
    }
}

/******************************************* 
 * @描述: 打印信息
 * @输入: 补遗结构
 * @输出: 
 * @返回: 
 * @其他: 
 * @param {struct} mmap_attribute
 *******************************************/
void mmap_printf(struct mmap_attribute* mmap_info)
{
    struct mmap_st* mmap_node;
    int flag = 1;
    int i = 1;
    printf("---------------  mmap_info  ---------------\n");
    if (mmap_is_empty(mmap_info)) {
        printf("no message in mmap\n");
    } else {
        for (mmap_node = mmap_info->front; mmap_node;
             mmap_node = ((mmap_node->next == -1) ? NULL : (mmap_info->mmap_addr + mmap_node->next))) {
            if (flag == 1) {
                printf("id|value|text\n");
                flag = 0;
            }
            printf("%d|%d|%s\n", i, mmap_node->value, mmap_node->text);
            i++;
        }
    }
    printf("---------------mmap_info end---------------\n");
}

四、测试用例

主函数

int main()
{
    struct mmap_attribute mmap_info;
    //文件结构体
    struct stat buf;
    int ret = 0;
    int i = 0, n = 0;
    int chose_num = 1;
    
    //初始化文件结构体大小,避免找不到文件该值为随机值
    buf.st_size = 0;
    stat(MMAP_FILE, &buf);
    if (buf.st_size == 0) {
        ret = create_mmap(&mmap_info);
    } else {
        ret = read_mmap(&mmap_info, buf.st_size);
        if (ret == 0) {
            printf("读取成功\n");
        }
    }

    
    while (chose_num != 0) {
        printf("\n0:退出\n1:查看mmap\n2:插入n条随机记录\n3:清空mmap\n4:删除第一条\n");
        printf("请选择:");
        scanf("%d", &chose_num);
        switch (chose_num) {
        case 0:
            break;
        case 1:
            mmap_printf(&mmap_info);
            break;
        case 2:
            printf("请输入n的值:");
            scanf("%d", &n);
            for (i = 0; i < n; i++) {
                if (mmap_is_full(&mmap_info)) {
                    printf("mmap_is_full\n");
                    break;
                }
                add_one_random_schedule(&mmap_info, time(NULL) - n + i);
            }
            printf("添加了 %d 条\n", i);
            break;
        case 3:
            delete_all_node(&mmap_info);
            break;
        case 4:
            delete_front_node(&mmap_info);
            break;
        default:
            printf("选错了,重选,(●'◡'●)\n");
            break;
        }
        getchar();
        if (chose_num == 0) {
            printf("回车退出");
        } else {
            printf("回车继续选择");
        }
        getchar();
    }
    printf("已退出\n");

    return 0;
}

编译

gcc mmap_use.c -o mmap_test

运行测试

第一次运行
在这里插入图片描述
查看
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里value为随机数0-499;text为15位的随机字符串 排序顺序为从头到尾

删除前两条

在这里插入图片描述

这里的第几条是相对于首地址的偏移量

再插入五条数据
在这里插入图片描述
可以看到添加的五条数据是哪几个地址

打印可以看到目前第三条是头结点
在这里插入图片描述
清空队列
在这里插入图片描述
退出
在这里插入图片描述

漏了一个,加载测试

在这里插入图片描述
先插入,然后在退出
在这里插入图片描述
然后在加载读取
在这里插入图片描述
可以正确加载

测试完毕

五、完整代码

ps:供我自己以后学习参考,或者小伙伴们使用

/******************************************* 
 * @Description  : 
 * @Author       : liutt
 * @Date         : 2021-09-14 16:13:56
 * @LastEditTime : 2021-09-16 14:00:30
 * @LastEditors  : liutt
 *******************************************/
#include <fcntl.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define MMAP_FILE "/tmp/mmap" //mmap文件路径
#define MAX_LENGTH 200 //存储结构体数量

const unsigned char allChar[63] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

//存储结构体
struct mmap_st {
    int value; //存储内容
    char text[20]; //存储内容
    int node; //当前节点
    int prev; //上一个节点
    int next; //下一个节点
};

struct mmap_attribute {
    struct mmap_st* mmap_addr; //mmap存放的首地址
    struct mmap_st* front; //头结点
    struct mmap_st* now; //当前结点
    struct mmap_st* rear; //尾结点
    int mmap_num; //队列数目
};

int create_mmap(struct mmap_attribute* mmap_info);
int read_mmap(struct mmap_attribute* mmap_info, size_t file_size);
bool mmap_is_full(const struct mmap_attribute* mmap);
bool mmap_is_empty(const struct mmap_attribute* mmap_info);
int mmap_count(const struct mmap_attribute* mmap_info);
static void first_initialize_mmap(struct mmap_attribute* mmap_info);
struct mmap_st* find_front_node(struct mmap_st* mmap_addr);
struct mmap_st* find_rear_node(struct mmap_st* mmap_addr);
static void read_initalize_mmap(struct mmap_attribute* mmap_info);
int find_minimum_empty_address(struct mmap_attribute* mmap_info);
static void copy_to_node(struct mmap_st mmap_item, struct mmap_st* mmap_info);
bool add_mmap(struct mmap_st mmap_item, struct mmap_attribute* mmap_info);
void delete_node(struct mmap_st* mmap_item, struct mmap_attribute* mmap_info);
static void clear_out_node(struct mmap_st* mmap_info);
void mmap_printf(struct mmap_attribute* mmap_info);
void generate_string(unsigned char* dest, const unsigned int len, time_t time_now);
void add_one_random_schedule(struct mmap_attribute* mmap_info, time_t time_now);
void delete_all_node(struct mmap_attribute* mmap_info);
void delete_front_node(struct mmap_attribute* mmap_info);

/******************************************* 
 * @描述: 创建mmap
 * @输入: 数据结点
 * @输出: 数据结点
 * @返回: 成功0/失败-1
 * @其他: 
 * @param {mmap_attribute*} mmap_info
 *******************************************/
int create_mmap(struct mmap_attribute* mmap_info)
{
    int fd = -1;
    int ret = -1;
    fd = open(MMAP_FILE, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU);
    if (-1 == fd) {
        printf("can not open %s", MMAP_FILE);
        return -1;
    }

    lseek(fd, MAX_LENGTH * sizeof(struct mmap_st), SEEK_SET);
    ret = write(fd, "\0", 1);
    if (ret == -1) {
        printf("the file %s write error", MMAP_FILE);
        return -1;
    }

    lseek(fd, 0, SEEK_SET);
    mmap_info->mmap_addr = (struct mmap_st*)mmap(NULL, MAX_LENGTH * sizeof(struct mmap_st) + 1, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if ((struct mmap_st*)-1 == mmap_info->mmap_addr) {
        printf("mmap failed");
        close(fd);
        return -1;
    }

    first_initialize_mmap(mmap_info);

    printf("create mmap success");
    close(fd);
    return 0;
}

/******************************************* 
 * @描述: 加载列表数据
 * @输入: 映射文件首地址,数据头结点,文件大小
 * @输出: 映射文件首地址,数据头结点
 * @返回: 成功0/失败-1
 * @其他: 
 * @param {struct} mmap_attribute
 * @param {size_t} file_size
 *******************************************/
int read_mmap(struct mmap_attribute* mmap_info, size_t file_size)
{
    int fd = -1;
    int ret = -1;
    fd = open(MMAP_FILE, O_RDWR | O_CREAT, S_IRWXU);
    if (-1 == fd) {
        printf("can not open %s", MMAP_FILE);
        return -1;
    }

    lseek(fd, file_size - 1, SEEK_SET);
    ret = write(fd, "\0", 1);
    if (ret == -1) {
        printf("the file %s write error", MMAP_FILE);
        return -1;
    }
    lseek(fd, 0, SEEK_SET);

    mmap_info->mmap_addr = (struct mmap_st*)mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if ((struct mmap_st*)-1 == mmap_info->mmap_addr) {
        printf("mmap mmap failed");
        close(fd);
        return -1;
    }

    read_initalize_mmap(mmap_info);

    printf("read mmap mmap success");
    close(fd);
    return 0;
}

/******************************************* 
 * @描述: 判断队列是否满
 * @输入: 队列属性结构体
 * @输出: 队列是否满的信息
 * @返回: 是ture/否false
 * @其他: 
 * @param {conststruct} mmap_attribute
 *******************************************/
bool mmap_is_full(const struct mmap_attribute* mmap_info)
{
    return mmap_info->mmap_num == MAX_LENGTH;
}

/******************************************* 
 * @描述: 判断队列是否为空
 * @输入: 队列属性结构体
 * @输出: 队列是否空的信息
 * @返回: 是ture/否false
 * @其他: 
 * @param {conststruct} mmap_attribute
 *******************************************/
bool mmap_is_empty(const struct mmap_attribute* mmap_info)
{
    return mmap_info->mmap_num == 0;
}

/******************************************* 
 * @描述: 输出队列中数量
 * @输入: 队列属性结构体
 * @输出: 队列剩余数量
 * @返回: 数量值
 * @其他: 
 * @param {conststruct} mmap_attribute
 *******************************************/
int mmap_count(const struct mmap_attribute* mmap_info)
{
    return mmap_info->mmap_num;
}

/******************************************* 
 * @描述: 创建文件时对mmap文件进行初始化
 * @输入: 队列信息
 * @输出: 无
 * @返回: 无
 * @其他: 内部函数不提供接口
 * @param {struct} mmap_attribute
 *******************************************/
static void first_initialize_mmap(struct mmap_attribute* mmap_info)
{
    int i = 0;
    mmap_info->front = mmap_info->rear = mmap_info->mmap_addr;
    mmap_info->now = NULL;
    mmap_info->mmap_num = 0;
    //将所有的 prev node next 都置为-1
    for (i = 0; i < MAX_LENGTH; i++) {
        (mmap_info->mmap_addr + i)->prev = -1;
        (mmap_info->mmap_addr + i)->node = -1;
        (mmap_info->mmap_addr + i)->next = -1;
    }
}

/******************************************* 
 * @描述: 获取mmap队列中头结点
 * @输入: mmap首地址
 * @输出: mmap队列头结点
 * @返回: mmap队列头结点
 * @其他: 
 * @param {conststruct} mmap_st
 *******************************************/
struct mmap_st* find_front_node(struct mmap_st* mmap_addr)
{
    int i = 0;
    struct mmap_st* front = NULL;
    for (i = 0; i < MAX_LENGTH; i++) {
        if ((mmap_addr + i)->prev == -1 && (mmap_addr + i)->node != -1) {
            front = mmap_addr + i;
        }
    }
    if (!front) {
        front = mmap_addr;
    }
    return front;
}

/******************************************* 
 * @描述: 获取mmap队列中尾结点
 * @输入: mmap首地址
 * @输出: mmap队列尾结点
 * @返回: mmap队列尾结点
 * @其他: 
 * @param {conststruct} mmap_st
 *******************************************/
struct mmap_st* find_rear_node(struct mmap_st* mmap_addr)
{
    int i = 0;
    struct mmap_st* rear = NULL;
    for (i = 0; i < MAX_LENGTH; i++) {
        if ((mmap_addr + i)->next == -1 && (mmap_addr + i)->node != -1) {
            rear = mmap_addr + i;
        }
    }
    if (!rear) {
        rear = mmap_addr;
    }
    return rear;
}

/******************************************* 
 * @描述: 读取文件初始化mmap队列
 * @输入: 队列信息
 * @输出: 初始化队列信息
 * @返回: 无
 * @其他: 内部函数不提供接口
 * @param {struct} mmap_attribute
 *******************************************/
static void read_initalize_mmap(struct mmap_attribute* mmap_info)
{
    struct mmap_st* pnode;
    //找到头结点
    mmap_info->front = find_front_node(mmap_info->mmap_addr);

    if (mmap_info->front->node == -1) {
        mmap_info->mmap_num = 0;
    } else {
        for (pnode = mmap_info->front; pnode; pnode = ((pnode->next == -1) ? NULL : (mmap_info->mmap_addr + pnode->next))) {
            mmap_info->mmap_num++;
        }
    }
    mmap_info->now = NULL;
    mmap_info->rear = find_rear_node(mmap_info->mmap_addr);
}

/******************************************* 
 * @描述: 获取队列最小的空地址
 * @输入: 队列信息
 * @输出: 最小空地址值
 * @返回: -1队列已满
 * @其他: 
 * @param {struct} mmap_attribute
 *******************************************/
int find_minimum_empty_address(struct mmap_attribute* mmap_info)
{
    int i = 0;
    int minimum_empty_address = -1;
    if (mmap_is_full(mmap_info)) {
        printf("supply mmap is full");
        return -1;
    }
    for (i = 0; i < MAX_LENGTH; i++) {
        if ((mmap_info->mmap_addr + i)->node == -1) {
            minimum_empty_address = i;
            break;
        }
    }
    return minimum_empty_address;
}

/******************************************* 
 * @描述: 给节点赋值
 * @输入: mmap_item 赋值结构体,mmap_info 赋值节点
 * @输出: 
 * @返回: 无
 * @其他: 内部函数,不提供接口
 * @param {struct mmap_st} mmap_item
 * @param {struct} mmap_st
 *******************************************/
static void copy_to_node(struct mmap_st mmap_item, struct mmap_st* mmap_info)
{
    mmap_info->value = mmap_item.value;
    strcpy(mmap_info->text, mmap_item.text);
}

/******************************************* 
 * @描述: 添加mmap链表
 * @输入: mmap_item补遗结构 mmap_info补遗信息
 * @输出: 
 * @返回: 成功ture/失败false
 * @其他: 
 * @param {struct mmap_st} mmap_item
 * @param {struct} mmap_attribute
 *******************************************/
bool add_mmap(struct mmap_st mmap_item, struct mmap_attribute* mmap_info)
{
    //最小空地址
    int minimum_empty_address = -1;
    minimum_empty_address = find_minimum_empty_address(mmap_info);
    printf("minimum_empty_address = %d\n", minimum_empty_address);
    if (minimum_empty_address == -1) {
        printf("add_mmap fail");
        return false;
    }
    copy_to_node(mmap_item, mmap_info->mmap_addr + minimum_empty_address);
    (mmap_info->mmap_addr + minimum_empty_address)->prev = -1;
    if (mmap_info->rear->node != -1) {
        mmap_info->rear->next = minimum_empty_address;
        (mmap_info->mmap_addr + minimum_empty_address)->prev = mmap_info->rear->node;
    }
    (mmap_info->mmap_addr + minimum_empty_address)->node = minimum_empty_address;
    (mmap_info->mmap_addr + minimum_empty_address)->next = -1;
    //更新尾节点
    mmap_info->rear = mmap_info->mmap_addr + minimum_empty_address;

    mmap_info->mmap_num++;

    return true;
}

void delete_node(struct mmap_st* mmap_item, struct mmap_attribute* mmap_info)
{
    if (mmap_item->prev != -1) {
        (mmap_info->mmap_addr + mmap_item->prev)->next = mmap_item->next;
    }
    if (mmap_item->next != -1) {
        (mmap_info->mmap_addr + mmap_item->next)->prev = mmap_item->prev;
    }
    mmap_info->mmap_num--;
    clear_out_node(mmap_item);
    mmap_info->front = find_front_node(mmap_info->mmap_addr);
    mmap_info->rear = find_rear_node(mmap_info->mmap_addr);
}

/******************************************* 
 * @描述: 清空某个node的值
 * @输入: mmap_info 补遗结点
 * @输出: 无
 * @返回: 无
 * @其他: 
 * @param {struct} mmap_st
 *******************************************/
static void clear_out_node(struct mmap_st* mmap_info)
{
    mmap_info->value = 0;
    strcpy(mmap_info->text, "");
    mmap_info->node = -1;
    mmap_info->prev = -1;
    mmap_info->next = -1;
}

/******************************************* 
 * @描述: 打印补遗信息
 * @输入: 补遗结构
 * @输出: 
 * @返回: 
 * @其他: 
 * @param {struct} mmap_attribute
 *******************************************/
void mmap_printf(struct mmap_attribute* mmap_info)
{
    struct mmap_st* mmap_node;
    int flag = 1;
    int i = 1;
    printf("---------------  mmap_info  ---------------\n");
    if (mmap_is_empty(mmap_info)) {
        printf("no message in mmap\n");
    } else {
        for (mmap_node = mmap_info->front; mmap_node;
             mmap_node = ((mmap_node->next == -1) ? NULL : (mmap_info->mmap_addr + mmap_node->next))) {
            if (flag == 1) {
                printf("id|value|text\n");
                flag = 0;
            }
            printf("%d|%d|%s\n", i, mmap_node->value, mmap_node->text);
            i++;
        }
    }
    printf("---------------mmap_info end---------------\n");
}

/******************************************* 
 * @描述: 获取随机字符串
 * @输入: 字符串地址,字符串长度,时间(种子)
 * @输出: 随机字符串
 * @返回: 
 * @其他: 
 * @param {unsigned char*} dest
 * @param {unsigned int} len
 * @param {time_t} time_now
 *******************************************/
void generate_string(unsigned char* dest, const unsigned int len, time_t time_now)
{
    unsigned int cnt, randNo;
    srand(time_now);

    for (cnt = 0; cnt < len; cnt++) {
        randNo = rand() % 62;
        *dest = allChar[randNo];
        dest++;
    }

    *dest = '\0';
}

/******************************************* 
 * @描述: 添加一条随机记录
 * @输入: 
 * @输出: 
 * @返回: 
 * @其他: 
 * @param {mmap_attribute*} mmap_info
 * @param {time_t} time_now
 *******************************************/
void add_one_random_schedule(struct mmap_attribute* mmap_info, time_t time_now)
{
    struct mmap_st mmap_item;
    srand(time_now);
    mmap_item.value = rand() % 500;
    generate_string(mmap_item.text, 15, time_now);
    add_mmap(mmap_item, mmap_info);
}

/******************************************* 
 * @描述: 清空mmap
 * @输入: 
 * @输出: 
 * @返回: 
 * @其他: 
 * @param {mmap_attribute*} mmap_info
 *******************************************/
void delete_all_node(struct mmap_attribute* mmap_info)
{
    while (mmap_info->front->node != -1) {
        delete_node(mmap_info->front, mmap_info);
    }
}

/******************************************* 
 * @描述: 删除第一条
 * @输入: 
 * @输出: 
 * @返回: 
 * @其他: 
 * @param {mmap_attribute*} mmap_info
 *******************************************/
void delete_front_node(struct mmap_attribute* mmap_info)
{
    struct mmap_st* mmap_node;
    int node = 0;
    mmap_node = mmap_info->front;
    if (mmap_is_empty(mmap_info)) {
        printf("mmap中无数据\n");
    } else {
        node = mmap_node->node;
        delete_node(mmap_node, mmap_info);
        printf("删除了mmap中第 %d 条数据\n", node + 1);
    }
}

int main()
{
    struct mmap_attribute mmap_info;
    //文件结构体
    struct stat buf;
    int ret = 0;
    int i = 0, n = 0;
    //初始化文件结构体大小,避免找不到文件该值为随机值
    buf.st_size = 0;
    stat(MMAP_FILE, &buf);
    if (buf.st_size == 0) {
        ret = create_mmap(&mmap_info);
    } else {
        ret = read_mmap(&mmap_info, buf.st_size);
        if (ret == 0) {
            printf("读取成功\n");
        }
    }

    int chose_num = 1;
    while (chose_num != 0) {
        printf("\n0:退出\n1:查看mmap\n2:插入n条随机记录\n3:清空mmap\n4:删除第一条\n");
        printf("请选择:");
        scanf("%d", &chose_num);
        switch (chose_num) {
        case 0:
            break;
        case 1:
            mmap_printf(&mmap_info);
            break;
        case 2:
            printf("请输入n的值:");
            scanf("%d", &n);
            for (i = 0; i < n; i++) {
                if (mmap_is_full(&mmap_info)) {
                    printf("mmap_is_full\n");
                    break;
                }
                add_one_random_schedule(&mmap_info, time(NULL) - n + i);
            }
            printf("添加了 %d 条\n", i);
            break;
        case 3:
            delete_all_node(&mmap_info);
            break;
        case 4:
            delete_front_node(&mmap_info);
            break;
        default:
            printf("选错了,重选,(●'◡'●)\n");
            break;
        }
        getchar();
        if (chose_num == 0) {
            printf("回车退出");
        } else {
            printf("回车继续选择");
        }
        getchar();
    }
    printf("已退出\n");

    return 0;
}

六、写在最后

很多小伙伴可能不知道为什么用mmap还要用链表,用数组不香吗?
其实是有应用场景的,用到了相信大家自然会知道的。
就比如我举一个例子,这就是我为什么会用到链表:

比如这个空间要存一些操作记录,并且程序异常退出之后可以重新加载。
此外这些记录要按照一定的方式去处理,并且每条记录有不同的优先级,那么此时处理记录时候就不能够只是简单的取最前面的或者最后面的,而是要查找他的优先级,然后再处理等等,其实用数组也可以解决这些问题,但是缺点就是每次都要全部遍历,见仁见智,看小伙伴们喜欢哪种方式了~

仅以此学习记录,有错误欢迎大家批评指正。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值