C Programmering

Læs Syscall Linux

Læs Syscall Linux
Så du skal læse binære data? Det kan være en god idé at læse fra en FIFO eller en stikkontakt? Du kan se, du kan bruge C-standardbiblioteksfunktionen, men ved at gøre det vil du ikke drage fordel af specielle funktioner leveret af Linux Kernel og POSIX. For eksempel kan du bruge timeouts til at læse på et bestemt tidspunkt uden at ty til afstemning. Du skal muligvis også læse noget uden at bekymre dig, hvis det er en speciel fil eller stikkontakt eller noget andet. Din eneste opgave er at læse noget binært indhold og få det i din applikation. Det er her, den læste syskall skinner.

Læs en normal fil med et Linux-syscall

Den bedste måde at begynde at arbejde med denne funktion er ved at læse en normal fil. Dette er den enkleste måde at bruge dette syscall på, og af en grund: det har ikke så mange begrænsninger som andre typer strøm eller rør. Hvis du tænker på det, er det logisk, når du læser output fra en anden applikation, skal du have noget output klar, før du læser det, og så bliver du nødt til at vente på, at denne applikation skriver denne output.

For det første en nøgleforskel med standardbiblioteket: Der er slet ingen buffering. Hver gang du kalder læsefunktionen, ringer du til Linux-kernen, og det vil tage tid - det er næsten øjeblikkeligt, hvis du kalder det en gang, men kan sænke dig, hvis du kalder det tusinder af gange i et sekund. Til sammenligning vil standardbiblioteket buffer input for dig. Så når du kalder læse, skal du læse mere end et par byte, men snarere en stor buffer som få kilobyte - undtagen hvis det, du har brug for, virkelig er få byte, for eksempel hvis du kontrollerer, om en fil findes og ikke er tom.

Dette har dog en fordel: hver gang du ringer til at læse, er du sikker på, at du får de opdaterede data, hvis andre applikationer i øjeblikket ændrer filen. Dette er især nyttigt for specielle filer, såsom dem i / proc eller / sys.

Tid til at vise dig et rigtigt eksempel. Dette C-program kontrollerer, om filen er PNG eller ej. For at gøre det læser den filen, der er angivet i den sti, du angiver i kommandolinjeargumentet, og den kontrollerer, om de første 8 byte svarer til et PNG-overskrift.

Her er koden:

#omfatte
#omfatte
#omfatte
#omfatte
#omfatte
#omfatte
#omfatte
 
typedef enum
IS_PNG,
FOR KORT,
INVALID_HEADER
pngStatus_t;
 
usigneret int isSyscallSuccessful (const ssize_t readStatus)
returnere readStatus> = 0;
 

 
/ *
* checkPngHeader kontrollerer, om pngFileHeader-arrayet svarer til en PNG
* filoverskrift.
*
* I øjeblikket kontrolleres det kun de første 8 bytes i arrayet. Hvis arrayet er mindre
* end 8 byte returneres TOO_SHORT.
*
* pngFileHeaderLength skal cintain kength af tye array. Enhver ugyldig værdi
* kan føre til udefineret adfærd, såsom applikationsnedbrud.
*
* Returnerer IS_PNG, hvis det svarer til en PNG-filoverskrift. Hvis der er mindst
* 8 bytes i arrayet, men det er ikke et PNG-header, INVALID_HEADER returneres.
*
* /
pngStatus_t checkPngHeader (const usigneret char * const pngFileHeader,
size_t pngFileHeaderLength) const usigneret char forventetPngHeader [8] =
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A;
int i = 0;
 
hvis (pngFileHeaderLength < sizeof(expectedPngHeader))
returner TOO_SHORT;
 

 
for (i = 0; i < sizeof(expectedPngHeader); i++)
hvis (pngFileHeader [i] != forventetPngHeader [i])
returner INVALID_HEADER;
 


 
/ * Hvis den når her, er alle de første 8 byte i overensstemmelse med et PNG-header. * /
returner IS_PNG;

 
int main (int argumentLength, char * argumentList [])
char * pngFileName = NULL;
usigneret char pngFileHeader [8] = 0;
 
ssize_t readStatus = 0;
/ * Linux bruger et nummer til at identificere en åben fil. * /
int pngFile = 0;
pngStatus_t pngCheckResult;
 
hvis (argumentLængde != 2)
fputs ("Du skal kalde dette program ved hjælp af isPng dit filnavn).\ n ", stderr);
returner EXIT_FAILURE;
 

 
pngFileName = argumentList [1];
pngFile = åben (pngFileName, O_RDONLY);
 
hvis (pngFile == -1)
perror ("Åbning af den angivne fil mislykkedes");
returner EXIT_FAILURE;
 

 
/ * Læs få byte for at identificere, om filen er PNG. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));
 
hvis (isSyscallSuccessful (readStatus))
/ * Kontroller, om filen er en PNG, da den fik dataene. * /
pngCheckResult = checkPngHeader (pngFileHeader, readStatus);
 
hvis (pngCheckResult == TOO_SHORT)
printf ("Filen% s er ikke en PNG-fil: den er for kort.\ n ", pngFileName);
 
ellers hvis (pngCheckResult == IS_PNG)
printf ("Filen% s er en PNG-fil!\ n ", pngFileName);
 
andet
printf ("Filen% s er ikke i PNG-format.\ n ", pngFileName);
 

 
andet
perror ("Læsning af filen mislykkedes");
returner EXIT_FAILURE;
 

 
/ * Luk filen ... * /
hvis (luk (pngFile) == -1)
perror ("Lukning af den angivne fil mislykkedes");
returner EXIT_FAILURE;
 

 
pngFile = 0;
 
returner EXIT_SUCCESS;
 

Se, det er et fuldt blæst, fungerende og kompilerbart eksempel. Tøv ikke med at kompilere det selv og teste det, det virker virkelig. Du skal ringe til programmet fra en terminal som denne:

./ isPng dit filnavn

Lad os nu fokusere på selve læseopkaldet:

pngFile = åben (pngFileName, O_RDONLY);
hvis (pngFile == -1)
perror ("Åbning af den angivne fil mislykkedes");
returner EXIT_FAILURE;

/ * Læs få byte for at identificere, om filen er PNG. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));

Den læste signatur er følgende (ekstraheret fra Linux-man-sider):

ssize_t læse (int fd, ugyldig * buf, størrelse_t antal);

For det første repræsenterer fd-argumentet filbeskrivelsen. Jeg har forklaret lidt dette koncept i min gaffelartikel.  En filbeskrivelse er en int, der repræsenterer en åben fil, stikkontakt, rør, FIFO, enhed, ja det er mange ting, hvor data kan læses eller skrives, generelt på en strømlignende måde. Jeg vil gå mere i dybden med det i en fremtidig artikel.

åben funktion er en af ​​måderne at fortælle til Linux: Jeg vil gøre ting med filen på den vej, find den, hvor den er, og giv mig adgang til den. Det giver dig denne int-kaldte filbeskrivelse tilbage, og hvis du vil gøre noget med denne fil, skal du bruge dette nummer. Glem ikke at ringe tæt, når du er færdig med filen, som i eksemplet.

Så du skal angive dette specielle nummer, der skal læses. Så er der buf-argumentet. Du skal her give en markør til det array, hvor læsning gemmer dine data. Endelig tæller det, hvor mange byte det højst læser.

Returneringsværdien er af typen ssize_t. Underlig type, er det ikke?? Det betyder "signeret størrelse_t", dybest set er det en lang int. Det returnerer antallet af bytes, det med succes læser, eller -1, hvis der er et problem. Du kan finde den nøjagtige årsag til problemet i den errno globale variabel oprettet af Linux, defineret i . Men for at udskrive en fejlmeddelelse er det bedre at bruge perror, da det udskriver errno på dine vegne.

I normale filer - og kun i dette tilfælde - læse returnerer kun mindre end tæller, hvis du har nået filens afslutning. Det buf-array, du leverer skal være stor nok til i det mindste at tælle bytes, ellers kan dit program gå ned eller oprette en sikkerhedsfejl.

Nu er læsning ikke kun nyttig til normale filer, og hvis du vil føle dens superkræfter - Ja, jeg ved, det er ikke i nogen Marvels tegneserier, men det har ægte kræfter - du vil gerne bruge den med andre strømme som f.eks. rør eller stikkontakter. Lad os se på det:

Linux-specialfiler og læs systemopkald

Den faktiske læsning fungerer med en række filer som f.eks. Rør, stikkontakter, FIFO'er eller specielle enheder såsom en disk eller seriel port er det, der gør det virkelig mere kraftfuldt. Med nogle tilpasninger kan du gøre rigtig interessante ting. For det første betyder det, at du bogstaveligt talt kan skrive funktioner, der arbejder på en fil og i stedet bruge det med et rør. Det er interessant at videregive data uden nogensinde at ramme disken, hvilket sikrer den bedste ydeevne.

Dette udløser dog også særlige regler. Lad os tage eksemplet med at læse en linje fra terminal sammenlignet med en normal fil. Når du kalder læs på en normal fil, behøver det kun få millisekunder til Linux for at få den mængde data, du anmoder om.

Men når det kommer til terminal, er det en anden historie: lad os sige, at du beder om et brugernavn. Brugeren indtaster terminalens brugernavn og trykker på Enter. Nu følger du mit råd ovenfor, og du kalder læs med en stor buffer som 256 byte.

Hvis læst fungerede som det gjorde med filer, ville det vente på, at brugeren skrev 256 tegn, før han vendte tilbage! Din bruger ville vente for evigt og derefter desværre dræbe din applikation. Det er bestemt ikke, hvad du vil have, og du ville have et stort problem.

Okay, du kunne læse en byte ad gangen, men denne løsning er frygtelig ineffektiv, som jeg fortalte dig ovenfor. Det skal fungere bedre end det.

Men Linux-udviklere tænkte at læse anderledes for at undgå dette problem:

  • Når du læser normale filer, forsøger det så meget som muligt at læse antal byte, og det får aktivt bytes fra disken, hvis det er nødvendigt.
  • For alle andre filtyper vender den tilbage så snart der er nogle tilgængelige data og højst tæl bytes:
    1. For terminaler er det generelt når brugeren trykker på Enter-tasten.
    2. For TCP-stik er det, så snart din computer modtager noget, betyder ikke noget, hvor mange byte den får.
    3. For FIFO eller rør er det generelt det samme beløb som det, den anden applikation skrev, men Linux-kernen kan levere mindre ad gangen, hvis det er mere praktisk.

Så du kan roligt ringe med din 2 KiB-buffer uden at forblive låst inde for evigt. Bemærk, at det også kan blive afbrudt, hvis applikationen modtager et signal. Da læsning fra alle disse kilder kan tage sekunder eller endda timer - indtil den anden side trods alt beslutter at skrive - at blive afbrudt af signaler gør det muligt at stoppe med at blive blokeret for længe.

Dette har dog også en ulempe: Når du nøjagtigt vil læse 2 KiB med disse specielle filer, skal du kontrollere læsets returværdi og ringe læse flere gange. læse udfylder sjældent hele din buffer. Hvis din applikation bruger signaler, skal du også kontrollere, om læsningen mislykkedes med -1, fordi den blev afbrudt af et signal ved hjælp af errno.

Lad mig vise dig, hvordan det kan være interessant at bruge denne særlige egenskab ved at læse:

#define _POSIX_C_SOURCE 1 / * sigaction er ikke tilgængelig uden denne #define. * /
#omfatte
#omfatte
#omfatte
#omfatte
#omfatte
#omfatte
/ *
* isSignal fortæller, om læst syscall er blevet afbrudt af et signal.
*
* Returnerer SAND, hvis det læste syscall er blevet afbrudt af et signal.
*
* Globale variabler: den læser errno defineret i errno.h
* /
usigneret int isSignal (const ssize_t readStatus)
returnere (readStatus == -1 && errno == EINTR);

usigneret int isSyscallSuccessful (const ssize_t readStatus)
returnere readStatus> = 0;

/ *
* shouldRestartRead fortæller, hvornår det læste syscall er blevet afbrudt af en
* signalhændelse eller ej, og givet denne "fejl" årsag er forbigående, kan vi
* genstart det læste opkald sikkert.
*
* I øjeblikket kontrolleres det kun, om læsning er blevet afbrudt af et signal, men det
* kunne forbedres for at kontrollere, om antallet af byte blev læst, og om det er
* ikke tilfældet, returner SAND for at læse igen.
*
* /
usigneret int shouldRestartRead (const ssize_t readStatus)
return isSignal (readStatus);

/ *
* Vi har brug for en tom håndterer, da læsesysscall kun afbrydes, hvis
* signalet håndteres.
* /
tomrum tomHandler (int ignoreret)
Vend tilbage;

int main ()
/ * Er på få sekunder. * /
const int alarmInterval = 5;
const struct sigaction emptySigaction = emptyHandler;
char lineBuf [256] = 0;
ssize_t readStatus = 0;
usigneret int ventetid = 0;
/ * Du må ikke ændre signatur, undtagen hvis du nøjagtigt ved, hvad du laver. * /
sigaction (SIGALRM, & emptySigaction, NULL);
alarm (alarminterval);
fputs ("Din tekst: \ n", stderr);
gøre
/ * Glem ikke '\ 0' * /
readStatus = read (STDIN_FILENO, lineBuf, sizeof (lineBuf) - 1);
hvis (isSignal (readStatus))
ventetid + = alarmInterval;
alarm (alarminterval);
fprintf (stderr, "% u sek af inaktivitet ... \ n", ventetid);

mens (shouldRestartRead (readStatus));
hvis (isSyscallSuccessful (readStatus))
/ * Afslut strengen for at undgå en fejl, når den leveres til fprintf. * /
lineBuf [readStatus] = '0';
fprintf (stderr, "Du skrev% lu tegn. Her er din streng: \ n% s \ n ", strlen (lineBuf),
lineBuf);
andet
perror ("Læsning fra stdin mislykkedes");
returner EXIT_FAILURE;

returner EXIT_SUCCESS;

Endnu en gang er dette et komplet C-program, som du kan kompilere og faktisk køre.

Det gør følgende: det læser en linje fra standardinput. Men hvert 5. sekund udskriver den en linje, der fortæller brugeren, at der endnu ikke blev givet noget input.

Eksempel, hvis jeg venter 23 sekunder, før jeg skriver “Penguin”:

$ alarm_read
Din tekst:
5 sek inaktivitet ..
10 sekunder inaktivitet ..
15 sekunder inaktivitet ..
20 sekunder inaktivitet ..
Pingvin
Du skrev 8 tegn. Her er din streng:
Pingvin

Det er utroligt nyttigt. Det kan bruges til ofte at opdatere brugergrænsefladen til at udskrive forløbet af læsningen eller behandlingen af ​​din applikation, du laver. Det kan også bruges som en timeout-mekanisme. Du kan også blive afbrudt af ethvert andet signal, der kan være nyttigt for din applikation. Under alle omstændigheder betyder det, at din applikation nu kan være lydhør i stedet for at forblive fast for evigt.

Så fordelene opvejer den ulempe, der er beskrevet ovenfor. Hvis du spekulerer på, om du skal understøtte specielle filer i et program, der normalt arbejder med normale filer - og så kalder Læs i en løkke - Jeg vil sige gør det, undtagen hvis du har travlt, viste min personlige erfaring ofte, at erstatning af en fil med et rør eller FIFO bogstaveligt kan gøre en applikation meget mere nyttig med lille indsats. Der er endda foruddefinerede C-funktioner på internettet, der implementerer den løkke til dig: det kaldes readn-funktioner.

Konklusion

Som du kan se, kan fread og læse se ud, de er det ikke. Og med kun få ændringer af, hvordan læsning fungerer for C-udvikleren, er læsning meget mere interessant for at designe nye løsninger på de problemer, du møder under applikationsudvikling.

Næste gang vil jeg fortælle dig, hvordan skriv syscall fungerer, da læsning er sej, men det er meget bedre at være i stand til begge dele. I mellemtiden kan du eksperimentere med læsning, lære det at kende, og jeg ønsker dig et godt nytår!

Sådan styrkes FPS i Linux?
FPS står for Billeder i sekundet. FPS's opgave er at måle billedhastigheden i videoafspilninger eller spiloptræden. I enkle ord betegnes antallet af u...
Top Oculus App Lab-spil
Hvis du er Oculus-headset-ejer, skal du være opmærksom på sideloading. Sideladning er processen med at installere ikke-butiksindhold på dit headset. S...
Top 10 spil at spille på Ubuntu
Windows-platform har været en af ​​de dominerende platforme til spil på grund af den enorme procentdel af spil, der udvikler sig i dag til indbygget a...