Linux プログラミング: mmap メモリ マッピング (メモリ共有の一種) の使用、mmap は大きなファイルのコピーのための最終兵器ですか?

序文

Linux における共有メモリの使用に関する前回の記事に続き、今回は、
鉄は熱いうちに、ipc 通信のもう 1 つの方法であるメモリ マッピングを紹介します。実際、これも共有メモリの一種とみなされますが、メモリ マッピングは、ファイルの特定のセクションをメモリにマッピングして、異なるプロセス間のデータ通信を実現します。
以前のブログポータル: https://blog.csdn.net/haohaohaihuai/article/details/106862138

基本的

通常のファイルの読み書き

プロセスは通常のファイルの読み取りと書き込みを実行した後、読み取り/書き込みシステム コールを呼び出した後にカーネルに入ります。カーネルはファイルの読み取りと書き込みを開始します。カーネルがファイルを読み取り中であると仮定すると、カーネルは最初にファイルを読み取ります。ユーザー バッファへのコピーでは、実際にはデータが 2 回コピーされます (最初はファイル -> カーネル バッファ、次にカーネル バッファ -> ユーザー ページ バッファ)。
ここに画像の説明を挿入します

メモリマップ

mmap を使用して fd、ディスク -> ユーザー ページ キャッシュをマッピングすると、コピーは 1 つだけになります。コピーを 1 つ減らすと、パフォーマンスが大幅に向上します。この手法は「zero_copy」とも呼ばれます。
ここに画像の説明を挿入します
mmap は、ハード ディスク領域内のファイルの連続セクションをメモリの連続セクションにマップします。「ファイル」の概念は、デバイス、デバイスを駆動するファイル、またはディスク ファイルです。メモリマッピングはファイルの読み書きをより高速かつ効率的に行うためのもので、もちろんIPC通信の手段としても利用できます。
ここに画像の説明を挿入します

関数の定義とパラメータ

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

start: 開始アドレス、通常はデフォルトで NULL
length: マッピング長、通常はファイルをマッピングするときにファイル長に設定 prot
: メモリ領域の権限、読み取り、書き込み、実行など
flags: マッピングを行うときのその他の補助フラグ
fd ファイル記述子、設定ファイルマッピング以外の場合は -1 に設定します
。 offset: ファイルをマッピングする場合はオフセット、通常は 0 に設定されます
。 説明:
PROT_EXEC ページのコンテンツを実行できます。
PROT_READ ページの内容を読み取ることができます。
PROT_WRITE ページのコンテンツを書き込むことができます。
PROT_NONE ページのコンテンツにはアクセスできない可能性があります。
次に、PROT_WRITE | PROT_WRITE は、読み取りおよび書き込みが可能であることを意味します

  • MAP_SHARED: このオブジェクトをマッピングする他のすべてのプロセスとマッピング スペースを共有します。共有領域への書き込みは、ファイルへの出力と同等です。ファイルは、msync() または munmap() が呼び出されるまで実際には更新されません。
  • MAP_PRIVATE: コピーオンライトのプライベート マッピングを作成します。メモリ領域への書き込みは、元のファイルには影響しません。このフラグと上記のフラグは相互に排他的であり、いずれか 1 つだけを使用できます。
  • 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_FIXED: 指定されたマッピング開始アドレスを使用します start パラメータと len パラメータで指定されたメモリ領域が既存のマッピング空間と重複する場合、重複部分は破棄されます。指定された開始アドレスが使用できない場合、操作は失敗します。また、開始アドレスはページ境界上にある必要があります。このオプションは、既存のマッピングを強制的に削除するため、マルチスレッド プロセスが自身のプロセス アドレス空間を破損しやすくするため、(それ自体が) 非常に危険です。
  • MAP_POPULATE: 先読みによるファイル マッピング用のページ テーブルを準備します。マップされた領域への後続のアクセスは、ページ違反によってブロックされません。
  • MAP_NONBLOCK: MAP_POPULATE と一緒に使用する場合にのみ意味があります。先読みは実行されず、メモリ内にすでに存在するページに対してページ テーブル エントリのみが作成されます。


フラグの組み合わせには、MAP_SHARED と MAP_PRIVATE など、相互に排他的なものと、MAP_SHARED |MAP_LOCKED、MAP_SHARED| MAP_ANONYMOUSなど、混合して使用できるものがあります。 MAP_SHARED、MAP_PRIVATE、
MAP_ANONYMOUS の組み合わせは、次のモードに大別できます。

  • プライベート ファイル マッピング
    複数のプロセスは初期化に同じ物理メモリ ページを使用しますが、各プロセスによるメモリ ファイルの変更は共有されず、物理ファイルにも反映されません。
  • プライベート匿名マッピング
    mmap は、各プロセスによって共有されない新しいマッピングを作成します。これは主にメモリを割り当てるために使用されます (malloc は大きなメモリを割り当て、mmap を呼び出します)。
  • 共有ファイルマッピング
    仮想メモリ技術により複数のプロセスが同じ物理メモリ空間を共有し、メモリファイルへの変更は実際の物理ファイルに反映され、プロセス間通信(IPC)の仕組みでもあります。
  • 共有匿名マッピング
    この機構は、フォーク時にコピーオンライトを使用せず、親プロセスと子プロセスが同じ物理メモリページを完全に共有することで、親子プロセス通信(IPC)も実現します。

mmap は成功の場合は 0、MAP_FAILED の場合は失敗を返し、エラー コードは errno にあります。
EACCES : アクセス エラー
EAGAIN : ファイルがロックされているか、ロックされているメモリが多すぎます
EBADF : fd は有効なファイル記述子ではありません
EINVAL : 1 つまたは詳細 無効なパラメータ
ENFILE : オープン ファイルのシステム制限に達しました
ENODEV : 指定されたファイルが配置されているファイル システムはメモリ マッピングをサポートしていません
ENOMEM : メモリが不足しているか、プロセスがメモリ マッピングの最大数を超えています
EPERM : 権限が不十分です、操作は許可されていません
ETXTBSY : ファイルをオープンし、MAP_DENYWRITE フラグを指定するための記述されたメソッド
SIGSEGV : 読み取り専用領域に書き込もうとしています
SIGBUS : プロセスに属さないメモリ領域にアクセスしようとしています

使用例

1.MAP_SHAREDモード

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

int main(int argc, char **argv)
{
    
    
    int fd;
    char *map_ptr;
    struct stat buff = {
    
    0};
    //打开文件
    fd = open(argv[1], O_RDWR);
    if(fd<0)
    {
    
    
        printf("open erro: %s\n", strerror(errno));
        close(fd);
        return -1;
    }
    //获取文件状态
    if (fstat(fd, &buff) < 0)
    {
    
    
        printf("file state error:%s\n", strerror(errno));
        close(fd);
        return -1;
    }
    //允许读写
    //允许其它进程访问此内存区域
    printf("mmap file size: %ld\n", buff.st_size);
    map_ptr = mmap(NULL, buff.st_size+10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if(map_ptr == MAP_FAILED)
    {
    
    
        printf("mmap erro: %s\n", strerror(errno));
        close(fd);
        return -1;
    }
    //使用映射区域
    //读
    printf("mmap content: %s\n", map_ptr);
    //写
    memcpy(map_ptr, "LLL",3);
    
    munmap(map_ptr, buff.st_size);
    close(fd);
    
    return 0;
}

プロジェクト ディレクトリに新しいファイル mmap_demo.txt を作成し、「pineapple and jackfruit~」という文字を書き込みます。UTF-8エンコード形式のファイルとして保存します。
ここに画像の説明を挿入します
コンパイルされたファイル: gcc -o mmapwrite mmapwrite.c
ここで使用される utf-8 エンコーディング。中国語の文字は通常 3 バイトの長さで、この例では最初の 3 バイトを LLL に置き換えます。
MAP_SHARED ファイル共有モードでプログラムを実行すると、ファイルの内容が更新されます。
ここに画像の説明を挿入します

2.MAP_PRIVATEモード

上記のマッピング コード例は、プライベート ファイル マッピングを確立するためにわずかに変更されています。

    //允许读写
    //允许其它进程访问此内存区域
    printf("mmap file size: %ld\n", buff.st_size);
    map_ptr = mmap(NULL, buff.st_size+10, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    if(map_ptr == MAP_FAILED)
    {
    
    
        printf("mmap erro: %s\n", strerror(errno));
        close(fd);
        return -1;
    }
    //使用映射区域
    printf("mmap content1: %s\n", map_ptr);
    memcpy(map_ptr, "LLL",3);
    printf("mmap content2: %s\n", map_ptr);

ここに画像の説明を挿入します
MAP_PRIVATE を使用すると、他のプロセスはこのメモリ領域にアクセスできなくなります。読み取りプロセスは「LLL」文字の内容を読み取るのではなく、元のファイルの内容を読み取るだけであり、変更された内容を読み取ることができるのは mmwrite プロセスのみであり、変更された内容はファイルに同期されないことがわかります。

3.mmap 大きなファイルのコピー例

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

int main(int argc, char **argv)
{
    
    
    int fd,fd1;
    char *rmap_ptr,*wmap_ptr;
    struct stat buff = {
    
    0};
    //打开文件
    fd = open(argv[1], O_RDWR);
    if(fd<0)
    {
    
    
        printf("open src erro: %s\n", strerror(errno));
        close(fd);
        return -1;
    }
    //获取文件状态
    if (fstat(fd, &buff) < 0)
    {
    
    
        printf("file state error:%s\n", strerror(errno));
        close(fd);
        return -1;
    }
	//判断目标文件是否存在, 不存在则创建目标文件
    fd1 = open("./file_copy", O_RDWR | O_CREAT | O_EXCL, 0777);
	if(fd1<0)
    {
    
    
        printf("open dest file erro: %s\n", strerror(errno));
        close(fd);
        return -1;
    }
	//设置文件大小
	ftruncate(fd1, buff.st_size);

    //读文件映射,使用MAP_PRIVATE
    printf("mmap file size: %ld\n", buff.st_size);
    rmap_ptr = mmap(NULL, buff.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if(rmap_ptr == MAP_FAILED)
    {
    
    
        printf("rmmap erro: %s\n", strerror(errno));
    	close(fd1);
        close(fd);
        return -1;
    }

    //写文件映射,必须用MAP_SHARED,否则内容将不能更新到文件
    wmap_ptr = mmap(NULL, buff.st_size, PROT_WRITE, MAP_SHARED, fd1, 0);
    if(wmap_ptr == MAP_FAILED)
    {
    
    
        printf("wmmap erro: %s\n", strerror(errno));
    	close(fd1);
        close(fd);
        return -1;
    }

    //拷贝文件内容
	memcpy(wmap_ptr,rmap_ptr,buff.st_size);

    close(fd1);
	close(fd);

   //解除映射
    munmap(wmap_ptr, buff.st_size);
    munmap(rmap_ptr, buff.st_size);

    return 0;
}

コピーする必要があるファイル wps-office_11.1.0.8392_amd64.deb のサイズは 213113086 バイトです。
プログラム ./mmwrite wps-office_11.1.0.8392_amd64.deb を実行して、
コピーされたターゲット ファイル file_copy を取得します。コピー速度は非常に速いです。
ここに画像の説明を挿入します

使用の概要:
1. このサンプル ファイル mmap_demo.txt のサイズは 19 バイトで、buff.st_size+10 バイトがマッピングされます。ただし、mmap() は PAGE_SIZE 単位でマッピングする必要があり、物理メモリのページ単位は 4096 バイトなので、実際には 4096 バイトのページがマッピングされます。
現時点では

  • バイト 0 ~ 19 にアクセスすると、ファイル操作の内容が返され、変更された内容がファイルに更新されます。
  • 20 ~ 4096 バイト目にアクセスすると 0 が返され、書き込まれた内容はファイルに更新されません。
  • 4096 バイトを超えるデータにアクセスすると、プログラム SIGSECV エラーが発生します。

2. Mmap を使用すると、大規模なデータの読み書きを効率的に行うことができ、ディスク領域をメモリ領域に交換する必要がある場合、mmap は非常に効果的です。MAP_SHARED使用時のIPC通信に使用できます。

3. mmap の後、ファイルの内容は物理ページにはロードされず、アドレス空間のみが仮想メモリに割り当てられます。プロセスがこのアドレスにアクセスすると、ページテーブルを参照すると、仮想メモリに対応するページが物理メモリにキャッシュされていないことがわかり、カーネルのページで処理される「ページフォールト」が発生します。フォールト例外ハンドラーによって、ファイルの対応するコンテンツがページ (4096) に変換され、物理メモリにロードされます。

著者: Feima Programmer
技術交流へようこそ: QQ: 255895056
転載の際は出典を明記し、不適切な点があれば修正してください

おすすめ

転載: blog.csdn.net/haohaohaihuai/article/details/107039410