web-dev-qa-db-ja.com

mmap、msync、およびlinuxプロセスの終了

Mmap()を使用してMAP_SHAREDフラグを設定し、固定サイズの構造体を既知のファイル名に関連付けることにより、mmapを使用してLinuxで実行されているCプログラムのプログラム状態の特定の部分の永続性を実装したいと思います。パフォーマンス上の理由から、msync()をまったく呼び出さない方がいいと思います。他のプログラムは、このファイルにアクセスしません。プログラムが終了して再起動すると、同じファイルが再度マップされ、処理が実行されて、終了前の状態が回復します。私の質問はこれです:ファイル記述子でmsync()を呼び出さない場合、カーネルは、プロセスがSIGKILLで終了した場合でも、メモリへのすべての更新がディスクに書き込まれ、その後回復可能であることを保証しますか?また、プログラムがmsync()を呼び出さない場合でも、カーネルが定期的にページをディスクに書き込むことによる一般的なシステムオーバーヘッドはありますか?

EDIT:データが書き込まれるかどうかの問題は解決しましたが、open()/ writeでこの問題を処理しようとすると、予期しないシステム負荷が発生するかどうかはまだわかりません。 ()/ fsync()であり、プロセスがKILL/SEGV/ABRT/etcにヒットした場合に、一部のデータが失われる可能性があるというリスクを負います。知識のある人がチャイムを鳴らすことを期待して、「linux-kernel」タグを追加しました。

26
BD at Rivenhill

私は怠惰ではなく、データがディスクに確実に書き込まれるかどうかという質問に、コードを書くことで答えることにしました。答えはそれが書かれるということです。

これは、mmapされたファイルにデータを書き込んだ後に突然自殺するプログラムです。

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

typedef struct {
  char data[100];
  uint16_t count;
} state_data;

const char *test_data = "test";

int main(int argc, const char *argv[]) {
  int fd = open("test.mm", O_RDWR|O_CREAT|O_TRUNC, (mode_t)0700);
  if (fd < 0) {
    perror("Unable to open file 'test.mm'");
    exit(1);
  }
  size_t data_length = sizeof(state_data);
  if (ftruncate(fd, data_length) < 0) {
    perror("Unable to truncate file 'test.mm'");
    exit(1);
  }
  state_data *data = (state_data *)mmap(NULL, data_length, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, fd, 0);
  if (MAP_FAILED == data) {
    perror("Unable to mmap file 'test.mm'");
    close(fd);
    exit(1);
  }
  memset(data, 0, data_length);
  for (data->count = 0; data->count < 5; ++data->count) {
    data->data[data->count] = test_data[data->count];
  }
  kill(getpid(), 9);
}

これは、前のプログラムが停止した後に結果のファイルを検証するプログラムです。

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

typedef struct {
  char data[100];
  uint16_t count;
} state_data;

const char *test_data = "test";

int main(int argc, const char *argv[]) {
  int fd = open("test.mm", O_RDONLY);
  if (fd < 0) {
    perror("Unable to open file 'test.mm'");
    exit(1);
  }
  size_t data_length = sizeof(state_data);
  state_data *data = (state_data *)mmap(NULL, data_length, PROT_READ, MAP_SHARED|MAP_POPULATE, fd, 0);
  if (MAP_FAILED == data) {
    perror("Unable to mmap file 'test.mm'");
    close(fd);
    exit(1);
  }
  assert(5 == data->count);
  unsigned index;
  for (index = 0; index < 4; ++index) {
    assert(test_data[index] == data->data[index]);
  }
  printf("Validated\n");
}
13
BD at Rivenhill

この質問に答えるLinusTorvaldsからのコメントを見つけました http://www.realworldtech.com/forum/?threadid=113923&curpostid=114068

マップされたページはファイルシステムキャッシュの一部です。つまり、そのページに変更を加えたユーザープロセスが停止しても、ページはカーネルによって管理され、そのファイルへのすべての同時アクセスはカーネルを経由します。プロセスはそのキャッシュから提供されます。一部の古いLinuxカーネルではそれが異なっていたため、一部のカーネルドキュメントではまだmsyncを強制するように指示されています。

編集:ありがとうRobHはリンクを修正しました。

19

私は混乱を増す何かを見つけました:

munmapは、マップされたオブジェクトには影響しません。つまり、munmapを呼び出しても、マップされた領域の内容はディスクファイルに書き込まれません。 MAP_SHARED領域のディスクファイルの更新は、メモリマップ領域に格納するときにカーネルの仮想メモリアルゴリズムによって自動的に行われます。

これは、UNIX®環境での高度なプログラミングからの抜粋です。

linuxのマンページから:

MAP_SHAREDこのマッピングを、このオブジェクトをマップする他のすべてのプロセスと共有します。リージョンへの保存は、ファイルへの書き込みと同等です。 msync(2)またはmunmap(2)が呼び出されるまで、ファイルは実際には更新されない場合があります。

2つは矛盾しているようです。 APUEは間違っていますか?

9
zach

私はあなたの質問に対する非常に正確な答えを見つけられなかったので、もう1つ追加することにしました:

  1. まず、データの損失について、書き込みまたはmmap/memcpyメカニズムを使用すると、ページキャッシュに書き込み、ページ置換設定/アルゴリズムに基づいてOSによってバックグラウンドで基盤となるストレージに同期されます。たとえば、Linuxにはvm.dirty_writeback_centisecsがあり、ディスクにフラッシュする「古い」と見なされるページを決定します。これで、書き込み呼び出しが成功した後にプロセスが停止した場合でも、データはカーネルページにすでに存在し、最終的にストレージに書き込まれるため、データが失われることはありません。データが失われる唯一のケースは、OS自体がクラッシュした場合(カーネルパニック、電源オフなど)です。データがストレージに到達したことを完全に確認する方法は、場合によってはfsyncまたはmsync(mmapされた領域の場合)を呼び出すことです。
  2. システム負荷の懸念については、はい、リクエストごとにmsync/fsyncを呼び出すと、スループットが大幅に低下するため、必要な場合にのみ行ってください。 OSのクラッシュでデータが失われるのを本当に防いでいることを忘れないでください。これはまれであり、おそらくほとんどの人が使用できるものだと思います。行われる一般的な最適化の1つは、バランスをとるために1秒などの定期的な間隔で同期を発行することです。
5
jayadev

Linuxのマンページ情報が正しくないか、Linuxがひどく不適合です。 msyncは、変更がファイルの論理状態にコミットされているかどうか、または他のプロセスがmmapまたはreadを使用してファイルにアクセスしているかどうかとは関係ありません。変更を参照してください。これは純粋にfsyncの類似物であり、電源障害またはその他のハードウェアレベルの障害が発生した場合にデータの整合性を確保する目的を除いて、no-opとして扱う必要があります。

1
R..