Faire un don ! | | style | statistiques | contactez-nous | plan | lettre d'information

Vous avez demandé le commentaire #909350.

Retourner sur le contenu associé.

Re: TSO et LRO

Posté par Brice Goglin () le 01/03/2008 à 00:15. (lien). Évalué à 10.

Dans Linux, y a du LRO spécifique a certains drivers (neterion et netxen iirc) et du LRO générique depuis peu. A terme tout le monde devrait utiliser le générique...

Pour bien suivre la suite, je conseille d'ouvrir net/ipv4/inet_lro.c pour voir le code du LRO générique auquel je vais faire reference ci-dessous ainsi que driver/net/ehea/ehea_main.c pour son utilisation dans le driver eHEA.


Lorsqu'un paquet arrive, les drivers normaux appelent netif_receive_skb() (ou netif_rx() selon si NAPI est actif ou pas) qui passe le paquet aux couches supérieures (genre IP et TCP). Si tu supportes LRO, tu appelles lro_receive_skb() a la place.

La-dedans, le vrai travail est fait dans __lro_proc_skb(). Quand cette fonction n'arrive pas a LROer, elle retourne une erreur et lro_receive_skb() se rabat sur les netif_receive_skb() ou netif_rx() habituels.

Le détail de __lro_proc_skb() maintenant. D'abord, elle utilise un callback get_skb_header() du driver pour recuperer des infos sur le paquet. Comme chaque carte stocke les headers et donnees de sa propre maniere, le callback est defini par le driver a l'initialisation et le LRO l'appelle quand il a besoin de trucs specifiques, l'endroit ou est stocké le header dans le paquet actuel en l'occurence.

Ensuite, __lro_proc_skb() verifie que c'est du IPv4 et TCP puis on essaie de recuperer un "descripteur" de LRO (l'equivalent des sessions de neterion) par lro_get_desc. On l'initialise si nécessaire, on vérifie que le nouveau paquet va bien juste après ce qui a deja ete recu dans ce descripteur, puis l'ajoute avec lro_add_packet()

lro_get_desc(), c'est juste un tableau de connexions en cours de LRO. Chaque driver en supporte un certain nombre (genre 8), limite plus ou moins arbitraire. Chaque arrivee de plein de paquets d'une meme connexion cree une session, et on la flushe un peu plus tard en passant l'agregation de tous les paquets d'un coup aux couches superieures.

Comme en general, meme si on a plein de connexions ouvertes, on a pas plein de paquet entrelacés de toutes les connexions a la fois, 8 sessions suffisent a peu pres pour couvrir les connexions qui bourrinent vraiment. A noter que si une connexion alterne entre activité et calme, elle acquiera/relachera des sessions LRO au fur et a mesure. On ne peut pas garder une session indefiniment, elle sera flushee de force de temps en temps pour la preter aux autres connexions. Bref ca marche :)

__lro_proc_skb() finit par l'ajout de paquet avec lro_add_packet(). C'est des trucs chiants de manipulation des buffers contenus dans le skb (on les chaine a la queueleuleu), et d'adaptation des longueurs, checksums et seqnums, pas grand chose d'intéressant.

La question finale, c'est "quand est-ce qu'on arrete d'agreger?". Deja, il y a un nombre max de paquets defini par le driver (lro_mgr->max_aggr, 64 souvent). De plus Linux n'aime pas les paquets de plus de 64ko, donc on arrete d'agreger avant.

Concretement, arreter d'agreger, c'est fermer une session et la passer aux couches superieures. On utilise lro_flush() pour faire ca. Elle finit d'ajuster les headers TCP/IP et passe le gros paquet agregé a netif_receive_skb() (ou netif_rx() selon si NAPI est actif). Apres ca, le descripteur/session LRO est relaché, donc une autre connexion (ou la meme) pourra l'utiliser.

De plus, on agrege les paquets qui se suivent dans une mem connexion. Mais si ca arrive pas dans l'ordre, on arrete d'agreger. Ca evite d'avoir a gerer des trous. Et aussi ca evite par exemple de passer des paquets tres tres dans le desordre aux couches superieures. Par exemple un paquet recent serait deja remonté pendant que des paquets vieux continueraient a se faire agreger au niveau du driver au lieu de remonter eux aussi.

Et enfin, on arrete d'agreger a intervalle vaguement regulier (quand NAPI fait un coup de polling du driver) pour éviter que des paquets soient ralentis trop longtemps dans le driver pour rien. On appelle lro_flush_all() qui fait lro_flush() sur tous les descripteurs/sessions en cours.


Vous pouvez aussi regarder drivers/net/myri10ge/myri10ge.c qui utilise le LRO generique avec des fragments (pages physiques discontigues) par opposition a eHEA qui utilise des skb lineaires (socket buffer contigus en memoire virtuelle). C'est juste une differente gestion de la memoire et de la facon dont la carte depose les paquets en reception. Le LRO fournit des fonctions *_skb() et *_frags() pour gerer les 2 modeles.

[ Répondre ]