C Programmeren

Lees Syscall Linux

Lees Syscall Linux
U moet dus binaire gegevens lezen? Misschien wilt u lezen van een FIFO of socket? U ziet, u kunt de C-standaardbibliotheekfunctie gebruiken, maar door dit te doen, profiteert u niet van de speciale functies van Linux Kernel en POSIX. U wilt bijvoorbeeld time-outs gebruiken om op een bepaald tijdstip te lezen zonder toevlucht te nemen tot polling. Het kan ook zijn dat je iets moet lezen zonder je zorgen te maken of het een speciaal bestand of socket of iets anders is. Uw enige taak is om wat binaire inhoud te lezen en deze in uw toepassing te krijgen. Dat is waar de gelezen syscall schijnt.

Lees een normaal bestand met een Linux syscall

De beste manier om met deze functie aan de slag te gaan, is door een normaal bestand te lezen. Dit is de eenvoudigste manier om die syscall te gebruiken, en met een reden: het heeft niet zoveel beperkingen als andere soorten streams of pijpen. Als je erover nadenkt, dat is logisch, wanneer je de uitvoer van een andere applicatie leest, moet je wat uitvoer gereed hebben voordat je deze kunt lezen en dus moet je wachten tot deze applicatie deze uitvoer schrijft.

Ten eerste een belangrijk verschil met de standaardbibliotheek: er is helemaal geen buffering. Elke keer dat je de leesfunctie aanroept, roep je de Linux Kernel aan, en dit gaat dus tijd kosten -‌ het is bijna onmiddellijk als je het één keer roept, maar het kan je vertragen als je het duizenden keren per seconde roept. Ter vergelijking: de standaardbibliotheek buffert de invoer voor u. Dus wanneer je read noemt, zou je meer dan een paar bytes moeten lezen, maar eerder een grote buffer zoals een paar kilobytes - behalve als je echt weinig bytes nodig hebt, bijvoorbeeld als je controleert of een bestand bestaat en niet leeg is.

Dit heeft echter een voordeel: elke keer dat u read aanroept, weet u zeker dat u de bijgewerkte gegevens krijgt, als een andere toepassing het bestand op dit moment wijzigt. Dit is vooral handig voor speciale bestanden zoals die in /proc of /sys.

Tijd om je te laten zien met een echt voorbeeld. Dit C-programma controleert of het bestand PNG is of niet. Om dit te doen, leest het het bestand dat is opgegeven in het pad dat u opgeeft in het opdrachtregelargument en controleert het of de eerste 8 bytes overeenkomen met een PNG-header.

Hier is de code:

#include
#include
#include
#include
#include
#include
#include
 
typedef enum
IS_PNG,
TE KORT,
INVALID_HEADER
pngStatus_t;
 
unsigned int isSyscallSuccessful(const ssize_t readStatus)
return readStatus >= 0;
 

 
/*
* checkPngHeader controleert of de pngFileHeader-array overeenkomt met een PNG
* bestandskop.
*
* Momenteel controleert het alleen de eerste 8 bytes van de array. Als de array kleiner is
* dan 8 bytes, wordt TOO_SHORT geretourneerd.
*
* pngFileHeaderLength moet de kength van tye-array hebben. Elke ongeldige waarde
* kan leiden tot ongedefinieerd gedrag, zoals het crashen van applicaties.
*
* Retourneert IS_PNG als het overeenkomt met een PNG-bestandsheader. Als er tenminste is
* 8 bytes in de array maar het is geen PNG-header, INVALID_HEADER wordt geretourneerd.
*
*/
pngStatus_t checkPngHeader(const unsigned char* const pngFileHeader,
size_t pngFileHeaderLength) const unsigned char verwachtPngHeader[8] =
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A;
int ik = 0;
 
if (pngFileHeaderLength < sizeof(expectedPngHeader))
retourneer TOO_SHORT;
 

 
voor (i = 0; i < sizeof(expectedPngHeader); i++)
if (pngFileHeader[i] != verwachtePngHeader[i])
retourneer INVALID_HEADER;
 


 
/* Als het hier komt, voldoen alle eerste 8 bytes aan een PNG-header. */
retourneer IS_PNG;

 
int main(int argumentLength,  char *argumentList[])
char *pngBestandsnaam = NULL;
unsigned char pngFileHeader[8] = 0;
 
ssize_t readStatus = 0;
/* Linux gebruikt een nummer om een ​​open bestand te identificeren. */
int pngFile = 0;
pngStatus_t pngCheckResult;
 
als (argumentLengte) != 2)
fputs("Je moet dit programma aanroepen met isPng je bestandsnaam.\n", stderr);
retourneer EXIT_FAILURE;
 

 
pngFileName = argumentList[1];
pngFile = open(pngFileName, O_RDONLY);
 
if (pngFile == -1)
perror("Openen van het opgegeven bestand is mislukt");
retourneer EXIT_FAILURE;
 

 
/* Lees enkele bytes om te bepalen of het bestand PNG is. */
readStatus = read(pngFile, pngFileHeader, sizeof(pngFileHeader));
 
if (isSyscallSuccessful(leesStatus))
/* Controleer of het bestand een PNG is omdat het de gegevens heeft ontvangen. */
pngCheckResult = checkPngHeader(pngFileHeader, readStatus);
 
if        (pngCheckResult == TOO_SHORT)
printf("Het bestand %s is geen PNG-bestand: het is te kort.\n", pngBestandsnaam);
 
else if (pngCheckResult == IS_PNG)
printf("Het bestand %s is een PNG-bestand!\n", pngBestandsnaam);
 
anders
printf("Het bestand %s is niet in PNG-formaat.\n", pngBestandsnaam);
 

 
anders
perr("Het lezen van het bestand is mislukt");
retourneer EXIT_FAILURE;
 

 
/* Sluit het bestand... */
if (close(pngFile) == -1)
perror("Sluiten van het opgegeven bestand is mislukt");
retourneer EXIT_FAILURE;
 

 
pngBestand = 0;
 
retourneer EXIT_SUCCESS;
 

Kijk, het is een compleet, werkend en compileerbaar voorbeeld. Aarzel niet om het zelf te compileren en te testen, het werkt echt. U zou het programma vanaf een terminal als deze moeten aanroepen:

./isPng uw bestandsnaam

Laten we ons nu concentreren op de leesaanroep zelf:

pngFile = open(pngFileName, O_RDONLY);
if (pngFile == -1)
perror("Openen van het opgegeven bestand is mislukt");
retourneer EXIT_FAILURE;

/* Lees enkele bytes om te bepalen of het bestand PNG is. */
readStatus = read(pngFile, pngFileHeader, sizeof(pngFileHeader));

De leeshandtekening is de volgende (geëxtraheerd uit Linux man-pagina's):

ssize_t read(int fd, void *buf, size_t count);

Ten eerste vertegenwoordigt het fd-argument de bestandsdescriptor. Ik heb dit concept een beetje uitgelegd in mijn vorkartikel.  Een bestandsdescriptor is een int die een open bestand, socket, pijp, FIFO, apparaat vertegenwoordigt, nou, het zijn veel dingen waar gegevens kunnen worden gelezen of geschreven, meestal op een stream-achtige manier. Daar ga ik in een volgend artikel dieper op in.

open-functie is een van de manieren om Linux te vertellen: ik wil dingen doen met het bestand op dat pad, zoek het alsjeblieft op waar het is en geef me er toegang toe. Het geeft je deze int genaamd bestandsdescriptor terug en nu, als je iets met dit bestand wilt doen, gebruik dan dat nummer. Vergeet niet close te bellen als je klaar bent met het bestand, zoals in het voorbeeld.

U moet dus dit speciale nummer opgeven om te kunnen lezen. Dan is er het buf-argument. U moet hier een verwijzing naar de array opgeven waar read uw gegevens zal opslaan. Ten slotte, tel is hoeveel bytes het maximaal zal lezen.

De retourwaarde is van het type ssize_t. Vreemd type, niet?? Het betekent "ondertekend size_t", eigenlijk is het een lange int. Het geeft het aantal bytes terug dat het met succes heeft gelezen, of -1 als er een probleem is. Je kunt de exacte oorzaak van het probleem vinden in de errno globale variabele gemaakt door Linux, gedefinieerd in defined . Maar om een ​​foutbericht af te drukken, is het beter om perror te gebruiken omdat het namens jou errno afdrukt.

In normale bestanden - en enkel en alleen in dit geval - read geeft alleen minder dan count terug als je het einde van het bestand hebt bereikt. De buf-array die u verstrekt moet groot genoeg zijn om op zijn minst in bytes te passen, anders kan je programma crashen of een beveiligingsfout veroorzaken.

Nu is lezen niet alleen handig voor normale bestanden en als je de superkrachten ervan wilt voelen - Ja, ik weet dat het in geen enkele Marvel-strip staat, maar het heeft echte krachten - je zult het willen gebruiken met andere streams zoals pijpen of stopcontacten. Laten we daar eens naar kijken:

Speciale Linux-bestanden en systeemoproep lezen

Het feit dat lezen werkt met een verscheidenheid aan bestanden, zoals buizen, sockets, FIFO's of speciale apparaten zoals een schijf of seriële poort, maakt het echt krachtiger. Met wat aanpassingen kun je echt interessante dingen doen. Ten eerste betekent dit dat je letterlijk functies kunt schrijven die aan een bestand werken en het in plaats daarvan met een pijp gebruiken. Dat is interessant om gegevens door te geven zonder ooit de schijf te raken, wat zorgt voor de beste prestaties.

Dit leidt echter ook tot speciale regels. Laten we het voorbeeld nemen van het lezen van een regel van terminal in vergelijking met een normaal bestand. Wanneer je read op een normaal bestand aanroept, heeft Linux maar een paar milliseconden nodig om de hoeveelheid gegevens te krijgen die je vraagt.

Maar als het op terminal aankomt, is dat een ander verhaal: laten we zeggen dat je om een ​​gebruikersnaam vraagt. De gebruiker typt terminal haar/zijn gebruikersnaam in en drukt op Enter. Nu volg je mijn advies hierboven en roep je read met een grote buffer zoals 256 bytes.

Als lezen werkte zoals het deed met bestanden, zou het wachten tot de gebruiker 256 tekens typt voordat hij terugkeert! Je gebruiker zou eeuwig wachten en dan helaas je applicatie doden kill. Het is zeker niet wat je wilt, en je zou een groot probleem hebben.

Oké, je zou één byte per keer kunnen lezen, maar deze oplossing is vreselijk inefficiënt, zoals ik je hierboven heb verteld. Het moet beter werken dan dat.

Maar Linux-ontwikkelaars dachten anders te lezen om dit probleem te voorkomen:

  • Wanneer je normale bestanden leest, probeert het zoveel mogelijk bytes te lezen en het zal actief bytes van de schijf halen als dat nodig is.
  • Voor alle andere bestandstypen zal het terugkeren zodra er zijn wat gegevens beschikbaar en hoogstens tel bytes:
    1. Voor terminals is het: over het algemeen wanneer de gebruiker op de Enter-toets drukt.
    2. Voor TCP-sockets geldt: zodra uw computer iets ontvangt, maakt het niet uit hoeveel bytes het krijgt.
    3. Voor FIFO of pipes is het over het algemeen hetzelfde bedrag als wat de andere applicatie schreef, maar de Linux-kernel kan minder tegelijk leveren als dat handiger is.

U kunt dus veilig bellen met uw 2 KiB-buffer zonder voor altijd opgesloten te zitten. Merk op dat het ook onderbroken kan worden als de applicatie een signaal ontvangt. Omdat het lezen van al deze bronnen seconden of zelfs uren kan duren - totdat de andere kant toch besluit te schrijven - onderbroken worden door signalen maakt het mogelijk om te lang geblokkeerd te blijven.

Dit heeft echter ook een nadeel: als je precies 2 KiB wilt lezen met deze speciale bestanden, moet je de return-waarde van read controleren en read meerdere keren aanroepen. lezen vult zelden je hele buffer. Als uw toepassing signalen gebruikt, moet u ook controleren of het lezen is mislukt met -1 omdat deze werd onderbroken door een signaal, met errno.

Ik zal je laten zien hoe het interessant kan zijn om deze speciale eigenschap van read:

#define _POSIX_C_SOURCE 1 /* sigactie is niet beschikbaar zonder deze #define. */
#include
#include
#include
#include
#include
#include
/*
* isSignal vertelt of leessyscall is onderbroken door een signaal.
*
* Retourneert TRUE als de gelezen syscall is onderbroken door een signaal.
*
* Globale variabelen: het leest errno gedefinieerd in errno.h
*/
unsigned int isSignal(const ssize_t readStatus)
return (readStatus == -1 && errno == EINTR);

unsigned int isSyscallSuccessful(const ssize_t readStatus)
return readStatus >= 0;

/*
* ShouldRestartRead vertelt wanneer de read syscall is onderbroken door a
* signaalgebeurtenis of niet, en gezien deze "fout" reden van voorbijgaande aard is, kunnen we:
* herstart de leesoproep veilig.
*
* Momenteel controleert het alleen of het lezen is onderbroken door een signaal, maar het
* kan worden verbeterd om te controleren of het beoogde aantal bytes is gelezen en of dit het geval is
* niet het geval, retourneer TRUE om opnieuw te lezen.
*
*/
unsigned int shouldRestartRead(const ssize_t readStatus)
retour isSignaal(leesStatus);

/*
* We hebben een lege handler nodig omdat de read syscall alleen wordt onderbroken als de
* signaal wordt afgehandeld.
*/
void legeHandler(int genegeerd)
terugkeer;

int hoofd()
/* Is in seconden. */
const int alarmInterval = 5;
const struct sigaction emptySigaction = emptyHandler;
char lineBuf[256] = 0;
ssize_t readStatus = 0;
unsigned int waitTime = 0;
/* Wijzig de sigactie alleen als u precies weet wat u doet. */
sigactie (SIGALRM, &emptySigaction, NULL);
alarm (alarminterval);
fputs("Uw tekst:\n", stderr);
Doen
/* Vergeet de '\0' niet */
readStatus = read(STDIN_FILENO, lineBuf, sizeof(lineBuf) - 1);
if (isSignaal(leesStatus))
waitTime += alarmInterval;
alarm (alarminterval);
fprintf(stderr, "%u seconden inactiviteit... \n", waitTime);

while (shouldRestartRead(readStatus));
if (isSyscallSuccessful(leesStatus))
/* Beëindig de string om een ​​bug te voorkomen bij het verstrekken aan fprintf. */
lineBuf[readStatus] = '\0';
fprintf(stderr, "Je typte %lu chars. Hier is je string:\n%s\n", strlen(lineBuf),
lijnBuf);
anders
perror ("Lezen van stdin mislukt");
retourneer EXIT_FAILURE;

retourneer EXIT_SUCCESS;

Nogmaals, dit is een volledige C-toepassing die u kunt compileren en daadwerkelijk kunt uitvoeren.

Het doet het volgende: het leest een regel uit de standaardinvoer. Elke 5 seconden drukt het echter een regel af die de gebruiker vertelt dat er nog geen invoer is gegeven.

Voorbeeld als ik 23 seconden wacht voordat ik "Pinguïn" typ:

$ alarm_read
Je berichtje:
5 seconden inactiviteit…
10 seconden inactiviteit…
15 seconden inactiviteit…
20 seconden inactiviteit…
pinguïn
Je hebt 8 tekens getypt. Hier is je tekenreeks:
pinguïn

Dat is ongelooflijk handig. Het kan worden gebruikt om de gebruikersinterface vaak bij te werken om de voortgang van het lezen of de verwerking van uw aanvraag af te drukken. Het kan ook worden gebruikt als een time-outmechanisme. U kunt ook worden onderbroken door een ander signaal dat nuttig kan zijn voor uw toepassing. Hoe dan ook, dit betekent dat je applicatie nu responsief kan zijn in plaats van voor altijd vast te blijven zitten.

Dus de voordelen wegen op tegen het hierboven beschreven nadeel. Als u zich afvraagt ​​of u speciale bestanden moet ondersteunen in een toepassing die normaal met normale bestanden werkt - en zo roepend lezen in een lus - Ik zou zeggen doe het, behalve als je haast hebt, mijn persoonlijke ervaring heeft vaak uitgewezen dat het vervangen van een bestand door een pipe of FIFO een applicatie letterlijk veel nuttiger kan maken met kleine inspanningen. Er zijn zelfs vooraf gemaakte C-functies op internet die die lus voor u implementeren: het wordt readn-functies genoemd.

Conclusie

Zoals je kunt zien, kunnen fread en read er hetzelfde uitzien, maar dat zijn ze niet. En met slechts enkele wijzigingen in hoe lezen werkt voor de C-ontwikkelaar, is lezen veel interessanter voor het ontwerpen van nieuwe oplossingen voor de problemen die u tegenkomt tijdens de ontwikkeling van applicaties.

De volgende keer zal ik je vertellen hoe syscall schrijven werkt, want lezen is cool, maar beide kunnen doen is veel beter. Experimenteer in de tussentijd met lezen, leer het kennen en ik wens je een gelukkig nieuwjaar!

5 beste arcade-spellen voor Linux
Tegenwoordig zijn computers serieuze machines die worden gebruikt om te gamen. Als je de nieuwe hoge score niet kunt halen, weet je wat ik bedoel. In ...
Strijd om Wesnoth 1.13.6 Ontwikkeling vrijgegeven
Strijd om Wesnoth 1.13.6 die vorige maand werd uitgebracht, is de zesde ontwikkelingsrelease in de 1.13.x-serie en het levert een aantal verbeteringen...
Hoe League Of Legends op Ubuntu 14 te installeren.04
Als je fan bent van League of Legends, dan is dit een kans voor jou om League of Legends te testen. Merk op dat LOL wordt ondersteund op PlayOnLinux a...