W dniu 19 sierpnia 2013 r. Randal L. Schwartz opublikował ten skrypt powłoki, który miał zapewnić w systemie Linux, że „działa tylko jedna instancja skryptu [bez] warunków wyścigu lub konieczności czyszczenia plików blokujących”:
#!/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
Wygląda na to, że działa zgodnie z reklamą:
$ ./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
$
Oto, co rozumiem:
- Skrypt przekierowuje (
<
) kopię własnej zawartości (tj. Z$0
) do STDIN (tj. Deskryptora pliku0
) podpowłoki. - W ramach podpowłoki skrypt próbuje uzyskać nieblokującą, wyłączną blokadę (
flock -n -x
) deskryptora pliku0
.- Jeśli ta próba się nie powiedzie, podpowłoka kończy działanie (podobnie jak główny skrypt, ponieważ nie ma nic więcej do zrobienia).
- Jeśli próba zakończy się powodzeniem, podpowłoka uruchamia żądane zadanie.
Oto moje pytania:
- Dlaczego skrypt musi przekierowywać do deskryptora pliku odziedziczonego przez podpowłokę kopię własnej zawartości zamiast, powiedzmy, zawartości innego pliku? (Próbowałem przekierować z innego pliku i uruchomić ponownie jak wyżej, a kolejność wykonywania uległa zmianie: zadanie bez tła zyskało blokadę przed drugim. Więc może użycie własnej zawartości pliku pozwala uniknąć warunków wyścigu; ale jak?)
- Dlaczego w każdym razie skrypt musi przekierowywać do deskryptora pliku odziedziczonego przez podpowłokę, kopię zawartości pliku?
- Dlaczego trzymanie wyłącznej blokady deskryptora pliku
0
w jednej powłoce uniemożliwia kopii tego samego skryptu działającego w innej powłoce uzyskanie wyłącznej blokady deskryptora pliku0
? Nie muszle mają swoje własne, oddzielne kopie standardowych deskryptorów (0
,1
, i2
, tj stdin, stdout i stderr)?
linux
shell-script
io-redirection
subshell
lock
sampablokuper
źródło
źródło
Odpowiedzi:
Możesz użyć dowolnego pliku, o ile wszystkie kopie skryptu używają tego samego. Użycie
$0
tylko przywiązuje blokadę do samego skryptu: jeśli skopiujesz skrypt i zmodyfikujesz go w innym celu, nie musisz wymyślać nowej nazwy pliku blokady. To jest wygodne.Jeśli skrypt jest wywoływany przez dowiązanie symboliczne, blokada znajduje się w rzeczywistym pliku, a nie w dowiązaniu.
(Oczywiście, jeśli jakiś proces uruchamia skrypt i nadaje mu wymyśloną wartość jako argument zerowy zamiast rzeczywistej ścieżki, wtedy się psuje. Ale to rzadko się zdarza.)
Czy na pewno było to spowodowane użytym plikiem, a nie tylko przypadkową odmianą? Podobnie jak w przypadku potoku, tak naprawdę nie ma pewności, w jakiej kolejności będą uruchamiane polecenia
cmd1 & cmd
. To zależy głównie od harmonogramu systemu operacyjnego. Dostaję losowe zmiany w moim systemie.Wygląda na to, że sama powłoka przechowuje kopię opisu pliku zawierającego blokadę, a nie tylko
flock
narzędzie przechowujące. Utworzona blokadaflock(2)
jest zwalniana, gdy deskryptory plików, które ją posiadają, są zamknięte.flock
ma dwa tryby, albo do zablokowania na podstawie nazwy pliku i uruchomienia zewnętrznego polecenia (w którym to przypadkuflock
zawiera wymagany otwarty deskryptor pliku) lub do pobrania deskryptora pliku z zewnątrz, więc proces zewnętrzny jest odpowiedzialny za przechowywanie to.Pamiętaj, że zawartość pliku nie ma tutaj znaczenia i nie ma kopii. Przekierowanie do podpowłoki nie kopiuje żadnych danych wokół siebie, po prostu otwiera uchwyt do pliku.
Tak, ale blokada pliku , a nie deskryptor pliku. Tylko jedna otwarta instancja pliku może przytrzymywać blokadę jednocześnie.
Myślę, że powinieneś być w stanie zrobić to samo bez podpowłoki,
exec
otwierając uchwyt pliku blokady:źródło
{ }
zamiast zamiast( )
również działałoby i unikało podpowłoki.exec
.Blokada pliku jest dołączona do pliku poprzez opis pliku . Na wysokim poziomie sekwencja operacji w jednym wystąpieniu skryptu to:
Trzymanie blokady uniemożliwia uruchomienie kolejnej kopii tego samego skryptu, ponieważ tak właśnie działają blokady. Tak długo, jak gdzieś w systemie istnieje wyłączna blokada pliku, niemożliwe jest utworzenie drugiej instancji tej samej blokady, nawet poprzez inny opis pliku.
Otwarcie pliku tworzy opis pliku . Jest to obiekt jądra, który nie ma dużej bezpośredniej widoczności w interfejsach programistycznych. Dostęp do opisu pliku uzyskuje się pośrednio za pomocą deskryptorów plików, ale zwykle uważa się go za dostęp do pliku (odczyt lub zapis jego zawartości lub metadanych). Blokada jest jednym z atrybutów, które są właściwością opisu pliku, a nie pliku lub deskryptora.
Na początku, gdy plik jest otwierany, opis pliku ma jeden deskryptor pliku, ale więcej deskryptorów można utworzyć, tworząc inny deskryptor (
dup
rodzinę wywołań systemowych) lub rozwidlając podproces (po czym zarówno rodzic, jak i dziecko ma dostęp do tego samego opisu pliku). Deskryptor pliku można zamknąć jawnie lub gdy proces, w którym się znajduje, umiera. Gdy ostatni deskryptor pliku dołączony do pliku zostanie zamknięty, opis pliku zostanie zamknięty.Oto jak powyższa sekwencja operacji wpływa na opis pliku.
<$0
otwiera plik skryptu w podpowłoce, tworząc opis pliku. W tym momencie do opisu dołączony jest jeden deskryptor pliku: deskryptor numer 0 w podpowłoce.flock
i czeka na zakończenie. Podczas działania stada do opisu dołączone są dwa deskryptory: liczba 0 w podpowłoce i liczba 0 w procesie flokowania. Kiedy stado bierze blokadę, ustawia to właściwość opisu pliku. Jeśli inny opis pliku ma już blokadę pliku, stado nie może przyjąć blokady, ponieważ jest to blokada wyłączna.Powodem, dla którego skrypt używa przekierowania
$0
jest to, że przekierowanie jest jedynym sposobem na otwarcie pliku w powłoce, a utrzymanie przekierowania jest jedynym sposobem na utrzymanie deskryptora pliku otwartego. Podpowłoka nigdy nie czyta ze swojego standardowego wejścia, wystarczy ją otworzyć. W języku, który zapewnia bezpośredni dostęp do otwierania i zamykania połączenia, możesz użyćMożesz faktycznie uzyskać tę samą sekwencję operacji w powłoce, jeśli przekierujesz za pomocą
exec
wbudowanego.Skrypt mógłby użyć innego deskryptora pliku, jeśli chce nadal uzyskiwać dostęp do oryginalnego standardowego wejścia.
lub z podpowłoką:
Blokada nie musi znajdować się w pliku skryptu. Może to być dowolny plik, który można otworzyć do odczytu (więc musi istnieć, musi to być typ pliku, który można odczytać, taki jak zwykły plik lub nazwany potok, ale nie katalog, a proces skryptu musi mieć pozwolenie na przeczytanie go). Zaletą pliku skryptu jest to, że jest obecny i czytelny (z wyjątkiem przypadku krawędzi, w którym został usunięty zewnętrznie między momentem wywołania skryptu a momentem, w którym skrypt dotrze do
<$0
przekierowania).Tak długo, jak się
flock
powiedzie, a skrypt znajduje się w systemie plików, w którym blokady nie są wadliwe (niektóre sieciowe systemy plików, takie jak NFS, mogą być wadliwe), nie widzę, jak użycie innego pliku blokady może pozwolić na wyścig. Podejrzewam błąd manipulacji z twojej strony.źródło
Plik używany do blokowania jest nieistotny, skrypt używa,
$0
ponieważ jest to plik, o którym wiadomo, że istnieje.Kolejność uzyskiwania blokad będzie mniej więcej losowa, w zależności od tego, jak szybko Twoja maszyna jest w stanie uruchomić dwa zadania.
Możesz użyć dowolnego deskryptora pliku, niekoniecznie 0. Blokada jest utrzymywana na pliku otwartym dla deskryptora pliku, a nie na samym deskryptorze.
źródło