Vraag Zijn er platforms waarbij het gebruik van structuurkopie op een fd_set (voor select () of pselect ()) problemen oplevert?


De select() en pselect() systeemaanroepen wijzigen hun argumenten (de 'fd_set *'argumenten), dus de invoerwaarde vertelt het systeem welke bestandsdescriptoren moeten worden gecontroleerd en de retourwaarden vertellen de programmeur welke bestandsdescriptors momenteel bruikbaar zijn.

Als u ze herhaaldelijk wilt bellen voor dezelfde set bestandsdescriptoren, moet u ervoor zorgen dat u voor elk gesprek een nieuw exemplaar van de descriptors hebt. De meest voor de hand liggende manier om dat te doen is door een structuurkopie te gebruiken:

fd_set ref_set_rd;
fd_set ref_set_wr;
fd_set ref_set_er;
...
...code to set the reference fd_set_xx values...
...
while (!done)
{
    fd_set act_set_rd = ref_set_rd;
    fd_set act_set_wr = ref_set_wr;
    fd_set act_set_er = ref_set_er;
    int bits_set = select(max_fd, &act_set_rd, &act_set_wr,
                          &act_set_er, &timeout);
    if (bits_set > 0)
    {
        ...process the output values of act_set_xx...
    }
 }

(Bewerkt om onjuist te verwijderen struct fd_set referenties - zoals aangegeven door 'R ..'.)

Mijn vraag:

  • Zijn er platforms waar het niet veilig is om een ​​structuurkopie te maken van de fd_set waarden zoals getoond?

Ik maak me zorgen dat er verborgen geheugentoewijzing of zoiets onverwachts gebeurt. (Er zijn macro's / functies FD_SET (), FD_CLR (), FD_ZERO () en FD_ISSET () om de internals van de toepassing te maskeren.)

Ik kan zien dat MacOS X (Darwin) veilig is; andere op BSD gebaseerde systemen zijn daarom waarschijnlijk veilig. U kunt helpen door andere systemen waarvan u weet dat ze veilig zijn in uw antwoorden te documenteren.

(Ik heb wel enkele zorgen over hoe goed de fd_set zou werken met meer dan 8192 open bestandsdescriptors - het standaard maximale aantal geopende bestanden is slechts 256, maar het maximale aantal is 'onbeperkt'. Aangezien de structuren 1 kB zijn, is de kopieercode bovendien niet verschrikkelijk efficiënt, maar is het vervolgens niet noodzakelijk om efficiënt een lijst met bestandsdescriptors door te nemen om het invoermasker opnieuw aan te maken voor elke cyclus. Misschien kan je het niet doen select() wanneer je veel bestandsdescriptors hebt geopend, maar dat is het moment waarop je waarschijnlijk de functionaliteit nodig hebt.)


Er is een gerelateerde SO-vraag - vragen over 'poll () vs select ()' die een andere reeks problemen uit deze vraag aanpakt.


Merk op dat op MacOS X - en vermoedelijk BSD meer in het algemeen - er een is FD_COPY() macro of functie, met het effectieve prototype:

  • extern void FD_COPY(const restrict fd_set *from, restrict fd_set *to);.

Het kan de moeite waard zijn om te emuleren op platforms waar het nog niet beschikbaar is.


16
2018-03-11 00:13


oorsprong


antwoorden:


Sinds struct fd_set is gewoon een normale C-structuur, dat zou altijd goed moeten zijn. Persoonlijk hou ik er niet van om structuurkopieeren via de = operator, aangezien ik op veel platforms heb gewerkt die geen toegang hadden tot de normale set compiler-intrinsiek. Gebruik makend van memcpy() expliciet in plaats van de compilator-insertie is een functieaanroep een betere manier om te gaan, in mijn boek.

Van de C spec, sectie 6.5.16.1 Eenvoudige toewijzing (hier kortheidshalve bewerkt):

Een van de volgende zal houden:

...

  • de linker operand heeft een gekwalificeerde of niet-gekwalificeerde versie van een structuur of union-type compatibel met het type van het recht;

...

In eenvoudige opdracht (=), de waarde van de rechter-operand wordt geconverteerd naar het type van de toewijzingsuitdrukking en vervangt de waarde die is opgeslagen in het object dat is aangewezen door de linkeroperand.

Als de waarde die in een object wordt opgeslagen, wordt gelezen van een ander object dat op enigerlei wijze de opslag van het eerste object overlapt, moet de overlapping exact zijn en moeten de twee objecten gekwalificeerde of niet-gekwalificeerde versies van een compatibel type hebben; anders is het gedrag niet gedefinieerd.

Dus daar ga je, zolang als struct fd_set is eigenlijk een normale C struct, je bent gegarandeerd een succes. Het hangt er echter van af, of je compiler een soort code uitzendt om het te doen, of te vertrouwen op wat dan ook memcpy() intrinsiek gebruikt voor structuurtoewijzing. Als uw platform om de een of andere reden niet kan worden gekoppeld aan de intrinsieke bibliotheken van de compiler, werkt dit mogelijk niet.

Je zult wat tricks moeten spelen als je meer open bestandsbeschrijvingen hebt dan er in passen struct fd_set. De linux man pagina zegt:

Een fd_set is een buffer met een vaste grootte. uitvoeren FD_CLR() of FD_SET() met een waarde van fd dat is negatief of is gelijk aan of groter dan FD_SETSIZE zal resulteren in ongedefinieerd gedrag. Bovendien vereist POSIX fd om een ​​geldige bestandsdescriptor te zijn.

Zoals hieronder vermeld, is het misschien niet de moeite waard om te bewijzen dat uw code veilig is op alle systemen. FD_COPY() is bedoeld voor een dergelijk gebruik en is vermoedelijk altijd gegarandeerd:

FD_COPY(&fdset_orig, &fdset_copy) vervangt een reeds toegewezen &fdset_copy bestandsdescriptor ingesteld met een kopie van &fdset_orig.


9
2018-03-11 00:24



Allereerst is er geen struct fd_set. Het wordt gewoon gebeld fd_set. POSIX vereist echter wel een struct-type, dus het kopiëren is duidelijk gedefinieerd.

Ten tweede is er geen sprake van standaard C waarin de fd_set object kan dynamisch toegewezen geheugen bevatten, omdat er geen vereiste is om een ​​functie / macro te gebruiken om het te bevrijden voordat het terugkeert. Zelfs als de compiler heeft alloca (een pre-vla-uitbreiding voor stack-gebaseerde toewijzing), fd_set kan geheugen dat is toegewezen aan de stapel niet gebruiken, omdat een programma een aanwijzer kan doorgeven aan de fd_set naar een andere functie die gebruikt FD_SET, enz., en het toegewezen geheugen zou ophouden te gelden zodra het terugkeert naar de beller. Alleen als de C-compiler enige uitbreiding bood voor destructors fd_set gebruik dynamische toewijzing.

Tot slot lijkt het veilig om alleen maarmemcpy  fd_set objecten, maar voor de zekerheid zou ik iets doen als:

#ifndef FD_COPY
#define FD_COPY(dest,src) memcpy((dest),(src),sizeof *(dest))
#endif

of als alternatief gewoon:

#ifndef FD_COPY
#define FD_COPY(dest,src) (*(dest)=*(src))
#endif

Dan gebruik je de bijgeleverde systeem FD_COPY macro als deze bestaat, en pas alleen terug naar de theoretisch potentieel onveilige versie als deze ontbreekt.


7
2017-07-26 13:09



U hebt gelijk dat POSIX niet garandeert dat het kopiëren van een fd_set moet werken". Ik ben nergens persoonlijk van op de hoogte dat dit niet zo is, maar ik heb het experiment nooit gedaan.

U kunt de poll() alternatief (dat ook POSIX is). Het werkt op een vergelijkbare manier select(), behalve dat de invoer / uitvoerparameter niet dekkend is (en geen pointers bevat, dus een kale memcpy zal werken), en het ontwerp ervan maakt ook volledig de noodzaak overbodig om een ​​kopie te maken van de "gevraagde bestandsbeschrijver" -structuur (omdat de "gevraagde gebeurtenissen" en "geretourneerde gebeurtenissen" zijn opgeslagen in verschillende velden).

U hebt ook gelijk om te vermoeden dat select() (en poll()) schalen niet bijzonder goed naar grote aantallen bestandsdescriptors - dit komt omdat elke keer dat de functie terugkeert, je elke bestandsbeschrijving moet doorlopen om te testen of er activiteit op is. De oplossingen hiervoor zijn verschillende niet-standaard interfaces (bijvoorbeeld Linux's epoll(), FreeBSD's kqueue), waarnaar u mogelijk moet kijken als u merkt dat u latentieproblemen heeft.


3
2018-03-11 04:27



Ik heb niet voldoende rep om dit toe te voegen als een reactie op het antwoord van caf, maar er zijn bibliotheken die moeten worden geabstraheerd over de niet-standaard interfaces zoals epoll() en kqueue. libevent is één, en libev een ander. Ik denk dat GLIB er ook een heeft die aansluit op zijn mainloop.


1
2018-03-11 06:00



Ik heb een beetje onderzoek gedaan naar MacOS X, Linux, AIX, Solaris en HP-UX en er zijn enkele interessante resultaten. Ik heb het volgende programma gebruikt:

#if __STDC_VERSION__ >= 199901L
#define _XOPEN_SOURCE 600
#else
#define _XOPEN_SOURCE 500
#endif /* __STDC_VERSION__ */

#ifdef SET_FD_SETSIZE
#define FD_SETSIZE SET_FD_SETSIZE
#endif

#ifdef USE_SYS_TIME_H
#include <sys/time.h>
#else
#include <sys/select.h>
#endif /* USE_SYS_TIME_H */

#include <stdio.h>

int main(void)
{
    printf("FD_SETSIZE = %d; sizeof(fd_set) = %d\n", (int)FD_SETSIZE, (int)sizeof(fd_set));
    return 0;
}

Het werd op elk platform tweemaal samengesteld:

cc -o select select.c
cc -o select -DSET_FD_SETSIZE=16384

(En op één platform, HP-UX 11.11, moest ik -DUSE_SYS_TIME_H toevoegen om dingen te laten compileren.) Ik deed afzonderlijk een visuele controle op FD_COPY - alleen MacOS X leek het te bevatten, en dat moest worden geactiveerd door verzekeren dat _POSIX_C_SOURCE was niet gedefinieerd of door te definiëren _DARWIN_C_SOURCE.

AIX 5.3

  • Standaard FD_SETSIZE is 65536
  • De FD_SETSIZE-parameter kan worden gewijzigd
  • Nee FD_COPY

HP-UX 11.11

  • Nee <sys/select.h> header - gebruik <sys/time.h> in plaats daarvan
  • Standaard FD_SETSIZE is 2048
  • De FD_SETSIZE-parameter kan worden gewijzigd
  • Nee FD_COPY

HP-UX 11.23

  • heeft <sys/select.h>
  • Standaard FD_SETSIZE is 2048
  • De FD_SETSIZE-parameter kan worden gewijzigd
  • Nee FD_COPY

Linux (kernel 2.6.9, glibc 2.3.4)

  • Standaard FD_SETSIZE is 1024
  • De FD_SETSIZE-parameter kan niet verkleind worden
  • Nee FD_COPY

MacOS X 10.6.2

  • Standaard FD_SETSIZE is 1024
  • De FD_SETSIZE-parameter kan worden gewijzigd
  • FD_COPY wordt gedefinieerd als strenge POSIX-compliance niet wordt gevraagd of als _DARWIN_C_SOURCE is gespecificeerd

Solaris 10 (SPARC)

  • Standaard FD_SETSIZE is 1024 voor 32-bits, 65536 voor 64-bits
  • De FD_SETSIZE-parameter kan worden gewijzigd
  • Nee FD_COPY

Het is duidelijk dat een triviale wijziging van het programma het automatisch controleren van FD_COPY mogelijk maakt:

#ifdef FD_COPY
    printf("FD_COPY is a macro\n");
#endif

Wat niet noodzakelijk triviaal is, is erachter te komen hoe je ervoor kunt zorgen dat het beschikbaar is; je doet uiteindelijk de handmatige scan en werkt uit hoe je het kunt activeren.

Op al deze machines lijkt het een fd_set kan worden gekopieerd door een structuurkopie zonder het risico te lopen op ongedefinieerd gedrag.


1
2018-03-13 00:48