Le 19 août 2013, Randal L. Schwartz a publié this Script shell, qui visait à garantir, sous Linux, "qu'une seule instance de [le] script est en cours d'exécution, sans conditions de concurrence ni devoir nettoyer les fichiers de verrouillage ":
#!/bin/sh
# randal_l_schwartz_001.sh
(
if ! flock -n -x 0
then
echo "$$ cannot get flock"
exit 0
fi
echo "$$ start"
sleep 10 # for testing. put the real task here
echo "$$ end"
) < $0
Il semble fonctionner comme annoncé:
$ ./randal_l_schwartz_001.sh & ./randal_l_schwartz_001.sh
[1] 11863
11863 start
11864 cannot get flock
$ 11863 end
[1]+ Done ./randal_l_schwartz_001.sh
$
Voici ce que je comprends:
<
) une copie de son propre contenu (c'est-à-dire de $0
) vers le STDIN (c'est-à-dire le descripteur de fichier 0
) d'un sous-shell.flock -n -x
) sur le descripteur de fichier 0
. Voici mes questions:
0
dans un Shell empêche une copie du même script, exécuté dans un autre Shell, d'obtenir un verrou exclusif sur le descripteur de fichier 0
? Les shells n'ont pas leurs propres copies séparées des descripteurs de fichiers standard (0
, 1
, et 2
, c'est-à-dire STDIN, STDOUT et STDERR)?Pourquoi le script doit-il rediriger, vers un descripteur de fichier hérité par le sous-shell, une copie de son propre contenu plutôt que, disons, le contenu d'un autre fichier?
Vous pouvez utiliser n'importe quel fichier, tant que toutes les copies du script utilisent le même. L'utilisation de $0
Lie simplement le verrou au script lui-même: si vous copiez le script et le modifiez pour une autre utilisation, vous n'avez pas besoin de trouver un nouveau nom pour le fichier de verrouillage. C'est pratique.
Si le script est appelé via un lien symbolique, le verrou se trouve sur le fichier réel et non sur le lien.
(Bien sûr, si un processus exécute le script et lui donne une valeur composée comme argument zéro au lieu du chemin réel, alors cela se casse. Mais c'est rarement fait.)
(J'ai essayé d'utiliser un fichier différent et de relancer comme ci-dessus, et l'ordre d'exécution a changé)
Êtes-vous sûr que c'était à cause du fichier utilisé, et pas seulement d'une variation aléatoire? Comme avec un pipeline, il n'y a vraiment aucun moyen de savoir dans quel ordre les commandes s'exécutent dans cmd1 & cmd
. Cela dépend principalement du planificateur du système d'exploitation. J'obtiens des variations aléatoires sur mon système.
Pourquoi le script doit-il de toute façon rediriger vers un descripteur de fichier hérité par le sous-shell une copie du contenu d'un fichier?
Il semble que le Shell lui-même contienne une copie de la description du fichier contenant le verrou, au lieu de simplement l'utilitaire flock
le détenant. Un verrou créé avec flock(2)
est libéré lorsque les descripteurs de fichiers le contenant sont fermés.
flock
a deux modes, soit pour prendre un verrou basé sur un nom de fichier, et exécuter une commande externe (auquel cas flock
contient le descripteur de fichier ouvert requis), soit pour prendre un descripteur de fichier de l'extérieur, donc un processus extérieur est responsable de le tenir.
Notez que le contenu du fichier n'est pas pertinent ici et qu'aucune copie n'a été effectuée. La redirection vers le sous-shell ne copie aucune donnée autour d'elle-même, elle ouvre simplement une poignée au fichier.
Pourquoi la détention d'un verrou exclusif sur le descripteur de fichier 0 dans un shell empêche-t-elle une copie du même script, exécuté dans un autre shell, d'obtenir un verrou exclusif sur le descripteur de fichier 0? Les shells n'ont-ils pas leurs propres copies séparées des descripteurs de fichiers standard (0, 1 et 2, c'est-à-dire STDIN, STDOUT et STDERR)?
Oui, mais le verrou est sur le fichier, pas sur le descripteur de fichier. Une seule instance ouverte du fichier peut contenir le verrou à la fois.
Je pense que vous devriez pouvoir faire la même chose sans le sous-shell, en utilisant exec
pour ouvrir un handle vers le fichier de verrouillage:
$ cat lock.sh
#!/bin/sh
exec 9< "$0"
if ! flock -n -x 9; then
echo "$$/$1 cannot get flock"
exit 0
fi
echo "$$/$1 got the lock"
sleep 2
echo "$$/$1 exit"
$ ./lock.sh bg & ./lock.sh fg ; wait; echo
[1] 11362
11363/fg got the lock
11362/bg cannot get flock
11363/fg exit
[1]+ Done ./lock.sh bg
Un verrou de fichier est attaché à un fichier, via une description de fichier . À un niveau élevé, la séquence d'opérations dans une instance du script est la suivante:
Maintenir le verrou empêche l'exécution d'une autre copie du même script, car c'est ce que font les verrous. Tant qu'un verrou exclusif sur un fichier existe quelque part sur le système, il est impossible de créer une deuxième instance du même verrou, même via une description de fichier différente.
L'ouverture d'un fichier crée un description du fichier. Il s'agit d'un objet noyau qui n'a pas beaucoup de visibilité directe dans les interfaces de programmation. Vous accédez à une description de fichier indirectement via des descripteurs de fichier, mais normalement vous la considérez comme l'accès au fichier (lecture ou écriture de son contenu ou de ses métadonnées). Un verrou est l'un des attributs qui sont une propriété de la description du fichier plutôt qu'un fichier ou un descripteur.
Au début, lorsqu'un fichier est ouvert, la description du fichier a un seul descripteur de fichier, mais d'autres descripteurs peuvent être créés soit en créant un autre descripteur (la famille dup
d'appels système) soit en forçant un sous-processus (après auquel le parent et l'enfant ont accès à la même description de fichier). Un descripteur de fichier peut être fermé explicitement ou lorsque le processus dans lequel il se trouve meurt. Lorsque le dernier descripteur de fichier joint à un fichier est fermé, la description du fichier est fermée.
Voici comment la séquence d'opérations ci-dessus affecte la description du fichier.
<$0
ouvre le fichier de script dans le sous-shell, créant une description de fichier. À ce stade, un descripteur de fichier unique est attaché à la description: le numéro de descripteur 0 dans le sous-shell.flock
et attend sa sortie. Pendant que flock est en cours d'exécution, deux descripteurs sont attachés à la description: le numéro 0 dans le sous-shell et le numéro 0 dans le processus de flock. Lorsque flock prend le verrou, cela définit une propriété de la description du fichier. Si une autre description de fichier a déjà un verrou sur le fichier, flock ne peut pas prendre le verrou, car il s'agit d'un verrou exclusif.La raison pour laquelle le script utilise une redirection à partir de $0
est que la redirection est le seul moyen d'ouvrir un fichier dans le shell, et le maintien d'une redirection active est le seul moyen de garder un descripteur de fichier ouvert. Le sous-shell ne lit jamais à partir de son entrée standard, il suffit de le garder ouvert. Dans une langue qui donne un accès direct aux appels ouverts et fermés, vous pouvez utiliser
fd = open($0)
flock(fd, LOCK_EX)
do stuff
close(fd)
Vous pouvez réellement obtenir la même séquence d'opérations dans le shell si vous effectuez la redirection avec le code intégré exec
.
exec <$0
flock -n -x 0
# do stuff
exec <&-
Le script peut utiliser un descripteur de fichier différent s'il souhaite continuer à accéder à l'entrée standard d'origine.
exec 3<$0
flock -n -x 0
# do stuff
exec 3<&-
ou avec un sous-shell:
(
flock -n -x 3
# do stuff
) 3<$0
Le verrou ne doit pas nécessairement se trouver sur le fichier de script. Il peut s'agir de n'importe quel fichier pouvant être ouvert en lecture (il doit donc exister, il doit s'agir d'un type de fichier pouvant être lu, tel qu'un fichier normal ou un canal nommé mais pas un répertoire, et le processus de script doit avoir l'autorisation de le lire). Le fichier de script a l'avantage qu'il est garanti d'être présent et lisible (sauf dans le cas d'Edge où il a été supprimé en externe entre le moment où le script a été appelé et le moment où le script arrive au <$0
redirection).
Tant que flock
réussit et que le script se trouve sur un système de fichiers où les verrous ne sont pas bogués (certains systèmes de fichiers réseau tels que NFS peuvent être bogués), je ne vois pas comment l'utilisation d'un fichier de verrouillage différent pourrait permettre une condition de course. Je soupçonne une erreur de manipulation de votre part.
Le fichier utilisé pour le verrouillage est sans importance, le script utilise $0
car il s'agit d'un fichier connu pour exister.
L'ordre dans lequel les verrous sont obtenus sera plus ou moins aléatoire, selon la vitesse à laquelle votre machine est en mesure de démarrer les deux tâches.
Vous pouvez utiliser n'importe quel descripteur de fichier, pas nécessairement 0. Le verrou est maintenu sur le fichier ouvert au descripteur de fichier, pas le descripteur lui-même.
( flock -x 9 || exit 1
echo 'Locking for 5 secs'; sleep 5; echo 'Done' ) 9>/tmp/lock &