Produkty Novinky Články Návody Kontakty

Operace open() a release()

Před samotným výkladem o souborových operacích open() a release() si něco povíme o struktuře file, jejíž instance, resp. ukazatel na ni je prvním parametrem každé souborové operace, která přistupuje k ovladači.

Struktury file

Jak již bylo řečeno, ukazatel na instanci struktury file je první parametr každé souborové operace. Instance této struktury reprezentuje v prostoru jádra otevřený soubor (podobně jako souborový deskriptor v uživatelském prostoru). V případě modulu pak otevřený soubor zařízení. Jádro vytváří vždy novou instanci struktury file při každém systémovém volání open(). Tato instance je platná až do uzavření souboru, kdy jádro zajistí její zrušení.
Poznámka: Tato struktura nemá žádnou souvislost se strukturou FILE definovanou knihovnou libc v uživatelském prostoru.
Struktura file má řadu členů, my si uvedeme jen ty nejdůležitější:
  • unsigned int flags - příznaky souboru zařízení, jako O_NONBLOCK apod., které má otevřený soubor zařízení nastavené.
  • mode_t f_mode - definuje práva čtení/zápisu případně obojí. Tento člen má smysl kontrolovat pouze u operací ioctl() nebo open(), kde můžeme potřebovat ověřit, že soubor byl otevřen ve správném módu přístupu. U operací read() a write() tuto kontrolu provádí samo jádra.
  • void * private_data - tento ukazatel je před voláním operace modulu open() nastaven jádrem na NULL. Používá se pro uchování ukazatele na jakákoliv data, která modul používá vždy jen v souvislosti s uživatelským programem, který si soubor zařízení otevřel. Jde například o privátní datový buffer apod. Tato data obvykle alokujeme ve operaci open(). Je třeba pamatovat na to, že je pak musíme uvolnit v operaci release().

Struktura inode

Vedle ukazatele na instanci struktury file, je jádrem předáván do operací open() a release() také ukazatel na instanci struktury inode. Jestliže struktura file existuje v paměti tolikrát, kolikrát byl soubor zařízení otevřen, tak struktura inode má vždy jen jednu instanci, protože v prostoru jádra reprezentuje soubor zařízení, nikoliv souborový deskriptor otevřeného souboru zařízení.
Proč je struktura inode pro nás důležitá? Protože obsahuje jeden zajímavý člen:
  • dev_t i_rdev - zde je uložen major-minor pár souboru zařízení. S ohledem na přenositelnost mezi verzemi jádra je doporučeno získávat hlavní a vedlejší číslo ze struktury inode pomocí těchto maker:
    unsigned int imajor(struct inode* inode);
    unsigned int iminor(struct inode* inode);
    

Operace open()

Operace open() je jádrem volána vždy při otevření souboru zařízení. Její prototyp je:
int (* open)(struct inode * inode, struct file * filp);
a provádíme v ní obvykle tyto činnosti:
  • Inicializaci ovládaného zařízení (pokud soubor zařízení nebyl dosud otevřen)
  • Kontrola stavu zařízení (jestli nenastala chyba, zda zařízení stále funguje, atd.)
  • Alokování privátních dat; ukazatel na data pak přiřadíme do členu filp->private_data struktury file.
Na výpisu 17 je kód operace open(), kterou implementuje náš modul dskel. Stručně si jej okomentujeme. Řádky 5 - 13 zajišťují, že soubor zařízení reprezentující náš modul dskel půjde otevřít jen z jednoho programu současně. První program tedy otevře soubor zařízení úspěšně, zatímco druhý obdrží chybu EBUSY. Jakmile první program soubor zařízení uzavře, může si jej druhý otevřít pro sebe. Soubory zařízení lze obvykle otevírat naráz více programy, ale pak se implementace modulu komplikuje, protože je nutné řešit sdílení zařízení více programy. Proto, pro jednoduchost umožníme práci se zařízením jen jednomu programu. Jak kód na řádcích 5 - 13 funguje? Je postaven na atomické proměnné dskel.open_cnt - počítadle počtu otevření modulu. Na začátku je proměnná dskel.open_cnt nastavena na 0. Při volání operace open() dojde k jejímu atomickému zvýšení o jedničku a zároveň je vrácena její nová hodnota, kterou si uložíme do pomocné proměnné. To vše se děje nepřerušitelně, atomicky, tzv. máme jistotu, že i při současném volání operace open() více programy naráz bude nová hodnota proměnné dskel.open_cnt správná. Jestliže je hodnota proměnné dskel.open_cnt vyšší než jedna (druhé a další otevření) je atomicky snížena (aby nerostla nekonečně) a je vrácena chyba EBUSY.
1static int dskel_open(struct inode * inode_ptr, struct file * file_ptr)
2{
3      int result = 0, tmp_open_cnt = 0;
4
                                                5      // soubor zarizeni muze byt otevren jen jednim programem soucasne
6      // atomicky inkrementne promenou o 1 a vrati PUVODNI hodnotu
7      tmp_open_cnt = atomic_inc_and_test(&dskel.open_cnt);
8      if ( tmp_open_cnt > 1 )
9      {
10             atomic_dec(&dskel.open_cnt);
11             result = -EBUSY;
12             goto End;
13     }
1415     // modul nepodporuje systemove volani lseek()
16     result = nonseekable_open(inode_ptr, file_ptr);
17     if ( result < 0 )
18     {
19             goto End;
20     }
2122End:
23     pr_alert("dskel_open(): %d\n", result);
2425     return result;
26}
Výpis zdrojového kódu ovladače - funkce open()
Řádky 14 - 19 deklarují jádru, že náš modul nepodporuje posun pozice v souboru zařízení. Tato dodatečná deklarace v operaci open() je nutná, byť jsme v proměnné typu struktura file_operations v hlavičce zdrojového souboru přiřadili operaci llseek() speciální funkci no_llseek(). Jádro zajistí, že volání systémového volání lseek() vrátí uživatelské aplikaci chybu.
Poslední zajímavostí je řádek 21. Funkce printk() funguje podobně jako funkce printf() v uživatelském prostoru. Zajistí zobrazení zadaného textu na systémové konzoliInformationSystémová konzole není virtuální terminál představovaný programem xterm v X windows, ale konzole, na kterou se zobrazují např. zprávy jádra při nabíhání systému. Ve většině distribucí je dostupná přes kombinaci kláves Ctrl+Alt+F1. a v souboru /var/log/syslog. Více detailů o jejím použití se dozvíme později.
Implementace operace open() si žádá také změny v hlavičce zdrojového souboru našeho modulu. Musíme jednak přidat člen open_cnt do struktury dskel_t včetně jejího nastavení na výchozí hodnotu v instanci dskel:
struct dskel_t
{
        /* ######## predchozi clenove struktury ######## */
        // hlida pocet volani operace open()
        atomic_t open_cnt;
};
​
static struct dskel_t dskel = 
{
        /* ######## predchozi clenove struktury ######## */
        .open_cnt = ATOMIC_INIT(0),     
}
A pak kvůli funkci printk() přidat hlavičkový soubor <linux/kernel.h> s její deklarací:
#include <linux/kernel.h>
Nesmíme rovněž zapomenout v proměnné dskel_file_ops typu struktura file_operations nastavit funkční ukazatel pro operaci open() na funkci dskel_open(). Krom toho je nutné do hlavičky umístit i dopředné deklarace funkcí dskel_open() a později i dskel_release() - podobně jako u úklidové funkce cleanup().

Operace release()

Operace release() je logicky opakem metody open(). Její prototyp je:
int (* release)(struct inode * inode, struct file * filp);
a provádíme v ní tyto činnosti:
  • Uvolnění privátních dat, přístupných pod ukazatelem filp->private_data ve struktuře file.
  • Ukončení zařízení, uvedení zařízení do nějakého výchozí stavu (pokud je to poslední volání operace release()).
Jádro zajišťuje, že počet volání operace open() odpovídá počtu volání operace release(). Nemůže se tedy stát, že operace release() je volána vícekrát než operace open().
Pokud operace open() a release() modul neimplementuje, tak jádro zajistí, že se jejich volání vždy podaří.
Výpis 18 ukazuje implementaci operace release() v našem modulu dskel. Tentokrát je kód velmi jednoduchý - prostě jen atomicky snížíme hodnotu proměnné dskel.open_cnt (počítadla otevření) a zobrazíme hlášku, která indikuje volání operace release().
1static int dskel_release(struct inode * inode_ptr, struct file * file_ptr)
2{
3      // povolime nove otevreni
4      atomic_dec(&dskel.open_cnt);
5
                                                6      pr_alert("dskel_release(): done\n");
7
                                                8      return 0;
9}
Výpis zdrojového kódu ovladače - funkce release()
Nezapomeneme přidat ukazatel na funkci dskel_release() do proměnné dskel_file_ops typu struktura file_operations v hlavičce zdrojového souboru modulu.