Com vaig esmentar a la meua entrada sobre les operacions ioctl, LwIP imposa un límit màxim de sockets, el que suposa un problema enorme per a un sistema com el Hurd. Ara que ja he acabat totes les tasques que tenia a la meua proposta inicial, m'ha semblat que era bon moment per a estudiar aquest tema tranquil·lament i tractar de trobar-li una solució.
El problema és el següent: a LwIP, els sockets es desen en una matriu de grandària fixa, la qual cosa és la millor solució per a un sistema encastat perquè permet indexar els sockets naturalment sense haver d'emprar variables addicionals i permet accedir a qualsevol socket directament a partir del seu índex en el mateix temps. Si eliminem el nombre fixe de sockets no podem seguir emprant una matriu de grandària fixa i llavors ens toca trobar altres fórmules per gestionar-ho tot plegat.
En el translator d'LwIP he optat per substituir la matriu per una llista enllaçada i això té unes implicacions: haver de recórrer la llista quan es vol crear, trobar o destruir un socket concret, garantir que els sockets ocupen a la llista la posició adequada per al seu índex i haver de crear noves variables per a mantindre tot aquest ordre. Tot això es pot fer si estem disposats a acceptar la pèrdua d'eficiència implícita, però hi ha un problema que no té tan fàcil solució: el límit que imposa la macro FD_SETSIZE
. Ho explicaré amb detall.
Al Hurd, cada procés té el seu propi comptador de sockets, mentre que LwIP només en té un de global. Això implica que el comptador global d'LwIP en realitat és la suma de tots els sockets servits a tots els processos, i és molt fàcil que aquesta suma arribe a ser superior a FD_SETSIZE
. Quan això ocorre, LwIP no podrá ficar el següent socket a l'interior d'una estructura fd_set i per tant ja no podrà cridar a lwip_select()
amb aquest socket. Com que la Glibc crida l'operació io_select()
cada cop que l'usuari crida a send()
o recv()
, a efectes pràctics aquest socket serà senzillament inutilitzable. Per tant, només els processos que han pogut accedir a un dels primers FD_SETSIZE
sockets reben un socket útil, tots els altres rebran una quantitat potencialment infinita de sockets inútils.
Després de donar-li voltes al tema he acabat trobant una solució de què no n'estic massa content, però és l'única que se m'ha ocorregut i, com que eliminar la restricció en el nombre de sockets és prioritat total, al final l'he implementada. La solució és la següent: si ens fixem en les macros FD_SET
, FD_CLR
i FD_ISSET
tal i com estan definides a Glibc, trobem que no protegeixen l'fd_set contra desbordament (això es posa interessant), de manera que les podem emprar per escriure més enllà del bit FD_SETSIZE
de l'fd_set. Com que en el servidor d'LwIP totes les crides a lwip_select()
estan centralitzades en l'operació io_select()
, podem reservar tanta memòria com necessitem en aquesta operació i tractar els punters a aquests espais de memòria com a punters a fd_set, així lwip_select()
pot treballar amb normalitat. Lamentablement m'ha tocat fer canvis també a lwip_select()
perquè l'operació FD_ZERO
no escriu més enllà d'FD_SETSIZE
i perquè aquesta funció crea fd_sets interns que també han d'estar preparats per a ser escrits amb seguretat. Al final la cosa m'ha quedat així. Dit això, si algú té una idea millor estaré encantat d'escoltar-la.
Amb tot, tenim el tema solucionat al Hurd, però la intenció inicial era que els canvis que he hagut de fer en LwIP per poder eliminar la restricció en el nombre de sockets pogueren ser inclosos en la propera versió de la pila. Lamentablement, no està clara la solució a l'assumpte d'FD_SETSIZE
en LwIP. Aquí vos deixe l'enllaç a la discussió en la seua llista de correu per si en voleu saber més.