C Programiranje

Preberite Syscall Linux

Preberite Syscall Linux
Torej morate prebrati binarne podatke? Morda boste želeli brati iz FIFO ali vtičnice? Veste, lahko uporabljate standardno knjižnično funkcijo C, vendar s tem ne boste imeli koristi od posebnih funkcij, ki jih ponujata Linux Kernel in POSIX. Na primer, morda boste želeli uporabiti časovne omejitve za branje ob določenem času, ne da bi se zatekli k glasovanju. Prav tako boste morda morali kaj prebrati, ne da bi vas skrbelo, ali gre za posebno datoteko ali vtičnico ali kaj drugega. Vaša edina naloga je prebrati nekaj binarnih vsebin in jih vnesti v svojo aplikacijo. Tam zasveti prebrani syscall.

Preberite običajno datoteko s sistemskim klicem Linux

Najboljši način za začetek dela s to funkcijo je branje običajne datoteke. To je najpreprostejši način uporabe tega syscall-a in to z razlogom: nima toliko omejitev kot druge vrste toka ali cevi. Če pomislite, da je to logika, morate pri branju izhodov druge aplikacije pripraviti nekaj izhodnih podatkov, preden jih preberete, zato boste morali počakati, da ta aplikacija napiše izhod.

Najprej ključna razlika s standardno knjižnico: medpomnjenja sploh ni. Vsakič, ko pokličete funkcijo branja, boste poklicali jedro Linuxa, zato bo to trajalo nekaj časa - je skoraj trenutek, če ga pokličete enkrat, lahko pa ga upočasnite, če ga pokličete tisočkrat v sekundi. Za primerjavo vam bo standardna knjižnica medpomnila vnos. Torej, kadar koli pokličete branje, bi morali prebrati več kot nekaj bajtov, prej pa velik medpomnilnik, kot je nekaj kilobajtov - razen če potrebujete zares nekaj bajtov, na primer če preverite, ali datoteka obstaja in ni prazna.

Vendar ima to prednost: vsakič, ko pokličete branje, ste prepričani, da dobite posodobljene podatke, če katera druga aplikacija trenutno spremeni datoteko. To je še posebej uporabno za posebne datoteke, kot so datoteke v / proc ali / sys.

Čas je, da vam pokažem pravi primer. Ta program C preverja, ali je datoteka PNG ali ne. V ta namen prebere datoteko, določeno v poti, ki jo navedete v argumentu ukazne vrstice, in preveri, ali prvih 8 bajtov ustreza glavi PNG.

Tu je koda:

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

 
/ *
* checkPngHeader preverja, ali matrika pngFileHeader ustreza PNG
* glava datoteke.
*
* Trenutno preverja samo prvih 8 bajtov matrike. Če je matrika manj
* od 8 bajtov se vrne TOO_SHORT.
*
* pngFileHeaderLength mora ohranjati kengto matrike. Vsaka neveljavna vrednost
* lahko privede do nedefiniranega vedenja, na primer do zrušitve aplikacije.
*
* Vrne IS_PNG, če ustreza glavi datoteke PNG. Če je vsaj
* 8 bajtov v matriki, vendar ni glavo PNG, vrne se INVALID_HEADER.
*
* /
pngStatus_t checkPngHeader (const nepodpisani znak * const pngFileHeader,
size_t pngFileHeaderLength) const nepodpisani znak, ki se pričakujePngHeader [8] =
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A;
int i = 0;
 
if (pngFileHeaderLength < sizeof(expectedPngHeader))
vrni TOO_SHORT;
 

 
za (i = 0; i < sizeof(expectedPngHeader); i++)
if (pngFileHeader [i] != pričakovanoPngHeader [i])
vrni INVALID_HEADER;
 


 
/ * Če doseže sem, je vseh prvih 8 bajtov v skladu z glavo PNG. * /
vrni IS_PNG;

 
int main (int argumentLength, char * argumentList [])
char * pngFileName = NULL;
nepodpisani znak pngFileHeader [8] = 0;
 
ssize_t readStatus = 0;
/ * Linux uporablja številko za identifikacijo odprte datoteke. * /
int pngFile = 0;
pngStatus_t pngCheckResult;
 
if (argumentLength != 2)
fputs ("Ta program morate poklicati s pomočjo isPng vaše ime datoteke.\ n ", stderr);
vrni EXIT_FAILURE;
 

 
pngFileName = argumentList [1];
pngFile = odprto (pngFileName, O_RDONLY);
 
če (pngFile == -1)
perror ("Odpiranje predložene datoteke ni uspelo");
vrni EXIT_FAILURE;
 

 
/ * Preberite nekaj bajtov, da ugotovite, ali je datoteka PNG. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));
 
if (isSyscallSuccessful (readStatus))
/ * Preverite, ali je datoteka PNG, ker je dobila podatke. * /
pngCheckResult = checkPngHeader (pngFileHeader, readStatus);
 
če (pngCheckResult == TOO_SHORT)
printf ("Datoteka% s ni datoteka PNG: prekratka je.\ n ", pngFileName);
 
sicer če (pngCheckResult == IS_PNG)
printf ("Datoteka% s je datoteka PNG!\ n ", pngFileName);
 
še
printf ("Datoteka% s ni v obliki PNG.\ n ", pngFileName);
 

 
še
perror ("Branje datoteke ni uspelo");
vrni EXIT_FAILURE;
 

 
/ * Zaprite datoteko… * /
if (close (pngFile) == -1)
perror ("Zapiranje predložene datoteke ni uspelo");
vrni EXIT_FAILURE;
 

 
pngFile = 0;
 
vrni EXIT_SUCCESS;
 

Glej, to je popoln, delujoč in sestavljiv primer. Ne oklevajte, da ga sestavite sami in preizkusite, res deluje. Program bi morali poklicati s terminala, kot je ta:

./ isPng vaše ime datoteke

Zdaj pa se osredotočimo na sam klic branja:

pngFile = odprto (pngFileName, O_RDONLY);
če (pngFile == -1)
perror ("Odpiranje predložene datoteke ni uspelo");
vrni EXIT_FAILURE;

/ * Preberite nekaj bajtov, da ugotovite, ali je datoteka PNG. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));

Podpis za branje je naslednji (izvleček iz strani z manu Linux):

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

Najprej argument fd predstavlja deskriptor datoteke. Ta koncept sem nekoliko razložil v članku o vilicah.  Deskriptor datoteke je int, ki predstavlja odprto datoteko, vtičnico, cev, FIFO, napravo, no, veliko stvari je, kjer je mogoče podatke brati ali zapisovati, na splošno na način, podoben toku. O tem bom podrobneje razložil v prihodnjem članku.

odprta funkcija je eden od načinov, kako sporočiti Linuxu: želim narediti nekaj z datoteko na tej poti, poiščite jo tam, kjer je in mi omogočite dostop do nje. Vrnil vam bo ta int, imenovan deskriptor datoteke, in zdaj, če želite s to datoteko karkoli storiti, uporabite to številko. Ko končate z datoteko, ne pozabite poklicati blizu, kot v primeru.

Zato morate za branje navesti to posebno številko. Potem je tu še argument buf. Tu bi morali navesti kazalec na matriko, kamor bo branje shranilo vaše podatke. Končno, štetje je, koliko bajtov bo prebral največ.

Vrnjena vrednost je tipa ssize_t. Čuden tip, kajne? Pomeni "podpisana velikost_t", v bistvu gre za dolg int. Vrne število bajtov, ki jih je uspešno prebral, ali -1, če je težava. Natančen vzrok težave lahko najdete v globalni spremenljivki errno, ki jo je ustvaril Linux in je definirana v . Če pa želite natisniti sporočilo o napaki, je uporaba perrorja boljša, saj v vašem imenu natisne napako.

V običajnih datotekah - in samo v tem primeru - branje bo vrnilo manj kot štetje le, če ste prišli do konca datoteke. Polje buf, ki ga navedete mora biti dovolj velik, da ustreza vsaj štetju bajtov, sicer se lahko vaš program sesuje ali ustvari varnostno napako.

Zdaj branje ni koristno samo za običajne datoteke in če želite začutiti njegove nadmoči - Da, vem, da ni v nobenem Marvelovem stripu, ima pa resnično moč - ga boste želeli uporabljati z drugimi tokovi, kot so cevi ali vtičnice. Oglejmo si to:

Posebne datoteke za Linux in branje sistemskega klica

Prebrano dejstvo deluje z različnimi datotekami, kot so cevi, vtičnice, FIFO-ji ali posebne naprave, kot so disk ali serijska vrata, zaradi česar je resnično zmogljivejše. Z nekaterimi prilagoditvami lahko počnete zares zanimive stvari. Prvič, to pomeni, da lahko dobesedno pišete funkcije, ki delujejo na datoteko, in jo namesto tega uporabite s cevjo. Zanimivo je posredovati podatke, ne da bi kdaj pritisnili na disk, kar zagotavlja najboljše delovanje.

Vendar to sproži tudi posebna pravila. Vzemimo primer branja vrstice s terminala v primerjavi z običajno datoteko. Ko pokličete branje v običajni datoteki, potrebuje Linux le nekaj milisekund, da dobi količino zahtevanih podatkov.

Toda ko gre za terminal, je to že druga zgodba: recimo, da zahtevate uporabniško ime. Uporabnik vtipka svoje uporabniško ime v terminal in pritisne Enter. Zdaj upoštevate moj zgornji nasvet in pokličete branje z velikim vmesnim pomnilnikom, na primer 256 bajtov.

Če bi branje delovalo kot pri datotekah, bi počakalo, da uporabnik vnese 256 znakov, preden se vrne! Vaš uporabnik bi večno čakal in nato na žalost ubil vašo aplikacijo. Zagotovo ni tisto, kar si želite, in imeli bi velik problem.

V redu, lahko ste prebrali en bajt naenkrat, vendar je ta rešitev zelo neučinkovita, kot sem vam že povedal. Delovati mora bolje kot to.

Toda razvijalci Linuxa so se menili drugače, da bi se izognili tej težavi:

  • Ko berete običajne datoteke, poskuša v največji možni meri prebrati štetje bajtov in po potrebi bo aktivno prejel bajte z diska.
  • Za vse druge vrste datotek se bo vrnila takoj, ko na voljo je nekaj podatkov in kvečjemu štetje bajtov:
    1. Za terminale je na splošno ko uporabnik pritisne tipko Enter.
    2. Za vtičnice TCP je takoj, ko računalnik nekaj prejme, ne glede na količino bajtov, ki jih dobi.
    3. Za FIFO ali pipe je na splošno enak znesek, kot je napisal drugi program, vendar lahko jedro Linuxa naenkrat prinese manj, če je to bolj priročno.

Tako lahko varno pokličete z medpomnilnikom 2 KiB, ne da bi ostali za vedno zaklenjeni. Upoštevajte, da se lahko tudi prekine, če aplikacija prejme signal. Ker lahko branje iz vseh teh virov traja sekunde ali celo ure - dokler se druga stran navsezadnje ne odloči za pisanje - če vas motijo ​​signali, lahko ustavite predolgo blokiranje.

Vendar ima to tudi pomanjkljivost: če želite s temi posebnimi datotekami natančno prebrati 2 KiB, boste morali večkrat preveriti povratno vrednost branja in večkrat prebrati klic. branje bo le redko zapolnilo celoten medpomnilnik. Če vaša aplikacija uporablja signale, boste morali z errno tudi preveriti, ali branje ni uspelo z -1, ker ga je signal prekinil.

Naj vam pokažem, kako zanimiva je uporaba te posebne lastnosti branja:

#define _POSIX_C_SOURCE 1 / * sigaction ni na voljo brez tega #define. * /
#include
#include
#include
#include
#include
#include
/ *
* isSignal pove, ali je branje syscall-a prekinilo signal.
*
* Vrne TRUE, če je bralni sistemski klic motil signal.
*
* Globalne spremenljivke: bere errno, definirano v errno.h
* /
unsigned int isSignal (const ssize_t readStatus)
vrnitev (readStatus == -1 && errno == EINTR);

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

/ *
* shouldRestartRead sporoča, kdaj je bralni sistemski klic prekinil a
* signalni dogodek ali ne, in ker je razlog za to "napako" prehoden, lahko
* varno znova zaženite prebrani klic.
*
* Trenutno preverja samo, ali je branje prekinilo signal, vendar to
* je mogoče izboljšati, da se preveri, ali je bilo prebrano ciljno število bajtov in ali je
* ni tako, vrnite TRUE, da znova preberete.
*
* /
unsigned int shouldRestartRead (const ssize_t readStatus)
return isSignal (readStatus);

/ *
* Potrebujemo prazen upravljalnik, ker bo syscall za branje prekinjen le, če je
* signal je obdelan.
* /
void emptyHandler (int prezrt)
vrnitev;

int main ()
/ * V nekaj sekundah. * /
const int alarmInterval = 5;
const struct sigaction emptySigaction = emptyHandler;
char lineBuf [256] = 0;
ssize_t readStatus = 0;
nepodpisan int waitTime = 0;
/ * Ne spreminjajte odmevanja, razen če natančno veste, kaj počnete. * /
sigakcija (SIGALRM, & emptySigaction, NULL);
alarm (alarmInterval);
fputs ("Vaše besedilo: \ n", stderr);
naredi
/ * Ne pozabite na '\ 0' * /
readStatus = branje (STDIN_FILENO, lineBuf, sizeof (lineBuf) - 1);
if (isSignal (readStatus))
waitTime + = alarmInterval;
alarm (alarmInterval);
fprintf (stderr, "% u sekund neaktivnosti ... \ n", čakalni čas);

while (shouldRestartRead (readStatus));
if (isSyscallSuccessful (readStatus))
/ * Končajte niz, da se izognete napaki, ko ga posredujete fprintf. * /
lineBuf [readStatus] = '\ 0';
fprintf (stderr, "Vtipkali ste znake% lu. Tu je tvoj niz: \ n% s \ n ", strlen (lineBuf),
lineBuf);
še
perror ("Branje iz stdina ni uspelo");
vrni EXIT_FAILURE;

vrni EXIT_SUCCESS;

Še enkrat, to je celotna aplikacija C, ki jo lahko sestavite in dejansko zaženete.

Naredi naslednje: prebere vrstico s standardnega vnosa. Vsakih 5 sekund pa natisne vrstico, ki uporabniku sporoči, da še ni vnosa.

Primer, če počakam 23 sekund, preden natipkam "Penguin":

$ alarm_read
Vaše besedilo:
5 sekund neaktivnosti ..
10 sekund neaktivnosti ..
15 sekund neaktivnosti ..
20 sekund neaktivnosti ..
Pingvin
Vtipkali ste 8 znakov. Tu je tvoj niz:
Pingvin

To je neverjetno koristno. Z njim lahko pogosto posodobite uporabniški vmesnik, da natisnete potek branja ali obdelave vaše aplikacije, ki jo izvajate. Uporablja se lahko tudi kot mehanizem časovne omejitve. Moti vas lahko tudi kateri koli drug signal, ki bi bil koristen za vašo aplikacijo. Kakorkoli, to pomeni, da je vaša aplikacija zdaj lahko odzivna, namesto da bi ostala za vedno.

Torej koristi odtehtajo zgoraj opisano pomanjkljivost. Če se sprašujete, ali bi morali v aplikaciji, ki običajno deluje z običajnimi datotekami, podpirati posebne datoteke - in tako kliče preberite v zanki - Rekel bi, da to storite, razen če se vam mudi, moje osebne izkušnje so pogosto dokazale, da lahko zamenjava datoteke s cevjo ali FIFO aplikacijo dobesedno z malo truda naredi veliko bolj uporabno. V internetu obstajajo celo predhodno izdelane funkcije C, ki za vas izvajajo to zanko: imenujejo se funkcije readn.

Zaključek

Kot lahko vidite, sta fread in branje videti podobna, ne. In le z nekaj spremembami o tem, kako branje deluje za razvijalca C, je branje veliko bolj zanimivo za oblikovanje novih rešitev za težave, s katerimi se srečujete med razvojem aplikacij.

Naslednjič vam bom povedal, kako deluje pisanje syscall-a, saj je branje kul, vendar je veliko boljše, če zmorem oboje. V tem času eksperimentirajte z branjem, spoznajte ga in želim vam srečno novo leto!

Kako prikazati prekrivanje zaslonskega menija v celozaslonskih aplikacijah in igrah za Linux
Igranje celozaslonskih iger ali uporaba aplikacij v celozaslonskem načinu brez motenj vam lahko odreže ustrezne sistemske informacije, ki so vidne na ...
Top 5 kartic za zajemanje iger
Vsi smo v YouTubu videli in oboževali pretakanje iger. PewDiePie, Jakesepticye in Markiplier so le nekateri izmed najboljših igralcev, ki so zaslužili...
Kako razviti igro na Linuxu
Pred desetletjem le malo uporabnikov Linuxa napoveduje, da bo njihov najljubši operacijski sistem nekoč priljubljena igralna platforma za komercialne ...