web-dev-qa-db-ja.com

最適なロックファイル方式

Windowsには、排他的アクセス権でファイルを開くオプションがあります。 Unixにはありません。

一部のファイルまたはデバイスへの排他的アクセスを確保するために、Unixでは通常、/ var/lockディレクトリに格納されているロックファイルを使用するのが一般的です。

C命令open( "/var/lock/myLock.lock", O_RDWR | O_CREAT | O_EXCL, 0666 )は、ロックファイルがすでに存在する場合は-1を返し、それ以外の場合は作成します。関数はアトミックであり、競合状態がないことを確認します。

リソースが解放されると、ロックファイルは次の命令remove( "/var/lock/myLock.lock" )によって削除されます。

この方法には2つの問題があります。

  1. ロックを解除せずにプログラムが終了する場合があります。たとえば、それが殺された、クラッシュした、などの理由で。ロックファイルはそのまま残り、リソースが使用されなくなっても、リソースへのアクセスはできなくなります。

  2. ロックファイルはグループと世界の書き込み権限で作成されますが、グループと世界の書き込み権限をクリアする権限マスクを使用するようにアカウントを設定するのが一般的な方法です。したがって、ロックが孤立している(使用されていない)と判断する信頼できる方法がある場合、ファイルの所有者ではないユーザーは、そのロックを削除できません。

記録として、私はロックファイルを使用して、シリアルポートに接続されたデバイス(実際には/ dev/ttyUSBx)への排他的アクセスを確保します。協力を必要とするアドバイザリー方法はOKです。ただし、異なるユーザー間で排他的なアクセスを確保する必要があります。

ロックファイルよりも優れた同期方法はありますか?ロックファイルを作成したプロセスがまだ実行されているかどうかを確認するにはどうすればよいですか?別のユーザーが使用していない場合にロックファイルを削除できるようにするにはどうすればよいですか?

私が思いついた1つの解決策は、ファイルをUnixソケットファイルとして使用することでした。ファイルが存在する場合は、そのファイルを使用して接続してみてください。失敗した場合は、ファイルの所有者プロセスが停止していると見なされる場合があります。これには、所有者プロセスのソケットaccept()でループするスレッドが必要です。残念ながら、システムはもうアトミックではありません。

34
chmike

啓発的なプレゼンテーションをご覧ください File Locking Tricks and Traps

この短い講演では、ファイルロックのいくつかの一般的な落とし穴と、ファイルロックをより効果的に使用するためのいくつかの便利なトリックを紹介します。

編集:より正確に質問に対処するには:

ロックファイルよりも優れた同期方法はありますか?

@Hasturkunがすでに述べたように、そして上記のプレゼンテーションが言ったように、使用する必要があるシステムコールは flock(2) です。多くのユーザー間で共有したいリソースが既にファイルベースである場合(あなたの場合は/dev/ttyUSBx)、flockデバイスファイル自体

ロックファイルを作成したプロセスがまだ実行されているかどうかを確認するにはどうすればよいですか?

flock- edロックは、プロセスが終了した場合でも、ファイルに関連付けられたファイル記述子を閉じると自動的に解放されるため、これを判別する必要はありません。

使用していない場合、他のユーザーがロックファイルを削除できるようにする方法を教えてください。

デバイスファイル自体をロックする場合は、ファイルを削除する必要はありません。 /var/lockで通常のファイルをロックする場合でも、flockを使用すると、ロックを解除するためにファイルを削除する必要はありません。

36

次のように、おそらく flock() を使用する必要があります

fd = open(filename, O_RDWR | O_CREAT, 0666); // open or create lockfile
//check open success...
rc = flock(fd, LOCK_EX | LOCK_NB); // grab exclusive lock, fail if can't obtain.
if (rc)
{
    // fail
}
22
Hasturkun

Hasturkunの答えは私を軌道に乗せたものです。

ここに私が使用するコードがあります

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>

/*! Try to get lock. Return its file descriptor or -1 if failed.
 *
 *  @param lockName Name of file used as lock (i.e. '/var/lock/myLock').
 *  @return File descriptor of lock file, or -1 if failed.
 */
int tryGetLock( char const *lockName )
{
    mode_t m = umask( 0 );
    int fd = open( lockName, O_RDWR|O_CREAT, 0666 );
    umask( m );
    if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 )
    {
        close( fd );
        fd = -1;
    }
    return fd;
}

/*! Release the lock obtained with tryGetLock( lockName ).
 *
 *  @param fd File descriptor of lock returned by tryGetLock( lockName ).
 *  @param lockName Name of file used as lock (i.e. '/var/lock/myLock').
 */
void releaseLock( int fd, char const *lockName )
{
    if( fd < 0 )
        return;
    remove( lockName );
    close( fd );
}
8
chmike

回答の1つに記載されているように実装されたロックおよびロック解除機能には注意してください。

int tryGetLock( char const *lockName )
{
    mode_t m = umask( 0 );
    int fd = open( lockName, O_RDWR|O_CREAT, 0666 );
    umask( m );
    if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 )
    {
        close( fd );
        fd = -1;
    }
    return fd;
}

そして:

void releaseLock( int fd, char const *lockName )
{
    if( fd < 0 )
        return;
    remove( lockName );
    close( fd );
}

問題は、releaseLockのremove呼び出しが競合状態のバグを引き起こすことです。厄介なタイミングで排他的な群れを取得しようとする3つのプロセスがあると考えてみましょう。

  • プロセス#1はロックファイルを開き、flockを取得し、アンロック関数を呼び出そうとしていますが、まだ実行していません。
  • プロセス#2がopenを呼び出して、lockNameが指すファイルを開き、そのファイル記述子を取得しましたが、まだflockは呼び出されていません。つまり、lockNameが指すファイルが2回開かれます。
  • プロセス#3はまだ開始されていません。

厄介なタイミングでは、プロセス#1が最初にremove()とclose()を呼び出し(順序は重要ではありません)、次にプロセス#2がすでに開いているファイル記述子を使用してflockを呼び出す可能性がありますが、これはもうありません。 lockNameという名前のファイルですが、どのディレクトリエントリにもリンクされていないファイル記述子です。

ここで、プロセス#3が開始されると、プロセスのopen()呼び出しがlockNameファイルを作成し、そのファイルがロックされていないため、そのロックを取得します。結果として、プロセス#2と#3はどちらも、fileNameのロックを所有していると思います->バグ。

実装の問題は、remove()(またはそれ以上のunlink())がディレクトリエントリから名前をリンク解除することだけです-そのファイルを参照するファイル記述子はまだ使用可能です。次に、同じ名前の別のファイルを作成できますが、すでに開かれているfdは別の場所を参照しています。

これは、ロック機能に遅延を追加することで実証できます。

int tryGetLock( char const *lockName)
{
    mode_t m = umask( 0 );
    int fd = open( lockName, O_RDWR|O_CREAT, 0666 );
    umask( m );
    printf("Opened the file. Press enter to continue...");
    fgetc(stdin);
    printf("Continuing by acquiring the lock.\n");
    if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 )
    {
        close( fd );
        fd = -1;
    }
    return fd;
}

static const char *lockfile = "/tmp/mylock.lock";

int main(int argc, char *argv[0])
{
    int lock = tryGetLock(lockfile);
    if (lock == -1) {
        printf("Getting lock failed\n");
        return 1;
    }

    printf("Acquired the lock. Press enter to release the lock...");
    fgetc(stdin);

    printf("Releasing...");
    releaseLock(lock, lockfile);
    printf("Done!\n");
    return 0;

}

  1. プロセス#1を開始して、Enterキーを1回押してロックを取得してください。
  2. 次に、別の端末でプロセス#2を開始します。
  3. プロセス#1が実行されている端末で別のEnterキーを押して、ロックを解除します。 4. Enterキーを1回押してプロセス#2を続行し、ロックを取得します。
  4. 次に、プロセス#3を実行する別のターミナルを開きます。そこで、Enterキーを1回押してロックを取得します。

「不可能」が起こります。プロセス#2と#3は、両方とも排他ロックを持っていると考えます。

これは、少なくとも通常のアプリケーションで実際に発生することはまれですが、それでも実装は正しくありません。

また、モード0666でファイルを作成すると、セキュリティ上のリスクが生じる可能性があります。

私は「コメントする評判」がなく、これもかなり古い質問ですが、人々はまだこれを参照して同じようなことをしているので、このメモを回答として追加するのはそのためです。

5
Miika Karanki

Hasturhunの答えを拡張します。ロックファイルの有無をインジケータとして使用する代わりに、ロックファイルが存在しない場合は作成し、ファイルに対して排他ロックを取得する必要があります。

このアプローチの利点は、プログラムを同期する他の多くの方法とは異なり、ロックを解除せずにプログラムが終了した場合、OSが整頓されていることです。

したがって、プログラム構造は次のようになります。

1: open the lock file creating it if it doesn't exist
2: ask for an exclusive lock an agreed byte range in the lock file
3: when the lock is granted then
    4: <do my processing here>
    5: release my lock
    6: close the lock file
end

ステップ:ロックが許可されるのを待つのをブロックするか、すぐに戻ることができます。ロックするバイトは、実際にファイルに存在する必要はありません。 Marc J. RochkindによるAdvanced Unix Programmingのコピーを手に入れることができる場合、彼はこのメソッドを使用して完全なCライブラリを開発し、OSによって整理されるプログラムを同期する方法を提供します。ただし、プログラムは終了します。

2
Jackson

私はchmikeが投稿したコードを使用していて、1つの小さな欠陥に気づきました。ロックファイルを開くときに、レースで問題が発生しました。時々、いくつかのスレッドが同時にロックファイルを開きます。

そのため、「remove(lockName);」を削除しました。 「releaseLock()」関数の行。理由はわかりませんが、何らかの形でこの行動が状況を改善しました。

次のコードを使用して、ロックファイルをテストしています。その出力により、複数のスレッドが1つのロックを同時に使用し始める時期を確認できます。

void testlock(void) {
  # pragma omp parallel num_threads(160)
  {    
    int fd = -1; char ln[] = "testlock.lock";
    while (fd == -1) fd = tryGetLock(ln);

    cout << omp_get_thread_num() << ": got the lock!";
    cout << omp_get_thread_num() << ": removing the lock";

    releaseLock(fd,ln);
  }
}
1
a.grochmal