Hlavní navigace

Co před námi tají /proc (11)

21. 8. 2001
Doba čtení: 8 minut

Sdílet

Znáte to, někde něco chvilku necháte bez dozoru a už je to pryč. Ano, uhodli jste správně, dnes bude řeč o zámcích. Konkrétně o zámcích zmiňovaných v /proc/locks. Jedná se o prostředky k ochraně obsahu souborů - souborové zámky.

Jak velí tradice, nejprve výpis základního adresáře /proc. Tečka před názvem znamená, že už to máme za sebou:

  .840     .devices     .kcore    modules     stat     ide/
  .842     .dma         .kmsg     mounts      swaps    irq/
  .847     .execdomains .ksyms    mtrr        uptime   net/
  .848     .filesystems .loadavg  partitions  version  nv/
  .apm     .interrupts   locks    pci         bus/     sys/
  .cmdline .iomem        meminfo  self@       driver/  sysvipc/
  .cpuinfo .ioports      misc     slabinfo    fs/      tty/
locks

Občas se stane, že s jedním souborem potřebuje pracovat více procesů najednou. Vlastně stačí, když nemůžeme zaručit, že nějaký soubor máme sami pro sebe. Potom může dojít k situaci, že na stejném místě v souboru jeden proces data čte a druhý je zapisuje. Pokud mají takové procesy spolupracovat, je jistě třeba zajistit alespoň minimální synchronizaci. Obdobná situace ale může nastat i pro procesy, které spolu nemají nic společného (kromě zmíněného souboru). Tam je to o to horší, že nemáme vůbec žádnou představu, které procesy, kdy, na jakém místě a jak s daným souborem pracují.

Řešením, které pro takové situace poskytuje operační systém, jsou souborové zámky. Informace o nich poskytuje právě popisovaný soubor:

1: POSIX  ADVISORY  WRITE 657 03:03:32413 0 EOF \
   c15c9178 c01fdb70 c15c90c4 00000000 c15c93ac
1: -> POSIX  ADVISORY  WRITE 479 03:03:32413 0 EOF \
   c15c93a0 c15c9400 c01fdb78 c15c9178 c15c9408
1: -> POSIX  ADVISORY  WRITE 659 03:03:32413 0 EOF \
   c15c93fc c15c9514 c15c93a4 c15c9178 c15c951c
1: -> POSIX  ADVISORY  WRITE 661 03:03:32413 0 EOF \
   c15c9510 c15c9120 c15c9400 c15c9178 c15c9128
1: -> POSIX  ADVISORY  WRITE 660 03:03:32413 0 EOF \
   c15c911c c01fdb78 c15c9514 c15c9178 c15c9184
2: FLOCK  ADVISORY  WRITE 490 03:03:115476 0 EOF \
   c15c90c0 c15c917c c01fdb70 00000000 c15c90cc

Znakem „\“ je rozdělen původně jeden řádek na dva. Celkem je tedy v souboru šest záznamů, každý o 13(+1) sloupcích. Je toho poměrně dost, tak si to projdeme pěkně postupně. První sloupec je číslo zámku. To se může opakovat, protože stejný zámek může mít více procesů. První řádek udává aktuálního majitele zámku, proces, který má nyní k souboru přístup. Další řádky se stejným číslem udávají ostatní procesy pracující s daným zámkem. Ty jsou však v současné době (alespoň co se týče zámků) blokovány. Blokované procesy jsou v řádcích označených na začátku šipkou („->“). Poté následuje několik příznakových slov. Ty udávají typ a parametry zámku. Následuje číslo procesu, který vlastní daný zámek, a potom popis zařízení, kde se zámek nachází. Umístění je dáno hlavním a vedlejším číslem zařízení (oddělena dvojtečkou) a číslem i-uzlu (inode) souboru, který je uzamčen. Následuje rozsah zámku. Zde je to ve všech případech celý soubor – začátek je 0 a konec je EOF. Posledními informacemi jsou adresy navazujících zámků, které ale bez další pomoci asi nemají valný význam. První je adresa struktury, která se na daném řádku vypisuje, pokračuje to odkazem na předchozí a následující zámek v systému. Předposlední číslo je další zámek pro tento i-uzel a nakonec je zde odkaz na další položku v seznamu zámků blokovaných procesů. Bylo to trochu rychle, ale dál si vše postupně rozepíšeme podrobněji spolu s popisem implementace a chování.

Podrobnější rozbor začněme z pohledu programátora. Bohužel zde není situace úplně jednoduchá, vlivem historického vývoje a soupeření vzniklo několik rozhraní a způsobů implementace zámků. Dvěmi základními větvemi Unixů, které spolu soupeří od „počátku světa“, jsou originální AT&T Unixy a jejich klony (označované jako System V.) = SVR4 větev a verze z Berkeley v Kalifornii = BSD větev. Časem přibyla ještě norma POSIX, a hned máme tři odlišné přístupy.

Nejprve pár slov o zámcích všeobecně pro ty, kteří nemají moc dobrou představu, o co vůbec jde. Používají se především dva druhy zámků. Prvním je zámek sdílený (shared), nazývaný též čtecí (read). Ten je možno aplikovat na oblast, která není uzamčena vůbec nebo je uzamčena právě sdíleným zámkem. Slouží k deklaraci faktu, že proces bude dané místo souboru číst (odtud druhý název) a nepřeje si, aby se mu v ní někdo vrtal. Druhým typem je zámek exkluzivní (exclusive) neboli zápisový (write). Ten umožňuje zamknout pouze oblast, na kterou nebyl aplikován žádný (ani sdílený) zámek. Slouží k získání jedinečného přístupu k části souboru, kterou je pak možno beztrestně modifikovat. Pokud proces požaduje uzamčení nějaké oblasti, které je v dané době neproveditelné (zámek write na oblast zamčenou read), zůstane zablokován do doby, než je možno požadavek na uzamčení uspokojit. Variantou je neblokující volání, kdy je funkce v této situaci ukončena chybou (EWOULDBLOCK). Proces potom může odložit uzamčení oblasti na později a dělat zatím něco jiného.

BSD Unixy obsahují podporu zamykání souborů od verze 4.2 (dle man flock), jejich sémantika je však dost odlišná od ostatních. Hlavní rozdíl je v tom, že se zamykají celé soubory najednou. Další věcí je, že tyto Unixy ve svém základu nepodporovaly zámky povinné, ale pouze doporučující. Tady si nejsem úplně jist českou terminologií, budu proto používat tyto výrazy: mandatory pro povinné zámky a advisory pro doporučující (nepovinné).

Pro neznalé vysvětlím rozdíl. Klasické souborové zámky, jak byly vymyšleny (v obou vývojových větvích), jsou doporučující – advisory. To znamená, že pokud uzamkneme soubor tímto zámkem, není pro jiný proces vynucen zákaz přístupu. Místo toho má tento proces možnost zjistit si (explicitně), zda je jím požadovaná část souboru volná (tím, že také použije odpovídající zámek). Pokud ale natvrdo, bez dalších testů něco zapíše na zamčenou pozici v souboru, nikdo mu v tom nezabrání. Použití advisory zámků se potom velmi blíží jiným (explicitním) synchronizačním nástrojům, jako jsou například semafory. Po čase se zjistilo, že výše popsané chování není vyhovující a že když si proces soubor zamkne, měl by mít právo očekávat, že bude mít exkluzivní přístup. Proto lidé ze skupiny kolem systému System V (dle Documentation/man­datory.txt) přišli s povinným zamykáním – mandatory locking. Pokud je implementován a použit povinný zámek, musí operační systém zajistit exkluzivitu přístupu i tehdy, pokud některá ze soupeřících stran nepoužije explicitní zamykání. Při rozhodování, jak navrhnout systém povinných zámků, se vycházelo z toho, že by se nemělo měnit existující rozhraní. Řešením je použití úplně stejných funkcí s tím, že požadavek na povinný zámek se vyjádří nastavením práv příslušného souboru (mimo tyto funkce) na takovou hodnotu, kdy bit X (provedení) pro skupiny není nastaven (0), zatímco SGID bit nastaven je (1). To je kombinace, která jinak valný smysl nedává.

BSD verze rozhraní je implementována funkcí a poté systémovým voláním flock(). Ta zamyká celý soubor na úrovni tabulek operačního systému (inode). Není tedy uzamčen deskriptor souboru a zámek se duplikuje pro volání jako dup(), respektive při duplikaci ukazují oba deskriptory na jeden soubor, a tudíž také na ten samý zámek. Možné operace se zámky v této implementaci jsou LOCK_SH pro získání sdíleného zámku, LOCK_EX pro exkluzivní zámek a LOCK_UN pro zrušení zámku. Je možno (LOCK_NB) povolit neblokující režim operací. Systémové volání flock() nepodporuje zamykání souborů přes NFS (a pravděpodobně ani jiné síťové souborové systémy), alespoň dle informací z manuálové stránky. Dokument Documentation/man­datory.txt tvrdí, že BSD verze nepodporuje povinné zamykání, nicméně dle zdrojového textu v fs/locks.c je jako další parametr možno použít i LOCK_MAND pro povinné zamykání, a to v kombinaci s LOCK_READ pro čtecí nebo LOCK_WRITE pro zápisové zámky. To vypadá jako dobrá zpráva. Ta špatná je, že při testování ve funkcích jako open, read nebo write se používá funkce (locks_verify_a­rea()), která pracuje jen se zámky typu POSIX. Ve skutečnosti jsou tedy (zatím?) povinné zámky pro BSD rozhraní ignorovány.

Druhým typem rozhraní je to ve stylu SVR4. Jedná se o funkci fcntl() s argumenty F_GETLK, F_SETLK a F_SETLKW. První příkaz slouží ke zjištění aktuálního stavu požadované oblasti, druhý nastaví požadovaný zámek a třetí je variantou nastavení zámku s blokováním. Informace o zámku jsou předávány ve struktuře typu struct flock, která obsahuje položky pro typ zámku (F_RDLCK, F_WRLCK, F_UNCLK), určení oblasti v souboru, s kterou se bude manipulovat (počátek a délka). Při zjišťování informací je také vrácena hodnota PID procesu, který právě vlastní zámek. Rozhraní normy POSIX je pouze zapouzdřením a vnitřně volá funkci fcntl() s potřebnými parametry. Jedná se o funkci lockf() s parametry F_LOCK, F_UNLOCK, F_TEST, F_TLOCK. Toto rozhraní pracuje pouze s explicitními (zápisovými) zámky. Velkou výhodou použití fcntl() je možnost práce s částí souboru. To ve spoustě případů velmi zvyšuje výkon konečné aplikace (jako třeba logovací démon). Zajímavá je také chyba, vrácená tehdy, nepodaří-li se alokovat další zámek – ENOLCK („no luck!“:). Na rozdíl od BSD rozhraní (kde to nedává smysl) se zde provádí slučování a modifikace zámků opět pro zvýšení efektivity.

Poté, co jsme si vysvětlili, jak zámky fungují ze strany programátora, se opět vraťme k našemu výpisu. Typ zámku (druhý sloupec) tedy může být POSIX pro zámky vyvolané přes fcntl(), FLOCK pro zámky přes stejnojmennou funkci a případě ACCESS pro zámky blokovaných procesů při užití povinného zamykání. V případě POSIXu může být typ MANDATORY nebo ADVISORY, u BSD rozhraní je prozatím jen druhá možnost. Poslední vlastností je typ. Zde jsou možnosti READ, WRITE, RW a NONE. Snadno přijdete na to, které mají smysluplný význam. K adresám na konci každého řádku zbývá jen dodat, že existují tři seznamy zámků: Seznam všech zámků v systému (bez ohledu na typ), dále seznam všech zámků pro daný i-uzel a nakonec seznam všech procesů, které se na daném zámku mohou blokovat.

Tentokrát jsem se nedostal tolik do způsobu implementace v aktuálním jádře, ale o mnoho jste nepřišli, není na tom nic převratného. Zájemce odkazuji na soubor fs/locks.c a příslušné hlavičkové soubory. Tohle téma je dle mého názoru z důvodu několika rozhraní trochu složitější, a tak prosím nad případnými nepřesnostmi přivřete jedno (až dvě – dle potřeby) oko a pokud objevíte (zcela náhodou :) větší chybu, dejte mi vědět.

Autor článku