使用 mmap 函數(shù)實(shí)現(xiàn)高效日志寫(xiě)入

一、前言

在性能敏感的場(chǎng)景中,傳統(tǒng)的文件讀寫(xiě)操作可能成為瓶頸。本文將通過(guò) mmap 的高效內(nèi)存映射特性,介紹如何構(gòu)建一個(gè)高性能的日志系統(tǒng),并詳細(xì)拆解實(shí)現(xiàn)過(guò)程。


二. 為什么選擇 mmap

問(wèn)題背景

在日志寫(xiě)入場(chǎng)景中,頻繁的 I/O 調(diào)用可能帶來(lái)以下問(wèn)題:

  • 性能瓶頸:傳統(tǒng)文件寫(xiě)入需要在用戶態(tài)和內(nèi)核態(tài)頻繁切換,效率較低。
  • 資源浪費(fèi):頻繁打開(kāi)、關(guān)閉文件或調(diào)整文件大小可能導(dǎo)致系統(tǒng)資源耗盡。

mmap 的優(yōu)勢(shì)

  • 高效性:將文件映射到虛擬內(nèi)存后,可以像操作普通內(nèi)存一樣訪問(wèn)文件內(nèi)容,避免頻繁 I/O 調(diào)用。
  • 雙向同步:內(nèi)存和文件之間的內(nèi)容修改會(huì)自動(dòng)同步,省去了額外的寫(xiě)回操作。
  • 固定大小:適用于需要固定大小日志文件的場(chǎng)景,控制存儲(chǔ)空間使用。

適用場(chǎng)景包括:

  • 高性能日志系統(tǒng)。
  • 共享內(nèi)存實(shí)現(xiàn)多進(jìn)程間通信。
  • 高速文件讀寫(xiě)操作。

三. mmap 的核心概念

mmap 是 Linux 提供的系統(tǒng)調(diào)用,功能是將文件或設(shè)備映射到進(jìn)程的虛擬地址空間中,核心作用包括:

  • 虛擬地址到文件的映射:進(jìn)程可以直接通過(guò)內(nèi)存地址訪問(wèn)文件內(nèi)容。
  • 支持雙向數(shù)據(jù)同步:修改虛擬地址空間中的數(shù)據(jù)會(huì)反映到文件中,反之亦然。

函數(shù)原型

void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
參數(shù) 含義
addr 指定映射的起始地址,通常傳 nullptr 讓系統(tǒng)選擇。
length 映射的內(nèi)存大小,必須為頁(yè)大小的整數(shù)倍。
prot 映射內(nèi)存的訪問(wèn)權(quán)限,如 PROT_READ 、 PROT_WRITE
flags 映射類(lèi)型,如 MAP_SHARED:映射的內(nèi)存區(qū)域與文件共享,修改映射區(qū)域的內(nèi)容會(huì)影響文件。
fd 文件描述符,指定映射到內(nèi)存的文件。
offset 是映射開(kāi)始位置在文件中的偏移。指定從文件開(kāi)頭偏移的字節(jié)數(shù),表示映射區(qū)域在文件中的起始位置。該偏移必須是頁(yè)面大小的整數(shù)倍,因?yàn)閮?nèi)存映射操作通常按頁(yè)面(通常是4KB)對(duì)齊。

返回值為映射內(nèi)存的起始地址,失敗時(shí)返回 MAP_FAILED。


四. 日志系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)邏輯

設(shè)計(jì)目標(biāo)

  • 固定大小日志文件:日志文件大小固定為 1MB,避免無(wú)限增長(zhǎng)占用存儲(chǔ)空間。
  • 高效日志寫(xiě)入:利用 mmap 減少 I/O 調(diào)用,提升性能。
  • 簡(jiǎn)單易用:提供寫(xiě)入和讀取日志內(nèi)容的基礎(chǔ)功能。

實(shí)現(xiàn)步驟

以下是實(shí)現(xiàn)的核心步驟:


image.png

Step 1:打開(kāi)文件

int fd = open("logfile.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);
  • 功能:打開(kāi)或創(chuàng)建一個(gè)名為 logfile.txt 的日志文件。
  • 參數(shù)解析
    • O_RDWR:以讀寫(xiě)模式打開(kāi)文件。
    • O_CREAT:如果文件不存在,則創(chuàng)建。
    • O_TRUNC:如果文件已存在,清空文件內(nèi)容。
  • 權(quán)限0666 表示所有用戶可讀寫(xiě)。

Step 2:調(diào)整文件大小

if (ftruncate(fd, LOG_SIZE) == -1) {
    perror("Error truncating file");
    close(fd);
    return -1;
}
  • 功能:將文件大小調(diào)整為 LOG_SIZE(1MB)。
  • 作用:為 mmap 分配固定大小的映射范圍。
  • 注意:文件大小不足時(shí)會(huì)填充空字節(jié),超出時(shí)會(huì)截?cái)唷?/li>

Step 3:映射文件到內(nèi)存

char* mapped = (char*)mmap(nullptr, LOG_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mapped == MAP_FAILED) {
    perror("Error mapping file");
    close(fd);
    return -1;
}
  • 功能:將文件映射到虛擬內(nèi)存地址。
  • 關(guān)鍵參數(shù)
    • PROT_READ | PROT_WRITE:支持讀寫(xiě)權(quán)限。
    • MAP_SHARED:允許進(jìn)程間共享映射。
    • fd0:指定映射的文件和偏移。
    • 返回值:成功返回映射地址,失敗返回 MAP_FAILED。

Step 4:寫(xiě)入日志內(nèi)容

void write_log(char* mapped, const char* log_msg, size_t offset) {
    if (offset < LOG_SIZE) {
        strcpy(mapped + offset, log_msg);
    } else {
        std::cerr << "Offset exceeds log size!" << std::endl;
    }
}
  • 功能:將日志內(nèi)容寫(xiě)入映射的內(nèi)存區(qū)域。
  • 參數(shù)
    • mapped是指向已映射到內(nèi)存的文件內(nèi)容的指針。
    • log_msg是要寫(xiě)入日志的字符串。
    • offset是要寫(xiě)入日志的起始位置。
  • 邏輯
    這個(gè)strcpy是字符串復(fù)制函數(shù),用于將log_msg(源字符串)復(fù)制到mapped + offset(目標(biāo)位置)。
    由于內(nèi)存映射是通過(guò)mmap將文件內(nèi)容加載到內(nèi)存中,mapped是指向文件內(nèi)容的指針。通過(guò)mapped + offset來(lái)指定在內(nèi)存中的哪個(gè)位置開(kāi)始寫(xiě)入數(shù)據(jù)。
    需要確保offset不超過(guò)映射的大?。↙OG_SIZE),否則可能會(huì)導(dǎo)致越界寫(xiě)入,造成不可預(yù)知的錯(cuò)誤。

步驟 5:讀取日志內(nèi)容

void read_log(char* mapped) {
    std::cout << "Log content: " << std::endl;
    std::cout << mapped << std::endl;
}
  • 功能:從映射內(nèi)存中讀取日志內(nèi)容。
  • 邏輯:直接訪問(wèn)映射的內(nèi)存,讀取日志內(nèi)容并打印。

步驟 6:取消映射與資源釋放

if (munmap(mapped, LOG_SIZE) == -1) {
    perror("Error unmapping file");
}
close(fd);
  • 功能:釋放映射的內(nèi)存區(qū)域和文件資源。
    • munmap 取消映射,進(jìn)程在映射空間對(duì)共享內(nèi)容的改變并不直接寫(xiě)回到磁盤(pán)文件中。
    • close 關(guān)閉文件描述符,釋放系統(tǒng)資源。

4. 完整代碼示例

以下是完整實(shí)現(xiàn):

#include <jni.h>
#include <string>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <android/log.h>

// Android日志宏
#define LOG_TAG "NativeLog"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

const size_t LOG_SIZE = 1024 * 1024; // 1MB

// 獲取當(dāng)前日志文件的偏移量
size_t get_current_offset(const char* mapped) {
    size_t offset = 0;
    while (offset < LOG_SIZE && mapped[offset] != '\0') {
        ++offset;
    }
    return offset;
}

void write_log(char* mapped, const char* log_msg, size_t offset) {
    size_t log_len = strlen(log_msg);
    if (offset + log_len < LOG_SIZE) {
        strcpy(mapped + offset, log_msg);
    } else {
        LOGI("Not enough space to append log!");
    }
}

void read_log(char* mapped) {
    LOGI("Log content: %s", mapped);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_mmapsamples_MainActivity_writeLog(JNIEnv *env, jobject thiz, jstring msg) {
    // 獲取應(yīng)用的私有文件路徑
    const char* filename = "/data/data/com.xgimi.mmapsamples/files/logfile.txt";

    // 打開(kāi)日志文件
    int fd = open(filename, O_RDWR | O_CREAT, 0666);
    if (fd == -1) {
        perror("Error opening file");
        return;
    }

    // 擴(kuò)展文件大小
    if (ftruncate(fd, LOG_SIZE) == -1) {
        perror("Error truncating file");
        close(fd);
        return;
    }

    // 映射文件到內(nèi)存
    char* mapped = (char*)mmap(nullptr, LOG_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mapped == MAP_FAILED) {
        perror("Error mapping file");
        close(fd);
        return;
    }

    // 獲取偏移量并寫(xiě)入日志
    const char* log_msg = env->GetStringUTFChars(msg, nullptr);
    size_t offset = get_current_offset(mapped);
    write_log(mapped, log_msg, offset);
    env->ReleaseStringUTFChars(msg, log_msg);

     // 解除映射
    if (munmap(mapped, LOG_SIZE) == -1) {
        perror("Error unmapping file");
    }

    // 關(guān)閉文件
    close(fd);

五. 總結(jié)與擴(kuò)展

  • 總結(jié):通過(guò) mmap,可以高效地實(shí)現(xiàn)文件讀寫(xiě)操作,特別適用于固定大小的日志存儲(chǔ)場(chǎng)景。
  • 擴(kuò)展
    • mmap 的應(yīng)用場(chǎng)景不限于日志,還可用于共享內(nèi)存、多線程通信等。
    • 可結(jié)合環(huán)形緩沖區(qū)設(shè)計(jì),實(shí)現(xiàn)循環(huán)日志存儲(chǔ),進(jìn)一步優(yōu)化日志系統(tǒng)。

六.參考

內(nèi)存映射的一些理論概述,可參考如下文章:
https://blog.csdn.net/luo_boke/article/details/109311432?utm_source=chatgpt.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • UNIX網(wǎng)絡(luò)編程第二卷進(jìn)程間通信對(duì)mmap函數(shù)進(jìn)行了說(shuō)明。該函數(shù)主要用途有三個(gè):1、將一個(gè)普通文件映射到內(nèi)存中,通...
    宇文黎琴閱讀 3,754評(píng)論 0 4
  • 參考 中文資料英文資料使用場(chǎng)景 介紹 除了標(biāo)準(zhǔn)的文件 IO,例如 open, read, write,內(nèi)核還提供接...
    找不到工作閱讀 18,180評(píng)論 0 3
  • linux庫(kù)函數(shù)mmap()原理 目錄 1.mmap基本概念 2.mmap內(nèi)存映射原理 3.mmap和常規(guī)文件操作...
    小烏龜爸閱讀 1,067評(píng)論 0 3
  • 前言 公司目前在做一款企業(yè)級(jí)智能客服系統(tǒng),對(duì)于系統(tǒng)穩(wěn)定性要求很高,不過(guò)難保用戶在使用中不會(huì)出現(xiàn)問(wèn)題,而 Andro...
    王晨彥閱讀 9,926評(píng)論 9 24
  • 作者: 一字馬胡 轉(zhuǎn)載標(biāo)志 【2018-03-27】 更新日志 日期更新內(nèi)容備注2018-03-27回顧以前的知...
    一字馬胡閱讀 610評(píng)論 0 3

友情鏈接更多精彩內(nèi)容