Optimale fichier de verrouillage méthode de

Windows a une option pour ouvrir un fichier avec des droits d'accès exclusifs. Unix n'est pas.

afin d'assurer un accès exclusif à un fichier ou à un périphérique, Il est pratique courante dans Unix d'utiliser un fichier lock habituellement stocké dans le répertoire /var/lock.

l'instruction C open( "/var/lock/myLock.lock", O_RDWR | O_CREAT | O_EXCL, 0666 ) retourne -1 si le verrouillage de fichier existe déjà, sinon il le crée. La fonction est atomique et assure il n'y a pas de condition de course.

Lorsque la ressource est libérée, la serrure fichier est supprimé par l'instruction suivante remove( "/var/lock/myLock.lock" ).

Il y a deux problèmes avec cette méthode.

  1. le programme peut se terminer sans enlever la serrure. Par exemple, parce qu'il est tué, s'écrase ou autre. Le fichier lock reste en place, et empêchera tout accès à la ressource même si elle n'est plus utilisée.

  2. le fichier lock est créé avec le privilège de groupe et World write mais c'est une pratique courante de configurer les comptes pour utiliser un masque de permission qui effacera le groupe et le monde permission d'écriture. Ainsi, si nous avions une méthode fiable pour déterminer que le verrou est orphelin (pas d'utilisation), un utilisateur non propriétaire du fichier ne sera pas autorisé à le supprimer.

pour mémoire, j'utilise le fichier lock pour assurer l'accès exclusif au périphérique connecté au port série (/dev/ttyUSBx en fait). Méthode de conseil, nécessitant la coopération, est OK. Mais l'accès exclusif doit être assurée entre différents utilisateurs.

Est-il une meilleure méthode de synchronisation que le fichier de verrouillage? Comment déterminer si le processus qui a créé le fichier lock est toujours en cours d'exécution? Comment permettre à un autre utilisateur de supprimer le fichier lock s'il n'est pas utilisé?

une solution que j'ai trouvée était d'utiliser le fichier comme fichier de socket Unix. Si le fichier existe, essayez de vous connecter en utilisant le fichier. Si elle échoue, nous pouvons supposer que le propriétaire du processus du fichier est morte. Cela nécessite d'avoir un fil boucle sur socket accept() dans le processus du propriétaire. Malheureusement, le système ne serait plus atomique.

29
demandé sur BartoszKP 2009-10-21 12:31:04

6 réponses

jetez un oeil à la présentation riche d' ruses et pièges de verrouillage des fichiers:

cette brève présentation présente plusieurs pièges courants du verrouillage de fichier et quelques trucs utiles pour utiliser le verrouillage de fichier plus efficacement.

Edit: pour répondre plus précisément à vos questions:

Est-il une meilleure méthode de synchronisation que le fichier de verrouillage?

comme @Hasturkun déjà mentionné et que la présentation ci-dessus dit, l'appel système, vous devez utiliser est flock(2). Si la ressource que vous souhaitez partager avec de nombreux utilisateurs est déjà basée sur des fichiers (dans votre cas, elle est /dev/ttyUSBx), alors vous pouvez flock le fichier de périphérique lui-même.

Comment déterminer si le processus qui a créé le fichier lock est toujours en cours d'exécution?

Vous n'avez pas à le déterminer, comme l' flock-ed verrou est automatiquement libérés lors de la fermeture du descripteur de fichier associé à votre fichier, même si le processus a été interrompu.

comment rendre possible pour un autre utilisateur de supprimer le fichier lock s'il n'est pas utilisé?

Si vous voulez verrouiller le fichier de périphérique lui-même, alors il n'y aura pas besoin de supprimer le fichier. Même si vous décidez de verrouiller un fichier ordinaire dans /var/lockflock vous n'aurez pas besoin de supprimer le fichier pour libérer la serrure.

30
répondu Andrey Vlasovskikh 2009-10-21 13:02:41

Vous devriez probablement utiliser des flock(), en

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
}
20
répondu Hasturkun 2009-10-21 15:53:45

La réponse de Hasturkun est celui qui m'a mis sur la bonne voie.

Voici le code que j'utilise

#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
répondu chmike 2009-10-30 15:55:57

j'utilisais le code posté par chmike, et j'ai remarqué une petite imperfection. J'ai eu un problème avec la course lors de l'ouverture du fichier de verrouillage. Parfois, plusieurs threads ouvrent le fichier lock simultanément.

par conséquent, j'ai supprimé la ligne" remove( lockName); "de la fonction" releaseLock ()". Je ne comprends pas pourquoi, mais d'une certaine manière cette action a aidé la situation.

j'utilise le code suivant pour tester les fichiers de verrouillage. Par sa sortie, on peut voir lorsque plusieurs les threads commencent à utiliser une serrure simultanément.

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
répondu a.grochmal 2014-10-09 17:00:05

attention avec les fonctions de verrouillage et de déverrouillage implémentées comme mentionné dans l'une des réponses, i.e. comme ceci:

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;
}

et:

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

le problème est que l'appel de suppression de releaseLock introduit un bug de situation de course. Considérons trois processus tout en essayant d'acquérir l'exclusivité troupeau avec nasty calendrier:

  • Processus 1 a ouvert le fichier de verrouillage, acquis le troupeau, et est sur le point d'appeler la fonction de déverrouillage, mais pas encore fait.
  • processus #2 a appelé open pour ouvrir le fichier pointé be lockName, et a obtenu un descripteur de fichier de celui-ci, mais pas encore appelé flock. Qui est, le fichier pointé par lockName est ouvert deux fois maintenant.
  • le processus n ° 3 n'est pas encore lancé.

avec le mauvais timing, il est possible que le processus # 1 appelle d'abord remove () et close () (l'ordre n'a pas d'importance), puis le processus #2 appelle le troupeau en utilisant le descripteur de fichier déjà ouvert, mais qui est ce n'est plus le fichier nommé lockName mais un descripteur de fichier qui n'est lié à aucune entrée de répertoire.

maintenant, si le processus #3 est lancé, l'appel open () de celui-ci crée le fichier lockName, et acquiert le verrou sur celui-ci car ce fichier n'est pas verrouillé. En conséquence, les processus #2 et #3 pensent tous les deux posséder le verrou sur fileName -> un bogue.

le problème dans l'implémentation est que remove () (ou plus unlink ()) ne fait que déverrouiller le nom à partir de l'entrée du répertoire - le fichier le descripteur se référant à ce fichier est toujours utilisable. On peut créer puis un autre fichier ayant le même nom, mais le fd déjà ouvert se réfère à un endroit différent.

ceci peut être démontré en ajoutant le delay à la fonction de verrouillage:

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. essayez de lancer le processus #1, et appuyez sur Entrée Une fois pour acquérir la serrure.
  2. puis lancer le processus #2 sur un autre terminal,
  3. Appuyez sur une autre entrée sur le terminal où le processus n ° 1 est en cours pour débloquer la serrure. 4. Continuer avec le processus #2 en appuyant sur Entrer une fois de sorte qu'il acquiert la serrure.
  4. puis ouvrir un autre terminal où exécuter le processus #3. Là-bas, appuyez sur Entrée Une fois pour acquérir la serrure.

l '"impossible" se produit: les processus #2 et #3 pensent qu'ils ont tous les deux la serrure exclusive.

cela pourrait être rare de se produire dans la pratique au moins avec les applications habituelles, mais néanmoins la mise en œuvre n'est pas correct.

en outre, la création d'un fichier avec le mode 0666 pourrait être un risque de sécurité.

Je n'ai pas "la réputation de commenter", et c'est aussi une question assez ancienne, mais les gens se réfèrent encore à cela et font quelque chose de similaire, c'est pourquoi d'ajouter cette note comme réponse.

1
répondu Miika Karanki 2018-09-24 14:27:46

pour développer la réponse de Hasturhun. Au lieu d'utiliser la présence ou l'absence du fichier lock comme indicateur, vous devez à la fois créer le fichier lock s'il n'existe pas et obtenir un lock exclusif sur le fichier.

les avantages de cette approche est que contrairement à beaucoup d'autres méthodes de synchronisation des programmes, L'OS devrait ranger pour vous si votre programme sort sans déverrouiller.

Donc la structure du programme serait quelque chose comme:

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

À l'étape: vous pouvez soit bloquer en attendant que la serrure soit accordée ou revenir immédiatement. Les octets que vous verrouillez n'ont pas à exister dans le fichier. Si vous pouvez vous procurer une copie de Avancé De Programmation Unix par Marc J. Rochkind, il développe une bibliothèque complète en C qui utilise cette méthode pour fournir une façon de synchroniser les programmes qui est mis en ordre par L'OS, cependant les programmes sortent.

0
répondu Jackson 2014-10-28 04:09:03