C / C ++には、スマートポインター、STLコンテナー、new / delete、malloc / free、brk、sbrkなど、多くの一般的なメモリ割り当ておよび管理方法があります。Linuxには、比較的低レベルのメモリ管理方法mmap / munmapがあります。これには、メモリ空間の維持に役立つ他の補助データ構造なしで、割り当てられた仮想メモリを完全に単独で維持する必要があります。mmapシステムコールは、匿名の仮想メモリ領域を割り当てたり、ファイルをメモリにマップしたりできます。このマッピングにより、ファイル操作が直接メモリ操作のようになります。この方法は、メモリマッピングと呼ばれます。
mmap()はメモリページ(PAGE_SIZE)にマップする必要があり、メモリはページにのみマップできます。PAGE_SIZEの整数倍ではないアドレス範囲をマップする場合は、最初にメモリアライメントを実行し、サイズを次のように強制する必要があります。 PAGE_SIZEの倍数であるマップします。
mmap操作は、ユーザープログラムがデバイスメモリに直接アクセスするためのメカニズムを提供します。このメカニズムは、ユーザースペースおよびカーネルスペースでデータをコピーするよりも効率的です。高性能を必要とするアプリケーションで一般的に使用されますが、mmapはストリーム指向のデバイスでは実行できません。mmapの実装はハードウェアに関連しています。
1.ターミナルにmanmmapと入力して、この関数のAPIドキュメントを表示します。この関数の具体的な説明は次のとおりです。
#include <sys / mman.h>
void * mmap(void * start、size_t length、int prot、int flags、int fd、off_t offset);
int munmap(void * start、size_t length);
関数パラメーター:
start:メモリの開始アドレスを指すマップ済み。通常はNULLに設定されます。これは、システムがアドレスを自動的に選択し、マッピングが成功した後にアドレスを返すことを意味します。
長さ:ファイルのどの部分がメモリにマップされるか、つまりマッピング領域の長さを表します。
prot:マップされた領域の保護モード。ファイルのオープンモードと競合することはできません。次の方法
で実行できます
。PROT_EXECマッピング領域を実行できますPROT_READマッピング領域を読み取ることができます
PROT_WRITEマッピング領域を書き込むことができます
PROT_NONEマッピング領域にアクセスできません
上記のメソッドは、or操作( "|")と合理的に組み合わせることができます。
フラグ:マッピング領域のさまざまな特性に影響を与え、マッピングオブジェクトのタイプ、マッピングオプション、およびマッピングページを共有できるかどうかを指定します。mmap()を呼び出すときは、MAP_SHAREDまたはMAP_PRIVATEを指定する必要があります。フラグの値は、次の1つ以上のビットの組み合わせにすることができます。MAP_FIXED
パラメーターstartが指すアドレスを正常にマップできない場合、マッピングは破棄され、アドレスは変更されません。このフラグの使用は一般的に推奨されていません。
MAP_SHAREDは、マップされた領域に書き込まれたデータをファイルにコピーして戻し、ファイルをマップする他のプロセスが共有できるようにします。このオブジェクトをマップする他のすべてのプロセスとマッピングスペースを共有します。共有領域への書き込みは、ファイルへの出力と同じです。msync()またはmunmap()が呼び出されるまで、ファイルは実際には更新されません。
マップされた領域へのMAP_PRIVATEの書き込み操作は、マップされたファイルのコピーを生成します。つまり、プライベートな「コピーオンライト」によってこの領域に加えられた変更は、元のファイルの内容を書き戻しません。元のファイルに影響します。このフラグと上記のフラグは相互に排他的であり、使用できるのは1つだけです。
MAP_ANONYMOUSは、匿名マッピングを確立します。このとき、パラメータfdは無視され、ファイルは含まれず、マッピング領域を他のプロセスと共有することはできません。
MAP_DENYWRITEは、マップされた領域への書き込み操作のみを許可し、ファイルへの他の直接書き込み操作は拒否されます。
MAP_LOCKEDは、マップされた領域をロックします。これは、領域がスワップされないことを意味し、ページがメモリからスワップアウトされるのを防ぎます。
MAP_NORESERVE //このマッピング用にスワップスペースを予約しないでください。スワップスペースが予約されている場合、マッピング領域の変更が保証される場合があります。スワップスペースが予約されておらず、メモリが不足している場合、マッピング領域を変更すると、セグメント違反のシグナルが発生します。
MAP_LOCKED //マッピング領域のページをロックして、ページがメモリからスワップアウトされないようにします。
MAP_GROWSDOWN //マッピング領域を下に拡張できることをカーネルVMシステムに通知するために、スタックに使用されます。
MAP_ANON // MAP_ANONYMOUSの別名で、使用されなくなりました。
MAP_FILE //互換性のあるフラグ。無視されます。
MAP_32BIT //マッピング領域をプロセスアドレス空間の下位2GBに配置します。MAP_FIXEDが指定されている場合は無視されます。現在、このフラグはx86-64プラットフォームでのみサポートされています。
MAP_POPULATE //ファイルマッピングを先読みして、ページテーブルを準備します。マップされた領域へのその後のアクセスは、ページ違反によってブロックされません。
MAP_NONBLOCK // MAP_POPULATEとともに使用した場合にのみ意味があります。先読みは実行されず、メモリにすでに存在するページに対してのみページテーブルエントリが作成されます。
fd:メモリにマップされるファイル記述子。匿名メモリマッピングが使用されている場合、つまり、MAP_ANONYMOUSがフラグに設定され、fdが-1に設定されている場合。一部のシステムは匿名メモリマッピングをサポートしていません。fopenを使用して/ dev / zeroファイルを開き、ファイル
をマップできます。匿名メモリマッピングの効果を実現することもできます。
offset:ファイルマッピングのオフセット。通常は0に設定されます。これは、ファイルの先頭に対応することを意味します。オフセットは、PAGE_SIZEの整数倍である必要があります。
戻り値:
実行が成功すると、マッピングが成功した場合、mmap()はマップされた領域のメモリ開始アドレスを返し、munmap()は0を返します。失敗すると、mmap()はMAP_FAILED [その値は(void *)-1]を返し、エラーの原因はerrnoに格納され、munmapは-1を返します。
エラーコード:
EBADFパラメータfdは有効なファイル記述子ではありません
EACCESアクセス許可が正しくありません。MAP_PRIVATEの場合、ファイルは読み取り可能である必要があります。MAP_SHAREDを使用する場合、PROT_WRITEとファイルは書き込み可能である必要があります。
EINVALパラメーターstart、length、またはoffsetのいずれかが不正です。
EAGAINファイルがロックされているか、ロックされているメモリが多すぎます。
ENOMEMのメモリが不足しています。
errno戻り値:
errnoは次のいずれかの値に設定されます:
EACCES:アクセスエラー
EAGAIN:ファイルがロックされているか、メモリが多すぎます
EBADF:fdは有効なファイル記述子ではありません
EINVAL:1つ以上のパラメーターが無効
ですENFILE:ファイルを開くためのシステム制限に達しました
。ENODEV:指定されたファイルが配置されているファイルシステムはメモリマッピングをサポートしていません。ENOMEM:メモリが
不足しているか、プロセスがメモリマッピングの最大数を超えています。EPERM:
電力、操作が不十分です。許可されていません
ETXTBSY:書き込まれたファイルを開き、MAP_DENYWRITEフラグを指定します
SIGSEGV:読み取り専用領域への書き込みを
試みますSIGBUS:プロセスに属していないメモリ領域へのアクセスを試み
ますユーザー層の呼び出しは非常に簡単です。特定の機能は、物理メモリをユーザー仮想メモリに直接マップすることです。ユーザースペースは、物理スペースを直接操作できます。しかし、カーネル層の場合、その特定の実装はより複雑です。
システムコール:
mmap()システムコールにより、プロセスは同じ共通ファイルをマッピングすることでメモリを共有できます。通常のファイルがプロセスのアドレス空間にマップされた後、プロセスはread()、write()などの操作を呼び出さなくても、通常のメモリのようにファイルにアクセスできます。
注:実際、mmap()システムコールは、共有メモリでの使用のみを目的として設計されているわけではありません。それ自体が通常のファイルにアクセスする別の方法を提供します。プロセスは、メモリの読み取りや書き込みなどの通常のファイルを操作できます。
PosixまたはSystemVの共有メモリIPCは、純粋に共有を目的としています。もちろん、mmap()もその主要なアプリケーションの1つです。
システムは、メモリを共有するために2つの方法でmmap()を呼び出します。
(1)通常のファイルによって提供されるメモリマッピングを使用します。任意のプロセスに適しています。この時点で、ファイルを開くか作成してから、mmap()を呼び出す必要があります。典型的な呼び出しコードは次のとおりです
。fd= open(name、flag、mode);
if(fd <0)
...
ptr = mmap(NULL、len、PROT_READ | PROT_WRITE、MAP_SHARED、fd、0);
共有メモリを実現します。 mmap()を介して通信方式には多くの特徴や注意点がありますので、例で詳しく説明します。
(2)特別なファイルを使用して匿名メモリマッピングを提供します。親族プロセスに適しています。親プロセスと子プロセスの間の特別な親族関係により、mmap()が最初に親プロセスで呼び出され、次にfork()が呼び出されます。
次に、fork()を呼び出した後、子プロセスは親プロセスによって匿名でマップされたアドレス空間を継承し、mmap()によって返されたアドレスも継承するため、親プロセスと子プロセスはマップされた領域を介して通信できます。
これは一般的な継承関係ではないことに注意してください。一般的に、子プロセスは、親プロセスから継承されたいくつかの変数を個別に維持します。mmap()によって返されるアドレスは、親プロセスと子プロセスによって維持されます。
関連するプロセスに共有メモリを実装する最良の方法は、匿名メモリマッピングを使用することです。このとき、特定のファイルを指定する必要はなく、対応するフラグを設定するだけです。
int munmap(void * addr、size_t len)
この呼び出しは、プロセスアドレス空間のマッピング関係を解放します。addrはmmap()が呼び出されたときに返されるアドレスであり、lenはマッピング領域のサイズです。マッピング関係が解放されると、元のマッピングアドレスにアクセスすると、セグメンテーション違反が発生します。
int msync(void * addr、size_t len、int flags)
一般的に、マッピングスペース内の共有コンテンツへのプロセス変更はディスクファイルに直接書き戻されず、操作は通常munmap()を呼び出した後に実行されます。msync()を呼び出すことにより、ディスク上のファイルの内容を共有メモリ領域の内容と一致させることができます。
プログラム例:
mmap()の2つの使用例を以下に示します。例1は、2つのプロセスが通常のファイルをマッピングすることによって共有メモリ通信を実現することを示し、例2は、親プロセスと子プロセスが匿名マッピングによって共有メモリを実現することを示しています。
システムコールmmap()には多くの興味深い点があります。以下は、通常のファイルをマッピングするmmap()を介したプロセス間の通信の例です。この例を使用して、共有メモリを実現するためのmmap()の特性と注意事項を示します。
例1:2つのプロセスは、共通ファイルをマッピングすることによって共有メモリ通信を実現します。
例1には、map_normalfile1.cとmap_normalfile2.cの2つのサブルーチンが含まれています。2つのプログラムをコンパイルします。実行可能ファイルはmap_normalfile1とmap_normalfile2です。
2つのプログラムは、コマンドラインパラメータを介して同じファイルを指定し、共有メモリモードでプロセス間通信を実現します。map_normalfile2は、コマンドラインパラメーターで指定された通常のファイルを開こうとし、ファイルをプロセスのアドレススペースにマップし、マップされたアドレススペースに書き込みます。
map_normalfile1は、コマンドラインパラメーターで指定されたファイルをプロセスアドレス空間にマップしてから、マップされたアドレス空間で読み取り操作を実行します。このように、2つのプロセスは、コマンドラインパラメータを介して同じファイルを指定し、共有メモリモードでプロセス間通信を実現します。
以下は2つのプログラムコードです。
/*-------------map_normalfile1.c-----------*/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
char name[4];
int age;
}people;
main(int argc, char** argv) // map a normal file as shared mem:
{
int fd,i;
people *p_map;
char temp;
fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
if(fd < 0)
{
printf("error open\n");
exit(1);
}
lseek(fd,sizeof(people)*5-1,SEEK_SET);
write(fd,"",1);
p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );
close( fd );
temp = 'a';
for(i=0; i<10; i++)
{
temp += 1;
memcpy( ( *(p_map+i) ).name, &temp,2 );
( *(p_map+i) ).age = 20+i;
}
printf(" initialize over \n ");
sleep(10);
munmap( p_map, sizeof(people)*10 );
printf( "umap ok \n" );
}
例2:
/*-------------map_normalfile2.c-----------*/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
char name[4];
int age;
}people;
main(int argc, char** argv) // map a normal file as shared mem:
{
int fd,i;
people *p_map;
fd=open( argv[1],O_CREAT|O_RDWR,00777 );
p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
for(i = 0;i<10;i++)
{
printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );
}
munmap( p_map,sizeof(people)*10 );
}
map_normalfile1.cは、最初に人のデータ構造を定義します(共有メモリ領域のデータは、通信の各プロセスによって決定される固定形式であることが多いため、ここではデータ構造が使用されます。構造は一般に代表的なものです)。
map_normfile1はファイルを開くか作成し、ファイルの長さを5人の構造のサイズに設定します。次に、mmap()のリターンアドレスから開始して、10人の構造が設定されます。
次に、プロセスは10秒間スリープし、他のプロセスが同じファイルをマップするのを待ち、最後にマップを解除します。
map_normfile2.cは、単にファイルをマップし、mmap()によって返されたアドレスから人のデータ構造の形式で10人の構造を読み取り、読み取った値を出力してから、マップを解除します。
2つのプログラムをそれぞれ実行可能ファイルmap_normalfile1とmap_normalfile2にコンパイルした後、端末で./map_normalfile2 / tmp / test_shmを実行すると、プログラムの出力は次のようになり
ます
。initializeover umap ok map_normalfile1
の出力が初期化された後、umapを出力する前にわかりました。別の端末でmap_normalfile2 / tmp / test_shmを実行すると、次の出力が生成されます(スペースを節約するために、出力は少し
整理された結果になります):name:b age 20; name:c age 21; name:d age 22;名前:e 23歳;名前:f 24歳;
名前:g 25歳;名前:h 26歳;名前:I 27歳;名前:j 28歳;名前:k29歳;
map_normalfile1がumapokを出力した後、map_normalfile2を実行すると、次の結果が出力されます。
名前:b年齢20;名前:c年齢21;名前:d年齢22;名前:e年齢23;名前:f年齢24;
名前:年齢0;名前:年齢0;名前:年齢0;名前:年齢0;名前:年齢0;
プログラムの実行結果から導き出せる結論:
1。最終的にマップされたファイルのコンテンツの長さは初期サイズを超えませんつまり、マッピングはファイルサイズを変更できません
。2。プロセス通信に使用できる有効なアドレス空間サイズは、通常、マップされたファイルのサイズによって制限されますが、ファイルサイズによって完全に制限されるわけではありません。
開いているファイルは5人のサイズに切り捨てられ、10人のデータ構造がmap_normalfile1で初期化されます。適切なタイミングで(map_normalfile1の出力が初期化された後、umap okの出力の前に)map_normalfile2
を呼び出すと、map_normalfile2が出力されます。 10人全員構造の価値については後で詳しく説明します。
注:Linuxでは、メモリ保護は基本単位としてページに基づいています。マップされたファイルのサイズが1バイトしかない場合でも、カーネルはマッピングにページサイズのメモリを割り当てます。
マップされたファイルがページサイズよりも小さい場合、プロセスはmmap()リターンアドレスから始まるページサイズにエラーなしでアクセスできます。
ただし、ページ以外のアドレス空間にアクセスすると、エラーが発生します。これについては後で詳しく説明します。したがって、プロセス間通信に使用できる実効アドレス空間サイズは、ファイルサイズとページサイズの合計を超えることはありません。
3.ファイルがマップされると、mmap()を呼び出すプロセスのリターンアドレスへのアクセスは、特定のメモリ領域へのアクセスになります。これは、一時的にディスク上のファイルの影響を受けません。mmap()のリターンアドレス空間に対するすべての操作は、メモリ内でのみ意味があります
。munmap()またはmsync()を呼び出した後でのみ、メモリ内の対応するコンテンツをディスクファイルに書き戻すことができ、書き込まれたコンテンツはファイルを超えることはできません。のサイズ。
プログラム例:
例一:
//
// main.cpp
//
//
// Created by ChengChao on 14-9-27.
// Copyright (c) 2014年 cc. All rights reserved.
//
#include <iostream>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, const char * argv[]) {
//申请内存
int* arr = static_cast<int*>(mmap(
NULL, //分配的首地址
getpagesize(), //分配内存大小(必须是页的整数倍, 32位1页=4k)
PROT_READ | PROT_WRITE, //映射区域保护权限:读|写
MAP_ANON | MAP_SHARED, //匿名映射(不涉及文件io), 后面两个参数忽略
0, //要映射到内存中的文件描述符
0 //文件映射的偏移量,通常设置为0,必须是页的整数倍
));
printf("申请内存大小=%dk\n", sizeof(arr));
*arr = 10;
*(arr + 1) = 20;
*(arr + 2) = 30;
printf("arr[2]=%d\n", arr[2]);
//释放指针arr指向的内存区域,并制定释放的内存大小
munmap(arr, getpagesize());
return 0;
}
例二:
#include <fstream>
#include <iostream>
#include <cstdlib>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
using namespace std;
int main(int argc, char* argv[])
{
int fd=open("/mnt/e/file",O_RDWR);
FILE* f=fdopen(fd,"rw");
fseek(f,0,SEEK_END);
long ps=ftell(f);
cout << "file size: " << ps << endl;
//分配的大小必须为页的整数倍
int* p = (int*)mmap(NULL, ps, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
{
perror("mmap error");
exit(1);
}
//比如我用2个进程运行该程序,进程1 让*p=100 ,那么进程2 去读取*p也会变成100,但重要的是文件并没有更新,就是说没有IO操作,文件仅仅在msync()或mumap()调用时才会被更新,因为此时2个进程映射的该文件,都指向了相同的内存区域,也就说只有内存中的数据改变了
int oper,data;
while(true){
cin>>oper;
if(oper==1){
cin>>data;
*p=data;
}
else{
cout<<*p<<endl;
}
}
//...
munmap(p, ps);
close(fd);
return 0;
}