C Programmering

Les Syscall Linux

Les Syscall Linux
Så du må lese binære data? Det kan være lurt å lese fra en FIFO eller stikkontakt? Du skjønner, du kan bruke C-standardbiblioteksfunksjonen, men ved å gjøre det vil du ikke dra nytte av spesielle funksjoner levert av Linux Kernel og POSIX. Det kan for eksempel være lurt å bruke tidsavbrudd for å lese på et bestemt tidspunkt uten å ty til avstemning. I tillegg må du kanskje lese noe uten å bry deg om det er en spesiell fil eller stikkontakt eller noe annet. Din eneste oppgave er å lese noe binært innhold og få det i applikasjonen din. Det er der lesesystemet skinner.

Les en vanlig fil med et Linux-syskall

Den beste måten å begynne å jobbe med denne funksjonen er ved å lese en vanlig fil. Dette er den enkleste måten å bruke den syskallen på, og av en grunn: den har ikke så mange begrensninger som andre typer strøm eller rør. Hvis du tenker på det som er logisk, når du leser utdataene fra et annet program, må du ha litt utgang klar før du leser den, og du må vente til denne applikasjonen skriver denne utgangen.

For det første en viktig forskjell med standardbiblioteket: Det er ingen buffering i det hele tatt. Hver gang du ringer til lesefunksjonen, vil du ringe Linux-kjernen, og dette vil ta tid - det er nesten øyeblikkelig hvis du kaller det en gang, men kan bremse deg hvis du kaller det tusenvis av ganger i løpet av et sekund. Til sammenligning vil standardbiblioteket buffer input for deg. Så når du kaller lese, bør du lese mer enn noen få byte, men heller en stor buffer som få kilobyte - bortsett fra om det du trenger er veldig få byte, for eksempel hvis du sjekker om en fil eksisterer og ikke er tom.

Dette har imidlertid en fordel: hver gang du ringer lese, er du sikker på at du får oppdaterte data, hvis et annet program endrer filen for øyeblikket. Dette er spesielt nyttig for spesielle filer som de i / proc eller / sys.

På tide å vise deg et reelt eksempel. Dette C-programmet sjekker om filen er PNG eller ikke. For å gjøre det, leser den filen som er spesifisert i banen du oppgir i kommandolinjeargumentet, og den sjekker om de første 8 byte tilsvarer et PNG-overskrift.

Her er koden:

#inkludere
#inkludere
#inkludere
#inkludere
#inkludere
#inkludere
#inkludere
 
typedef enum
IS_PNG,
FOR KORT,
INVALID_HEADER
pngStatus_t;
 
usignert int isSyscallSuccessful (const ssize_t readStatus)
return readStatus> = 0;
 

 
/ *
* checkPngHeader sjekker om pngFileHeader-matrisen tilsvarer en PNG
* filoverskrift.
*
* For øyeblikket sjekker den bare de første 8 bytene i matrisen. Hvis matrisen er mindre
* enn 8 byte, returneres TOO_SHORT.
*
* pngFileHeaderLength må cintain kength av tye array. Enhver ugyldig verdi
* kan føre til udefinert oppførsel, for eksempel programkrasj.
*
* Returnerer IS_PNG hvis det tilsvarer en PNG-filoverskrift. Hvis det er i det minste
* 8 byte i matrisen, men det er ikke en PNG-overskrift, INVALID_HEADER returneres.
*
* /
pngStatus_t checkPngHeader (const usignert char * const pngFileHeader,
size_t pngFileHeaderLength) const usignert røye forventetPngHeader [8] =
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A;
int i = 0;
 
hvis (pngFileHeaderLength < sizeof(expectedPngHeader))
gå tilbake TOO_SHORT;
 

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


 
/ * Hvis den når hit, samsvarer alle de første 8 bytene med en PNG-overskrift. * /
returner IS_PNG;

 
int main (int argumentLength, char * argumentList [])
char * pngFileName = NULL;
usignert røye pngFileHeader [8] = 0;
 
ssize_t readStatus = 0;
/ * Linux bruker et tall for å identifisere en åpen fil. * /
int pngFile = 0;
pngStatus_t pngCheckResult;
 
hvis (argumentLengde != 2)
fputs ("Du må kalle dette programmet med isPng filnavnet ditt).\ n ", stderr);
returner EXIT_FAILURE;
 

 
pngFileName = argumentList [1];
pngFile = åpen (pngFileName, O_RDONLY);
 
hvis (pngFile == -1)
perror ("Åpning av den oppgitte filen mislyktes");
returner EXIT_FAILURE;
 

 
/ * Les noen få byte for å identifisere om filen er PNG. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));
 
hvis (isSyscallSuccessful (readStatus))
/ * Sjekk om filen er en PNG siden den fikk dataene. * /
pngCheckResult = checkPngHeader (pngFileHeader, readStatus);
 
hvis (pngCheckResult == TOO_SHORT)
printf ("Filen% s er ikke en PNG-fil: den er for kort.\ n ", pngFileName);
 
annet hvis (pngCheckResult == IS_PNG)
printf ("Filen% s er en PNG-fil!\ n ", pngFileName);
 
annet
printf ("Filen% s er ikke i PNG-format.\ n ", pngFileName);
 

 
annet
perror ("Lesing av filen mislyktes");
returner EXIT_FAILURE;
 

 
/ * Lukk filen ... * /
hvis (lukk (pngFile) == -1)
perror ("Lukking av den oppgitte filen mislyktes");
returner EXIT_FAILURE;
 

 
pngFile = 0;
 
returner EXIT_SUCCESS;
 

Se, det er et fullverdig, fungerende og kompilerbart eksempel. Ikke nøl med å kompilere det selv og teste det, det fungerer virkelig. Du bør ringe programmet fra en terminal som denne:

./ isPng filnavnet ditt

La oss nå fokusere på selve lesesamtalen:

pngFile = åpen (pngFileName, O_RDONLY);
hvis (pngFile == -1)
perror ("Åpning av den oppgitte filen mislyktes");
returner EXIT_FAILURE;

/ * Les noen få byte for å identifisere om filen er PNG. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));

Lesesignaturen er følgende (hentet fra Linux-man-sider):

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

Først representerer fd-argumentet filbeskrivelsen. Jeg har forklart litt om dette konseptet i gaffelartikkelen min.  En filbeskrivelse er en int som representerer en åpen fil, stikkontakt, pipe, FIFO, enhet, vel, det er mange ting der data kan leses eller skrives, vanligvis på en strømlignende måte. Jeg vil gå nærmere inn på det i en fremtidig artikkel.

åpen funksjon er en av måtene å fortelle til Linux: Jeg vil gjøre ting med filen på den banen, vær så snill å finne den der den er og gi meg tilgang til den. Det vil gi deg tilbake denne int kalt filbeskrivelsen, og nå, hvis du vil gjøre noe med denne filen, bruk dette nummeret. Ikke glem å ringe nær når du er ferdig med filen, som i eksemplet.

Så du må oppgi dette spesielle nummeret å lese. Så er det buf-argumentet. Du bør her gi en peker til matrisen der lesing lagrer dataene dine. Til slutt er telle hvor mange byte den vil lese på det meste.

Returverdien er av typen ssize_t. Rart type, er det ikke?? Det betyr "signert størrelse_t", i utgangspunktet er det en lang int. Den returnerer antall byte den leses, eller -1 hvis det er et problem. Du kan finne den nøyaktige årsaken til problemet i errno global variabel opprettet av Linux, definert i . Men for å skrive ut en feilmelding, er det bedre å bruke perror ettersom det skriver ut feil på dine vegne.

I normale filer - og kun i dette tilfellet - les vil bare returnere mindre enn telle hvis du har nådd filens slutt. Buf-arrayet du gir være stor nok til å passe minst antall byte, ellers kan programmet krasje eller opprette en sikkerhetsfeil.

Nå er lese ikke bare nyttig for normale filer, og hvis du vil føle superkreftene - Ja, jeg vet at det ikke er i noen Marvels tegneserier, men det har sanne krefter - du vil bruke den med andre strømmer som rør eller stikkontakter. La oss ta en titt på det:

Linux-spesielle filer og les systemanrop

Fakta lese fungerer med en rekke filer som rør, stikkontakter, FIFOer eller spesielle enheter som en disk eller seriell port er det som gjør den virkelig kraftigere. Med noen tilpasninger kan du gjøre veldig interessante ting. For det første betyr dette at du bokstavelig talt kan skrive funksjoner som fungerer på en fil og bruke den med et rør i stedet. Det er interessant å overføre data uten å treffe noen disk, noe som gir best ytelse.

Dette utløser imidlertid også spesielle regler. La oss ta eksemplet med å lese en linje fra terminalen sammenlignet med en vanlig fil. Når du ringer for å lese på en vanlig fil, trenger den bare noen få millisekunder til Linux for å få mengden data du ber om.

Men når det gjelder terminal, er det en annen historie: la oss si at du ber om et brukernavn. Brukeren skriver inn terminalen sitt brukernavn og trykker Enter. Nå følger du rådene mine ovenfor, og du ringer lese med en stor buffer som 256 byte.

Hvis lesing fungerte som den gjorde med filer, ville den vente til brukeren skrev 256 tegn før han kom tilbake! Brukeren din ville vente for alltid, og så dessverre drepe søknaden din. Det er absolutt ikke det du vil ha, og du vil ha et stort problem.

Ok, du kan lese en byte om gangen, men denne løsningen er veldig ineffektiv, som jeg fortalte deg ovenfor. Det må fungere bedre enn det.

Men Linux-utviklere tenkte å lese annerledes for å unngå dette problemet:

  • Når du leser vanlige filer, prøver den så mye som mulig å lese antall byte, og den vil aktivt få byte fra disken hvis det er nødvendig.
  • For alle andre filtyper kommer den tilbake så snart som det er noen data tilgjengelig og på det meste telle byte:
    1. For terminaler er det som regel når brukeren trykker Enter-tasten.
    2. For TCP-stikkontakter er det så snart datamaskinen din mottar noe, spiller ingen rolle hvor mye byte den får.
    3. For FIFO eller rør er det generelt samme beløp som det andre programmet skrev, men Linux-kjernen kan levere mindre om gangen hvis det er mer praktisk.

Så du kan trygt ringe med 2 KiB-bufferen uten å forbli låst for alltid. Merk at det også kan bli avbrutt hvis applikasjonen mottar et signal. Da det kan ta sekunder eller til og med timer å lese fra alle disse kildene - helt til den andre siden bestemmer seg for å skrive - blir avbrutt av signaler gjør det mulig å slutte å være sperret for lenge.

Dette har også en ulempe: Når du vil lese nøyaktig 2 KiB med disse spesielle filene, må du sjekke lesens returverdi og ringelese flere ganger. lese vil sjelden fylle hele bufferen. Hvis applikasjonen din bruker signaler, må du også sjekke om lesingen mislyktes med -1 fordi den ble avbrutt av et signal ved hjelp av errno.

La meg vise deg hvordan det kan være interessant å bruke denne spesielle egenskapen til å lese:

#define _POSIX_C_SOURCE 1 / * sigaction er ikke tilgjengelig uten denne #define. * /
#inkludere
#inkludere
#inkludere
#inkludere
#inkludere
#inkludere
/ *
* isSignal forteller om lest syscall har blitt avbrutt av et signal.
*
* Returnerer SANN hvis den avleste syskallen er avbrutt av et signal.
*
* Globale variabler: den leser errno definert i errno.h
* /
usignert int isSignal (const ssize_t readStatus)
retur (readStatus == -1 && errno == EINTR);

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

/ *
* shouldRestartRead forteller når lesesystemet er avbrutt av a
* signalhendelse eller ikke, og gitt denne "feilen" årsaken er forbigående, kan vi
* start omlest på en sikker måte.
*
* For øyeblikket sjekker den bare om lesing er avbrutt av et signal, men det
* kan forbedres for å sjekke om målet antall byte ble lest, og om det er
* ikke tilfelle, returner SANT for å lese igjen.
*
* /
usignert int shouldRestartRead (const ssize_t readStatus)
retur isSignal (readStatus);

/ *
* Vi trenger en tom behandler, ettersom lesesystemet bare blir avbrutt hvis
* signalet håndteres.
* /
ugyldig tomHandler (int ignorert)
komme tilbake;

int main ()
/ * Er på sekunder. * /
const int alarmInterval = 5;
const struct sigaction emptySigaction = emptyHandler;
røyke lineBuf [256] = 0;
ssize_t readStatus = 0;
usignert int ventetid = 0;
/ * Ikke modifiser signatur bortsett fra hvis du vet nøyaktig hva du gjør. * /
sigaction (SIGALRM, & emptySigaction, NULL);
alarm (alarmInterval);
fputs ("Teksten din: \ n", stderr);
gjør
/ * Ikke glem '\ 0' * /
readStatus = read (STDIN_FILENO, lineBuf, sizeof (lineBuf) - 1);
hvis (isSignal (readStatus))
ventetid + = alarmintervall;
alarm (alarmInterval);
fprintf (stderr, "% u sekunder av inaktivitet ... \ n", ventetid);

mens (shouldRestartRead (readStatus));
hvis (isSyscallSuccessful (readStatus))
/ * Avslutt strengen for å unngå en feil når du gir den til fprintf. * /
lineBuf [readStatus] = '\ 0';
fprintf (stderr, "Du skrev% lu tegn. Her er strengen din: \ n% s \ n ", strlen (lineBuf),
lineBuf);
annet
perror ("Lesing fra stdin mislyktes");
returner EXIT_FAILURE;

returner EXIT_SUCCESS;

Nok en gang er dette et fullstendig C-program som du kan kompilere og faktisk kjøre.

Den gjør følgende: den leser en linje fra standardinndata. Men hvert 5. sekund skriver den ut en linje som forteller brukeren at det ikke ble gitt noen inndata ennå.

Eksempel hvis jeg venter 23 sekunder før jeg skriver "Penguin":

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

Det er utrolig nyttig. Den kan brukes til å oppdatere brukergrensesnittet ofte for å skrive ut fremdriften for lesingen eller behandlingen av applikasjonen du gjør. Den kan også brukes som en timeout-mekanisme. Du kan også bli avbrutt av ethvert annet signal som kan være nyttig for applikasjonen din. Uansett betyr dette at søknaden din nå kan være responsiv i stedet for å bli sittende fast for alltid.

Så fordelene oppveier ulempen beskrevet ovenfor. Hvis du lurer på om du skal støtte spesielle filer i et program som normalt fungerer med vanlige filer - og så ringe lese i en løkke - Jeg vil si gjør det, bortsett fra hvis du har det travelt, viste min personlige erfaring ofte at å erstatte en fil med et rør eller FIFO bokstavelig talt kan gjøre en applikasjon mye mer nyttig med liten innsats. Det er til og med forhåndsinnstilte C-funksjoner på Internett som implementerer den sløyfen for deg: det kalles readn-funksjoner.

Konklusjon

Som du kan se, kan fread og lese se ut, de er det ikke. Og med bare noen få endringer i hvordan lesing fungerer for C-utvikleren, er lesing mye mer interessant for å designe nye løsninger på problemene du møter under applikasjonsutvikling.

Neste gang vil jeg fortelle deg hvordan skrive syscall fungerer, ettersom det er kult å lese, men å kunne gjøre begge deler er mye bedre. I mellomtiden kan du eksperimentere med lese, bli kjent med den og jeg ønsker deg et godt nytt år!

Installer det siste OpenRA-strategispillet på Ubuntu Linux
OpenRA er en Libre / Free Real Time Strategy-spillmotor som gjenskaper de tidlige Westwood-spillene som den klassiske Command & Conquer: Red Alert. Di...
Installer nyeste Dolphin Emulator for Gamecube & Wii på Linux
Dolphin Emulator lar deg spille de valgte Gamecube- og Wii-spillene dine på Linux Personal Computers (PC). Som en fritt tilgjengelig og åpen kildekod...
Hvordan bruke GameConqueror Cheat Engine i Linux
Artikkelen dekker en guide om bruk av GameConqueror-juksemotoren i Linux. Mange brukere som spiller spill på Windows bruker ofte "Cheat Engine" -appli...