Bu komut dosyası yalnızca bir örneğinin çalışmasını nasıl sağlar?


22

19 Ağustos 2013 tarihinde, Randal L. Schwartz , Linux'ta, "[b] betiğinin yalnızca bir örneğinin yarış koşulları olmadan veya kilit dosyalarını temizlemek zorunda kalmadan" çalışmasını sağlamak için tasarlanan bu kabuk betiğini yayınladı :

#!/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

Reklamı yapıldığı gibi çalışıyor gibi görünüyor:

$ ./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
$

İşte anladığım şey:

  • Komut dosyası ( <) kendi içeriğinin bir kopyasını (yani buradan $0) 0bir alt kabuğun STDIN'ine (yani dosya tanımlayıcısına ) yönlendirir.
  • Alt kabuk içinde, komut dosyası flock -n -x, dosya tanıtıcısında engelleyici olmayan, özel bir kilit ( ) almaya çalışır 0.
    • Bu deneme başarısız olursa, alt kabuk çıkar (ve yapacak başka bir şey olmadığından ana komut dosyası da gider).
    • Deneme yerine başarılı olursa, alt kabuk istenen görevi çalıştırır.

İşte benim sorularım:

  • Neden komut dosyasının, alt kabuk tarafından miras alınan bir dosya tanımlayıcısına, başka bir dosyanın içeriğinden ziyade kendi içeriğinin bir kopyasını yönlendirmesi gerekiyor ? (Farklı bir dosyadan yönlendirmeyi ve yeniden çalıştırmayı denedim ve yürütme sırası değişti: arka plan dışı görev arka plandan önce kilitlendi. Yani, belki de dosyanın içeriğini kullanmak yarış koşullarından kaçınır; ama nasıl?)
  • Neden komut dosyasının, alt kabuk tarafından miras alınan bir dosya tanımlayıcısına, bir dosyanın içeriğinin bir kopyasını yine de yönlendirmesi gerekiyor?
  • Dosya tanımlayıcıda özel bir kilidi 0tek bir kabukta tutmak , aynı komut dosyasının farklı bir kabukta çalışan bir kopyasının dosya tanımlayıcıda özel bir kilitlenmesini neden önlüyor 0? Kabukları standart dosya tanımlayıcıları (kendi ayrı kopyalarını yok mu 0, 1ve 2, yani STDIN, STDOUT ve STDERR)?

Farklı bir dosyadan yönlendirmek için denemenizi denediğinizde tam test süreciniz neydi?
Freiheit

1
Bence bu bağlantıya başvurabilirsin. stackoverflow.com/questions/185451/…
Deb Paikar

Yanıtlar:


22

Neden komut dosyasının, alt kabuk tarafından miras alınan bir dosya tanımlayıcısına, başka bir dosyanın içeriğinden ziyade kendi içeriğinin bir kopyasını yönlendirmesi gerekiyor?

Komut dosyasının tüm kopyaları aynı dosyayı kullandığı sürece herhangi bir dosyayı kullanabilirsiniz. Kullanımı $0Sadece senaryodan kendisine kilidi bağlar: Senaryoyu kopyalamak ve diğer bazı kullanım için değiştirmek, kilit dosyası için yeni bir isim bulması gerekmez. Bu uygun.

Eğer komut dosyası bir link üzerinden çağrılırsa, kilit link üzerinde değil gerçek dosyadadır.

(Elbette, bazı işlemler komut dosyasını çalıştırır ve gerçek yol yerine sıfırcı argüman olarak yapılmış bir değer verirse, o zaman bu kırılır. Ancak bu nadiren yapılır.)

(Farklı bir dosya kullanmaya ve yukarıdaki gibi yeniden çalıştırmaya çalıştım ve yürütme sırası değişti)

Bunun sadece rastgele varyasyon değil, kullanılan dosyadan kaynaklandığından emin misiniz? Bir boru hattında olduğu gibi, komutların hangi sırada çalıştırılacağından emin olmanın bir yolu yok cmd1 & cmd. Çoğu zaman işletim sistemi zamanlayıcısına bağlı. Sistemimde rastgele çeşitlilik alıyorum.

Neden komut dosyasının, alt kabuk tarafından miras alınan bir dosya tanımlayıcısına, bir dosyanın içeriğinin bir kopyasını yine de yönlendirmesi gerekiyor?

Öyle gözüküyor ki, kabuğun kendisi yalnızca flocktutan yardımcı program yerine kilidi tutan dosya açıklamasının bir kopyasını tutar . Birlikte flock(2)verilen dosya tanımlayıcıları kapatıldığında yapılan bir kilit açılır.

flockdosya adına göre kilitleme yapmak ve bir dış komut çalıştırmak (bu durumda flockgerekli olan açık dosya tanımlayıcısını tutmak) veya dışardan bir dosya tanımlayıcısını almak için iki mod vardır; o.

Dosyanın içeriğinin burada önemli olmadığını ve kopyaların bulunmadığını unutmayın. Alt kabuk yönlendirme, kendi içindeki herhangi bir veriyi kopyalamaz, sadece dosyaya bir tutamaç açar.

Dosya tanımlayıcı 0'da özel bir kilidi bir kabukta tutmak, aynı komut dosyasının bir kopyasının farklı bir kabukta çalışan bir kopyasını dosya tanımlayıcı 0'da özel bir kilitlenmesini neden önlüyor? Kabukların standart dosya tanımlayıcılarının (0, 1 ve 2, yani STDIN, STDOUT ve STDERR) ayrı kopyaları yok mu?

Evet, ama kilidi açık dosyasında değil, dosya tanımlayıcısı. Dosyanın yalnızca bir açılan örneği bir anda kilidi tutabilir.


execKilit dosyasına bir tanıtıcı açmak için kullanarak, alt kabuk olmadan aynı işlemi yapabilmeniz gerektiğini düşünüyorum :

$ 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

1
{ }Bunun yerine kullanmak ( )da işe yarar ve denizaltıyı engellerdi.
R. ..

G + postasındaki yorumlarda ayrıca, birileri de kabaca aynı yöntemi kullanarak önerdi exec.
David Z,

@R .., oh, tabi. Ama asıl senaryo etrafındaki ekstra diş telleri ile hala çirkin.
ilkkachu

9

Bir dosya kilit takıldığında için bir içinden, bir dosyanın dosya açıklaması . Yüksek düzeyde, komut dosyasının bir örneğindeki işlemlerin sırası:

  1. Kilidin takılı olduğu dosyayı açın (“kilit dosyası”).
  2. Kilit dosyasına kilitlen.
  3. Şeyler yapmak.
  4. Kilit dosyasını kapatın. Bu, bir dosyayı açarak oluşturulan dosya açıklamasına eklenen kilidi serbest bırakır.

Kilidi basılı tutmak, aynı betiğin başka bir kopyasının çalışmasını engeller çünkü kilitlerin yaptığı budur. Bir dosyadaki özel bir kilit sistemde bir yerde olduğu sürece, aynı dosya kilidinin ikinci bir örneğini, hatta farklı bir dosya açıklamasıyla oluşturmak imkansızdır.

Bir dosyayı açmak bir dosya açıklaması oluşturur . Programlama arayüzlerinde doğrudan görünürlüğü olmayan bir çekirdek nesnesidir. Bir dosya açıklamasına dolaylı olarak dosya tanımlayıcıları aracılığıyla erişirsiniz, ancak normalde dosyaya erişiyor olduğunu düşünürsünüz (içeriğini veya meta verilerini okumak veya yazmak). Kilit, bir dosya yerine tanımlayıcıya veya dosya tanımlayıcısına özgü özelliklerden biridir.

Başlangıçta, bir dosya açıldığında, dosya açıklaması tek bir dosya tanımlayıcısına sahiptir, ancak başka bir tanımlayıcı ( dupsistem çağrıları ailesi) oluşturarak veya bir alt işlemi (daha sonra hem ana öğeyi hem de ana bilgisayarı) işaretleyerek daha fazla tanımlayıcı oluşturulabilir . çocuk aynı dosya tanımına erişebiliyor). Bir dosya tanıtıcısı açıkça veya içinde bulunduğu işlem öldüğünde kapatılabilir. Bir dosyaya eklenen son dosya tanımlayıcısı kapatıldığında, dosya açıklaması kapatılır.

Yukarıdaki işlemlerin sırasının dosya açıklamasını nasıl etkilediği aşağıda açıklanmıştır.

  1. Yönlendirme <$0, komut dosyasını alt kabukta açar ve bir dosya açıklaması oluşturur. Bu noktada açıklamaya ekli tek bir dosya tanıtıcısı var: alt kabuktaki tanımlayıcı numarası 0.
  2. Alt kabuk çağırır flockve çıkmasını bekler. Sürü çalışırken, açıklamaya eklenmiş iki tanımlayıcı vardır: alt kabuktaki sayı 0 ve sürü işlemindeki sayı 0. Flock kilidi aldığında, bu dosya açıklamasının bir özelliğini belirler. Başka bir dosya açıklamasında zaten bir dosya kilidi varsa, özel bir kilit olduğundan, kilit kilidi alamaz.
  3. Alt kabuk işleri yapar. Kilidi olan açıklamada hala açık bir dosya tanımlayıcısı bulunduğundan, bu açıklama mevcut kalır ve hiç kimse kilidi kaldırmadığı için kilidini korur.
  4. Alt kabuk kapanış parantezinde ölür. Bu, kilidi olan dosya tanımındaki son dosya tanımlayıcısını kapatır, bu nedenle kilit bu noktada kaybolur.

Komut dosyasının yeniden yönlendirmeyi kullanmasının nedeni, yeniden $0yönlendirmenin kabukta bir dosyayı açmanın tek yoludur ve yeniden yönlendirmeyi etkin tutmanın bir dosya tanıtıcısını açık tutmanın tek yoludur. Alt kabuk asla standart girişinden okumaz, sadece açık tutması gerekir. Açık ve kapalı aramaya doğrudan erişim sağlayan bir dilde,

fd = open($0)
flock(fd, LOCK_EX)
do stuff
close(fd)

execYerleşik ile yönlendirme yaparsanız, kabukta aynı işlem sırasını gerçekten alabilirsiniz .

exec <$0
flock -n -x 0
# do stuff
exec <&-

Komut, orijinal standart girişe erişmeye devam etmek istiyorsa farklı bir dosya tanımlayıcısı kullanabilir.

exec 3<$0
flock -n -x 0
# do stuff
exec 3<&-

veya bir deniz kabuğu ile:

(
  flock -n -x 3
  # do stuff
) 3<$0

Kilit komut dosyasında olmak zorunda değildir. Okumak için açılabilen herhangi bir dosyada olabilir (bu yüzden var olmalı, normal bir dosya veya adlandırılmış bir kanal gibi okunabilen bir dosya türü olmalı, ancak bir dizin olmamalı ve komut dosyası işleminin olması gerekir). okuma izni). Komut dosyasının mevcut ve okunabilir olmasını garanti altına alma avantajı vardır (komut dosyasının çağrıldığı zaman ile komut dosyasının <$0yönlendirilmesine ulaştığı zaman arasında harici olarak silindiği kenar durumlar hariç ).

flockBaşarılı olduğu ve komut dosyası kilitlerin dolaşmadığı bir dosya sistemi üzerinde olduğu sürece (NFS gibi bazı ağ dosya sistemleri buggy olabilir), farklı bir kilit dosyasının kullanılmasının yarış koşullarına nasıl izin verebileceğini anlamıyorum. Sizin tarafınızdan bir manipülasyon hatası olduğundan şüpheleniyorum.


Bir yarış durumu var: betiğin hangi örneğinin kilitleneceğini kontrol edemezsiniz . Neyse ki, hemen hemen tüm amaçlar için, önemli değil.
Mark

4
@Mark Kilidi açan bir yarış var, ancak bu bir yarış koşulu değil. Bir yarış koşulu , zamanlamanın kötü bir şeyin olmasına izin vermesidir, örneğin iki süreç aynı kritik bölümdedir. Kritik kısma hangi sürecin gireceğini bilmemek belirsizcilikçiliği beklemiyor, bu bir yarış koşulu değil.
Gilles 'SO- kötülükten vazgeç'

1
Sadece FYI, "dosya açıklaması" ndaki bağlantı, yapmayı düşündüğünüz şey olan kavramın spesifik bir tanımından ziyade Açık Grup özellikleri dizin sayfasına işaret ediyor. Veya eski cevabınızı buraya da bağlayabilirsiniz unix.stackexchange.com/a/195164/85039
Sergiy Kolodyazhnyy

5

Kilitleme için kullanılan dosya önemsizdir, komut dosyası kullandığı $0için var olduğu bilinen bir dosyadır.

Kilitlerin alınma sırası, makinenizin iki görevi ne kadar hızlı başlatabildiğine bağlı olarak, az çok rastgele olacaktır.

Herhangi bir dosya tanımlayıcısını kullanabilirsiniz, 0 olması gerekmez. Kilit, tanımlayıcının kendisinde değil, dosya tanımlayıcısına açılan dosyada tutulur .

( flock -x 9 || exit 1
  echo 'Locking for 5 secs'; sleep 5; echo 'Done' ) 9>/tmp/lock &
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.