Linux E X P R E S

Facebook

Vývoj jádra XV. - síťová zařízení

Ovladače síťových zařízení jsou svým způsobem specifické. Kromě jiného se liší i tím, že k nim uživatelské programy nepřistupují přímo, nýbrž přes síťovou vrstvu jádra. Tento díl seriálu se zabývá odlišnostmi síťových zařízení od ostatních kategorií a postupem při tvorbě ovladače.


Postavení síťových zařízení

Zatímco znaková a bloková zařízení mají své soubory, přes které s nimi pracujeme, u síťových nic takového není. Je to logické – málokdy totiž potřebujeme posílat nebo přijímat data „tak, jak jsou“. Typicky musíme přinejmenším přidat/zkontrolovat potřebné hlavičky, většinou ale provést ještě řadu dalších operací, než se data dostanou z programu na síťové médium nebo naopak.

Pro ovladače síťových zařízení určité třídy (např. Ethernet, FDDI, Token Ring) existují v jádře funkce, které výrazně zjednodušují psaní ovladačů a snižují objem potřebného kódu. Například pro alokaci se používají funkce alloc_etherdev() nebo alloc_fddidev(), připravující datovou strukturu přímo pro zařízení dané třídy.

Proto uživatelský program pracuje s objektem zvaným socket. Socket má řadu vlastních systémových volání, kromě toho ale podporuje i „obyčejné“ čtení a zápis, což je velká výhoda. Upravit program zapisující na disk, aby místo toho posílal data po síti, je velmi snadné.

Mezi socketem a ovladačem síťového zařízení (rozhraní) je dlouhá cesta – data postupují přes vrstvy jednotlivých protokolů (např. nejdřív třeba TCP, potom IP a nakonec Ethernet) a jsou podrobena filtraci a směrování. Pak to vypadá tak, že nad různým počtem síťových zařízení existuje různý počet socketů, bez vzájemné pevné vazby.

Další specialitou je silně asynchronní chování. U jiných druhů zařízení není sice nemožné, ale nebývá obvyklé. U zařízení síťových je naopak charakteristické, protože data mohou po médiu přitéct obecně kdykoliv.

Výrazným usnadněním při vývoji síťových ovladačů je využití MII (Media Independent Interface). Jedná se o standardizované rozhraní pro zařízení typu Ethernet, pro které je v jádře Linuxu k dispozici ovladač. Značnou část práce lze proto přenechat tomuto univerzálnímu ovladači a explicitně pak už řešit pouze to, co je u daného zařízení specifické.

Chování síťového ovladače

Síťový ovladač má s ostatními ovladači společné mimo jiné dvě základní operace – otevření a uvolnění (zde funkce open() a stop()). I zde tyto funkce alokují, resp. uvolňují potřebné prostředky (IRQ, DMA apod.). Rozdíl je však v tom, že se tyto operace nevolají z programu, který zařízení využívá, nýbrž z programu, který zařízení zapíná a vypíná (tedy např. ifconfig nebo ip). Používají se k tomu volání ioctl(SIOCSIFFLAGS).

Nejdůležitější činností ovladače je odesílání a příjem dat. Data putují po síti v paketech, zajímá nás tedy nakládání s jednotlivými pakety. Odesílání probíhá poměrně přímočaře – jádro prostě zavolá příslušnou funkci (hard_start_xmit()). Ovladač zjistí, zda lze paket odeslat (v tom případě udělá vše potřebné) a oznámí výsledek. Ještě před samotným odesláním se musí vytvořit hardwarová (např. ethernetová) hlavička – hard_header().

Pokud není paket odeslán v časovém limitu, časovač v síťové vrstvě jádra způsobí zavolání funkce tx_timeout(). Veškerou režii ohledně nakládání s úspěšně převzatým paketem nese na bedrech ovladač. Spočívá v předání dat do zařízení (např. nakopírováním do paměti zařízení nebo přípravou pro DMA), odstartování přenosu a reakci na dokončení (zařízení kupř. vyvolá přerušení) – ovladač pak musí zrušit buffer, z něhož se data odesílala. Režijní činnosti se s výhodou vykonávají v pracovní frontě.

Opačnou činností je příjem paketu. Ten je – jak již bylo řečeno – asynchronní. Data prostě přijdou a musí se s nimi nějak naložit. Obvykle to vypadá tak, že zařízení vyvolá přerušení, ovladač alokuje buffer pro data, ta se do něj nějak přenesou (např. explicitním kopírováním nebo přes DMA) a buffer se předá síťové vrstvě jádra k dalšímu zpracování.

Pro rychlá síťová rozhraní se používá ještě jedna metoda příjmu paketů, a to dotazování funkcí poll(). Pokud by se totiž generovalo přerušení pro každý paket, znamenalo by to zbytečnou režii na obsluhu těchto přerušení. Tato metoda přerušení nevyužívá (musí být možnost je na zařízení vypnout, avšak nechat je samozřejmě zapnutá pro jiné účely, např. úspěšné odeslání paketu). Síťová vrstva jádra dotazuje ovladač vždy, když není nic důležitého na práci. Zařízení musí být schopno uložit větší počet přijatých paketů, ať už přímo ve své vlastní paměti či (přes DMA) v paměti počítače.

Píšeme ovladač

Kdo už psal ovladače pro jiné kategorie zařízení, nesetká se tu s ničím vysloveně novým. Opět se registruje a odregistrovává zařízení, opět je tu datová struktura zařízení a struktura síťových („souborových“) operací, byť se některé věci provádějí odlišně. Následující příklad ukazuje, jak by vypadal jednoduchý ovladač zařízení „místní smyčky“. Z odeslaného paketu se okamžitě stává přijatý. Pro úsporu místa příklad neobsahuje includy hlavičkových souborů ani obvyklé modulové deklarace.

static struct net_device* s_mynetdev = NULL;

static int mynet_open(struct net_device *dev)
{
  memcpy(dev->dev_addr, "\0mynet", ETH_ALEN);
  netif_start_queue(dev);
  return 0;
}

static int mynet_stop(struct net_device *dev)
{
  netif_stop_queue(dev);
  return 0;
}

static int mynet_rx(struct net_device* dev, void* data,
    size_t len)
{
  struct sk_buff* skb = dev_alloc_skb(len);
  if (skb == NULL)
    return -ENOMEM;

  skb->dev = dev;
  skb->protocol = eth_type_trans(skb, dev);
  skb->ip_summed = CHECKSUM_UNNECESSARY;
  memcpy(skb_put(skb, len), data, len);
  netif_rx(skb);

  return 0;
}

static int mynet_tx(struct sk_buff *skb,
    struct net_device *dev)
{
  int res = 0;

  dev->trans_start = jiffies;
  res = mynet_rx(dev, skb->data, skb->len);
  if (res != 0)
    return res;

  dev_kfree_skb(skb);
  return 0;
}

static void mynet_setup(struct net_device* dev)
{
  ether_setup(dev);
  dev->open            = mynet_open;
  dev->stop            = mynet_stop;
  dev->set_config      = NULL;
  dev->hard_start_xmit = mynet_tx;
  dev->do_ioctl        = NULL;
  dev->get_stats       = NULL;
  dev->rebuild_header  = NULL;
  dev->hard_header     = NULL;
  dev->tx_timeout      = NULL;
  dev->watchdog_timeo  = 0;
  dev->flags            |= IFF_NOARP;
  dev->features         |= NETIF_F_NO_CSUM;
  dev->hard_header_cache = NULL;
}
static int __init mymodule_init(void)
{
  int res = 0;
  s_mynetdev = alloc_netdev(0, "mynet", mynet_setup);
  if (s_mynetdev == NULL) {
    res = -ENOMEM;
    goto alloc_failed;
  }

  res = register_netdev(s_mynetdev);
  if (res != 0)
    goto reg_failed;

  return 0;

reg_failed:

  free_netdev(s_mynetdev);

alloc_failed:

  return res;
}
    
static void __exit mymodule_cleanup(void)
{
  unregister_netdev(s_mynetdev);
  free_netdev(s_mynetdev);
}

Povšimněte si prosím, jak vypadají jednotlivé části. Při inicializaci modulu se nejprve alokuje příslušná datová struktura pro instanci zařízení (bude jen jediná – pro fyzická zařízení jich ale obvykle bývá více). Při alokaci se ze síťové vrstvy zavolá inicializační funkce (viz dále). Byla-li alokace úspěšná, zařízení se zaregistruje (od této chvíle je plně k dispozici v systému). Obvyklou součástí datové struktury bývají soukromá data, zde však nejsou využita, proto se při alokaci jejich velikost stanovuje na nulu.

Inicializační funkce má za úkol připravit datovou strukturu a nastavit ukazatele na příslušné funkce. Při přípravě si pomůžeme ethernetovskou funkcí ether_setup(), ukazatele se musí nastavit ručně. Řada funkcí zde pro jednoduchost není implementována. Všimněte si také, že se nebude používat ARP (IFF_NOARP) ani kontrolní součet (NETIF_F_NO_CSUM).

Otvírací funkce (mynet_open()) obsahuje pouze nastavení hardwarové adresy (použijeme znaky z názvu) a aktivaci fronty na pakety. Uzavírací funkce pouze deaktivuje paketovou frontu.

Nejzajímavější je odesílání a příjem paketu (zde jsou funkce synchronní a propojené). Funkce pro odesílání v podstatě jen zavolá funkci pro příjem a předá jí informace o paketu (adresu v paměti a délku) – pokud je přenos úspěšný, uvolní buffer paketu. Funkce pro příjem naopak buffer alokuje, nastaví informativní údaje a překopíruje data. Následně předá buffer síťové vrstvě ke zpracování.

Uvedený ovladač je extrémně jednoduchý, neobsahuje spoustu věcí, které v ovladači obvykle bývají – např. statistiku přenosů a práci s hlavičkami. Zde to ale není potřeba. Že ovladač funguje, se lze přesvědčit snadno. Po načtení do paměti (insmod nebo modprobe) se obvyklým způsobem (přes ip nebo ifconfig) nastartuje zařízení mynet a přiřadí se mu adresa (např. 192.168.1.100). Pak stačí spustit dvě instance programu netcat (nc), jednu v režimu serveru a druhou jako klient. Data posílaná z jedné instance se objevují v druhé a naopak. Informace o ovladači jsou k dispozici přes sysfs (zde na /sys/class/net/mynet).

Práce s PCI

Zatím ve všech ovladačích, kterých se týkaly dosavadní díly seriálu, byla tiše ignorována problematika sběrnic, ke kterým jsou zařízení připojena. Základ ovladačů opravdu nebývá spjat s konkrétními cestami, jimiž se se zařízením komunikuje. Ale pro úspěšné zprovoznění zařízení připojeného na sběrnici je potřeba znát i věci, které právě s danou sběrnicí souvisejí.

Příští díl se proto bude věnovat jedné ze dvou dnes nejpoužívanějších sběrnic v počítačích: PCI. Podíváme se na to, jaká pravidla pro ovladače zařízení na PCI platí a jak co nejlépe využít mechanismů, které nám PCI subsystém poskytuje.

Diskuze (0) Nahoru