web-dev-qa-db-fra.com

Méthode de fichier de verrouillage optimal

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

Afin de garantir un accès exclusif à un fichier ou de certains périphériques, il est courant d'utiliser un fichier de verrouillage habituellement stocké dans le répertoire/var/var.

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

Lorsque la ressource est relâchée, le fichier de verrouillage est supprimé par l'instruction suivante remove( "/var/lock/myLock.lock" ).

Cette méthode présente deux problèmes.

  1. Le programme peut terminer sans supprimer la serrure. Par exemple parce qu'il est tué, se bloque ou autre chose. Le fichier de verrouillage reste en place et empêchera tout accès à la ressource même s'il n'est plus utilisé.

  2. Le fichier de verrouillage est créé avec le Privilege de groupe et World Write, mais il s'agit d'une pratique courante pour configurer des comptes pour utiliser un masque d'autorisation qui effacera la permission du groupe et de l'écriture mondiale. Ainsi, si nous avions une méthode fiable pour déterminer que le verrou est orphelin (non utilisé), un utilisateur non propriétaire du fichier ne sera pas autorisé à le supprimer.

Pour l'enregistrement, j'utilise le fichier de verrouillage pour assurer un accès exclusif au périphérique connecté au port série (/ dev/ttyusbx en fait). Méthode de conseil, nécessitant une coopération, est correct. Mais l'accès exclusif devrait être assuré entre différents utilisateurs.

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

Une solution que j'ai proposée était d'utiliser le fichier de fichier UNIX. Si le fichier existe, essayez de vous connecter à l'aide du fichier. Si cela échoue, nous pouvons assumer que le processus propriétaire du fichier est mort. Cela nécessite d'avoir une boucle de fil sur la prise accept() dans le processus propriétaire. Malheureusement, le système ne serait plus atomique.

34
chmike

Jetez un coup d'œil à la présentation éclairante trucs de verrouillage du fichier et pièges :

Cette courte conversation présente plusieurs pièges communs de verrouillage de fichiers et quelques astuces utiles pour l'utilisation de fichiers verrouillant plus efficacement.

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

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

Comme @Hasturkun déjà mentionné et comme la présentation ci-dessus a dit, l'appel du système que vous devez utiliser est flock(2) . Si la ressource que vous souhaitez partager sur de nombreux utilisateurs est déjà basée sur le fichier (dans votre cas, c'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 de verrouillage est toujours en cours d'exécution?

Vous n'avez pas besoin de déterminer cela, car le verrouillage flock- ed sera automatiquement publié lors de la fermeture du descripteur de fichier associé à votre fichier, même si le processus a été terminé.

Comment permettre à un autre utilisateur de supprimer le fichier de verrouillage s'il n'est pas utilisé?

Si vous verrouillez le fichier de périphérique lui-même, il n'y aura plus besoin de supprimer le fichier. Même si vous décidez de verrouiller un fichier ordinaire dans /var/lock, avec flock vous n'aurez pas besoin de supprimer le fichier afin de libérer la serrure.

36
Andrey Vlasovskikh

Vous devriez probablement utiliser flock() , comme dans

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

Soyez prudent avec les fonctions de verrouillage de verrouillage et de libération implémentées comme mentionnées dans l'une des réponses, c'est-à-dire 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 la suppression d'appel de Releaselock introduit un bogue de situation de course. Considérons qu'il y a trois processus essentiellement d'acquérir le troupeau exclusif avec un chronométrage méchant:

  • Le processus n ° 1 a ouvert le fichier de verrouillage, a acquis le troupeau et est sur le point d'appeler la fonction de déverrouillage, mais pas encore cela.
  • Le processus n ° 2 a été appelé ouvert pour ouvrir le fichier pointé de verrouillage, et dispose d'un descripteur de fichier, mais pas encore appelé Flock. C'est-à-dire que le fichier pointé par LockName est ouvert deux fois maintenant.
  • Le processus n ° 3 n'est pas encore commencé.

Avec un chronométrage méchant, il est possible que le processus n ° 1 appelle d'abord enlever () et fermer () (la commande n'a pas d'importance), puis le processus n ° 2 appelle le troupeau à l'aide du descripteur de fichier déjà ouvert, mais qui n'est plus la Fichier nommé LockName mais un descripteur de fichier qui n'est pas lié à une entrée de répertoire.

Maintenant, si le processus n ° 3 est démarré, l'appel Open () de celui-ci crée le fichier Lockname et acquiert la serrure à ce poste car ce fichier n'est pas verrouillé. À titre de résultat, les processus n ° 2 et n ° 3 pensent tous les deux propriétaires de la verrouillage du nom de fichier -> un bug.

Le problème de la mise en œuvre est que supprimer () (ou plus Dislinkink ()) Diffuser uniquement le nom de l'entrée Répertoire - Le descripteur de fichier faisant référence à ce fichier est toujours utilisable. On peut créer un autre fichier ayant le même nom, mais toujours le FD déjà ouvert fait référence à un endroit différent.

Cela peut être démontré d'ajouter du délai à 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 démarrer le processus n ° 1 et appuyez sur Entrée une fois pour acquérir la serrure.
  2. Puis démarrez le processus n ° 2 sur un autre terminal,
  3. Appuyez sur une autre entrée sur le terminal où le processus n ° 1 est en cours d'exécution pour libérer le verrou. 4. Continuez avec le processus n ° 2 en appuyant sur Entrée une fois de manière à ce qu'il acquiert la serrure.
  4. Puis ouvrez un autre terminal où exécuter le processus n ° 3. De là, appuyez une fois sur Entrée pour acquérir la serrure.

L'impossible "se produit: les processus n ° 2 et n ° 3 pensent qu'ils ont tous les deux le verrou exclusif.

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

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

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

5
Miika Karanki

Développer la réponse de Hasturhun. Au lieu d'utiliser la présence ou l'absence du fichier de verrouillage en tant qu'indicateur, vous devez créer un fichier de verrouillage s'il n'existe pas, puis obtenez une serrure exclusive dans le fichier.

Les avantages de cette approche sont que contrairement à de nombreuses autres méthodes de synchronisation des programmes, le système d'exploitation devrait vous ranger si votre programme sort sans déverrouillage.

La structure du programme serait donc 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

A l'étape: vous pouvez soit bloquer l'attente de la serrure à accorder ou de retourner immédiatement. Les octets que vous verrouillez ne doivent pas réellement exister dans le fichier. Si vous pouvez obtenir une copie de Programmation avancée Unix de Marc J. Rochkind, il développe une bibliothèque C complète C qui utilise cette méthode pour fournir une façon de synchroniser des programmes qui se tiendront par le système d'exploitation, Cependant, les programmes quittent.

2
Jackson

J'utilisais le code posté par Chmike et remarquais une petite imperfection. J'ai eu un problème avec la course pendant le fichier de verrouillage d'ouverture. Parfois, plusieurs threads ouvrent simultanément le fichier de verrouillage.

Par conséquent, j'ai supprimé le "Supprimer (Lockname);" ligne de "ReleasElock ()" fonction. Je ne comprends pas pourquoi, mais 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 threads commencent à utiliser une seule 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
a.grochmal