web-dev-qa-db-fra.com

Comment récupérer un sémaphore lorsque le processus qui l'a décrémenté à zéro se bloque?

J'ai plusieurs applications compilées avec g ++, fonctionnant sous Ubuntu. J'utilise des sémaphores nommés pour coordonner les différents processus.

Tout fonctionne correctement sauf dans la situation suivante: si l'un des processus appelle sem_wait() ou sem_timedwait() pour décrémenter le sémaphore, puis se bloque ou est tué -9 avant il a la chance d'appeler sem_post(), puis à partir de ce moment, le sémaphore nommé est "inutilisable".

Par "inutilisable", ce que je veux dire, c'est que le nombre de sémaphores est maintenant nul, et le processus qui aurait dû l'incrémenter à 1 est mort ou a été tué.

Je ne trouve pas une API sem_*() qui pourrait me dire que le processus qui a été décrémenté en dernier s'est écrasé.

Suis-je manquant d'une API quelque part?

Voici comment j'ouvre le sémaphore nommé:

sem_t *sem = sem_open( "/testing",
    O_CREAT     |   // create the semaphore if it does not already exist
    O_CLOEXEC   ,   // close on execute
    S_IRWXU     |   // permissions:  user
    S_IRWXG     |   // permissions:  group
    S_IRWXO     ,   // permissions:  other
    1           );  // initial value of the semaphore

Voici comment je le décrémente:

struct timespec timeout = { 0, 0 };
clock_gettime( CLOCK_REALTIME, &timeout );
timeout.tv_sec += 5;

if ( sem_timedwait( sem, &timeout ) )
{
    throw "timeout while waiting for semaphore";
}
47
Stéphane

Il s'avère qu'il n'y a aucun moyen de récupérer de manière fiable le sémaphore. Bien sûr, n'importe qui peut post_sem() vers le sémaphore nommé pour que le nombre augmente à nouveau après zéro, mais comment savoir quand une telle récupération est nécessaire? L'API fournie est trop limitée et n'indique en aucune façon quand cela s'est produit.

Méfiez-vous des outils ipc également disponibles - les outils courants ipcmk, ipcrm et ipcs sont uniquement pour les sémaphores SysV obsolètes. Ils ne fonctionnent pas spécifiquement avec les nouveaux sémaphores POSIX.

Mais il semble qu'il y ait d'autres choses qui peuvent être utilisées pour verrouiller des choses, que le système d'exploitation libère automatiquement lorsqu'une application meurt d'une manière qui ne peut pas être interceptée dans un gestionnaire de signaux. Deux exemples: un socket d'écoute lié à un port particulier, ou un verrou sur un fichier spécifique.

J'ai décidé que le verrouillage d'un fichier était la solution dont j'avais besoin. Donc, au lieu d'un appel à sem_wait() et sem_post(), j'utilise:

lockf( fd, F_LOCK, 0 )

et

lockf( fd, F_ULOCK, 0 )

Lorsque l'application se ferme de quelque manière que ce soit, le fichier est automatiquement fermé, ce qui libère également le verrouillage du fichier. Les autres applications clientes en attente du "sémaphore" sont alors libres de procéder comme prévu.

Merci pour l'aide les gars.

38
Stéphane

Utilisez un fichier verrou au lieu d'un sémaphore, un peu comme la solution de @ Stéphane mais sans les appels flock (). Vous pouvez simplement ouvrir le fichier à l'aide d'un verrou exclusif:

//call to open() will block until it can obtain an exclusive lock on the file.
errno = 0;
int fd = open("/tmp/.lockfile", 
    O_CREAT | //create the file if it's not present.
    O_WRONLY | //only need write access for the internal locking semantics.
    O_EXLOCK, //use an exclusive lock when opening the file.
    S_IRUSR | S_IWUSR); //permissions on the file, 600 here.

if (fd == -1) {
    perror("open() failed");
    exit(EXIT_FAILURE);
}

printf("Entered critical section.\n);
//Do "critical" stuff here.

//exit the critical section
errno = 0;
if (close(fd) == -1) {
    perror("close() failed");
    exit(EXIT_FAILURE);
}

printf("Exited critical section.\n");
6

Il s'agit d'un problème typique lors de la gestion des sémaphores. Certains programmes utilisent un seul processus pour gérer l'initialisation/suppression du sémaphore. Habituellement, ce processus fait exactement cela et rien d'autre. Vos autres applications peuvent attendre que le sémaphore soit disponible. J'ai vu cela fait avec l'API de type SYSV, mais pas avec POSIX. Similaire à ce que 'Duck' a mentionné, en utilisant l'indicateur SEM_UNDO dans votre appel semop ().


Mais, avec les informations que vous avez fournies, je vous suggère de ne pas utiliser de sémaphores. Surtout si votre processus risque d'être tué ou de s'écraser. Essayez d'utiliser quelque chose que le système d'exploitation nettoiera automatiquement pour vous.

5
Steve Lazaridis

Vous devriez pouvoir le trouver à partir du shell en utilisant lsof. Ensuite, vous pouvez peut-être le supprimer?

Mise à jour

Ah oui... man -k semaphore à la rescousse.

Il semble que vous pouvez utiliser ipcrm pour vous débarrasser d'un sémaphore. Semble que vous n'êtes pas le premier avec ce problème.

2
Carl Smotricz

Vous devrez vérifier deux fois, mais je pense que sem_post peut être appelé à partir d'un gestionnaire de signaux. Si vous parvenez à détecter certaines des situations qui mettent un terme au processus, cela pourrait vous aider.

Contrairement à un mutex, tout processus ou thread (avec des autorisations) peut publier dans le sémaphore. Vous pouvez écrire un simple utilitaire pour le réinitialiser. Vraisemblablement, vous savez quand votre système est bloqué. Vous pouvez le désactiver et exécuter le programme utilitaire.

Le sémaphone est également généralement répertorié sous/dev/shm et vous pouvez le supprimer.

Les sémaphores SysV conviennent mieux à ce scénario. Vous pouvez spécifier SEM_UNDO, dans lequel le système annulera les modifications apportées au sémaphore par un processus s'il meurt. Ils ont également la possibilité de vous indiquer le dernier identifiant de processus pour modifier le sémaphore.

2
Duck

Si le processus a été TUÉ, il n'y aura aucun moyen direct de déterminer s'il a disparu.

Vous pouvez effectuer une sorte de vérification d'intégrité périodique sur tous les sémaphores que vous avez - utilisez semctl (cmd = GETPID) pour trouver le PID du dernier processus qui a touché chaque sémaphore dans l'état que vous décrivez, puis vérifiez si ce processus est toujours là. Sinon, effectuez le nettoyage.

1
martin clayton

Si vous utilisez un sémaphore nommé, vous pouvez utiliser un algorithme comme celui utilisé dans lsof ou fuser.

Prenez-les en considération:

Chaque sémaphore POSIX nommé crée un fichier dans un système de fichiers tmpfs généralement sous le chemin:

/dev/shm/

2.Chaque processus a un map_files sous linux, sous le chemin:

/proc/[PID]/map_files/

Ces fichiers de mappage montrent quelle partie d'une mappe de mémoire de processus vers quoi!

Ainsi, en utilisant ces étapes, vous pouvez déterminer si le sémaphore nommé est toujours ouvert par un autre processus ou non:

1Facultatif) Trouvez le chemin exact du sémaphore nommé (au cas où ce ne serait pas sous /dev/shm)

  • Ouvrez d'abord le sémaphore nommé dans le nouveau processus et affectez le résultat à un pointeur
  • Recherchez l'emplacement de l'adresse du pointeur dans la mémoire (généralement avec une conversion de l'adresse du pointeur en type entier) et convertissez-la en hexadécimal (c'est-à-dire résultat: 0xffff1234) numéro, puis utilisez ce chemin:

    /proc/self/map_files/ffff1234-*

    il ne devrait y avoir qu'un seul fichier répondant à ces critères.

  • Obtenez la cible du lien symbolique de ce fichier. C'est le chemin complet du sémaphore nommé.

2- Parcourez tous les processus pour trouver un fichier de carte dont le taget de liens symboliques correspond au chemin complet du sémaphore nommé. S'il y en a un, le sémaphore est réellement utilisé, mais s'il n'y en a pas, vous pouvez dissocier le sémaphore nommé en toute sécurité et le rouvrir à nouveau pour votre usage.

--- ([~ # ~] mise à jour [~ # ~]

À l'étape 2, lors de l'itération sur tous les processus, au lieu d'itérer sur tous les fichiers du dossier map_file, il vaut mieux utiliser le fichier /proc/[PID]/maps et recherchez le chemin complet du fichier de sémaphore nommé (c'est-à-dire: /dev/shm/sem_xyz) à l'intérieur. Dans cette approche, même si certains autres programmes dissocient le sémaphore nommé mais que le sémaphore utilise toujours dans d'autres processus, il peut toujours être trouvé mais un indicateur "(supprimé)" est ajouté à la fin de son chemin de fichier.

0
AshkanVZ

Faites simplement une sem_unlink() immédiatement après la sem_open(). Linux sera supprimé une fois que tous les processus auront fermé la ressource, ce qui inclut les fermetures internes.

0
gsb