C Programiranje

Pročitajte Syscall Linux

Pročitajte Syscall Linux
Dakle, trebate čitati binarne podatke? Možda ćete htjeti čitati s FIFO-a ili utičnice? Vidite, možda ćete koristiti funkciju standardne knjižnice C, ali time nećete imati koristi od posebnih značajki koje pružaju Linux Kernel i POSIX. Na primjer, možda ćete htjeti koristiti vremenske ograničenja za čitanje u određeno vrijeme bez pribjegavanja anketiranju. Isto tako, možda ćete trebati nešto pročitati bez brige radi li se o posebnoj datoteci ili utičnici ili bilo čemu drugom. Vaš je jedini zadatak pročitati neke binarne sadržaje i unijeti ih u vašu aplikaciju. Tu blista pročitani syscall.

Pročitajte normalnu datoteku s Linux syscall-om

Najbolji način za početak rada s ovom funkcijom je čitanje normalne datoteke. Ovo je najjednostavniji način korištenja tog syscall-a i to iz razloga: nema toliko ograničenja kao druge vrste toka ili cijevi. Ako razmišljate o tome da je logika, kada čitate izlaz druge aplikacije, prije čitanja morate imati pripremljen izlaz i zato ćete trebati pričekati da aplikacija napiše ovaj izlaz.

Prvo, ključna razlika sa standardnom knjižnicom: Uopće nema međuspremnika. Svaki put kad pozovete funkciju čitanja, pozvat ćete Linux kernel, pa će ovo potrajati - gotovo je trenutačno ako ga nazovete jednom, ali može vas usporiti ako ga nazovete tisuće puta u sekundi. Za usporedbu, standardna će knjižnica spremiti ulaz za vas. Dakle, kad god nazovete čitanje, trebali biste pročitati više od nekoliko bajtova, već veliki međuspremnik poput nekoliko kilobajta - osim ako je ono što vam treba zaista malo bajtova, na primjer ako provjerite postoji li datoteka i nije li prazna.

To, međutim, ima prednost: svaki put kad nazovete čitanje, sigurni ste da ćete dobiti ažurirane podatke, ako bilo koja druga aplikacija trenutno modificira datoteku. To je posebno korisno za posebne datoteke poput datoteka u / proc ili / sys.

Vrijeme je da vam pokažem pravi primjer. Ovaj C program provjerava je li datoteka PNG ili nije. Da bi to učinio, čita datoteku navedenu u putu koji navedete u argumentu naredbenog retka i provjerava odgovara li prvih 8 bajtova PNG zaglavlju.

Evo koda:

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

 
/ *
* checkPngHeader provjerava odgovara li niz pngFileHeader PNG-u
* zaglavlje datoteke.
*
* Trenutno provjerava samo prvih 8 bajtova niza. Ako je niz manji
* manje od 8 bajtova, vraća se TOO_SHORT.
*
* pngFileHeaderLength mora održavati kengtu niza. Bilo koja nevaljana vrijednost
* može dovesti do nedefiniranog ponašanja, poput pada sustava.
*
* Vraća IS_PNG ako odgovara zaglavlju PNG datoteke. Ako postoji barem
* 8 bajtova u polju, ali to nije PNG zaglavlje, vraća se INVALID_HEADER.
*
* /
pngStatus_t checkPngHeader (const nepotpisani char * const pngFileHeader,
size_t pngFileHeaderLength) const nepotpisani znak očekujePngHeader [8] =
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A;
int i = 0;
 
if (pngFileHeaderLength < sizeof(expectedPngHeader))
vratiti TOO_SHORT;
 

 
za (i = 0; i < sizeof(expectedPngHeader); i++)
if (pngFileHeader [i] != očekivaniPngHeader [i])
vratiti INVALID_HEADER;
 


 
/ * Ako dosegne ovdje, svih prvih 8 bajtova odgovara PNG zaglavlju. * /
povratak IS_PNG;

 
int main (int argumentLength, char * argumentList [])
char * pngFileName = NULL;
nepotpisani znak pngFileHeader [8] = 0;
 
ssize_t readStatus = 0;
/ * Linux koristi broj za identificiranje otvorene datoteke. * /
int pngFile = 0;
pngStatus_t pngCheckResult;
 
if (argumentLength != 2)
fputs ("Ovaj program morate nazvati pomoću isPng vaše ime datoteke.\ n ", stderr);
povratak EXIT_FAILURE;
 

 
pngFileName = argumentList [1];
pngFile = otvoren (pngFileName, O_RDONLY);
 
if (pngFile == -1)
perror ("Otvaranje dostavljene datoteke nije uspjelo");
povratak EXIT_FAILURE;
 

 
/ * Pročitajte nekoliko bajtova da biste utvrdili je li datoteka PNG. * /
readStatus = čitanje (pngFile, pngFileHeader, sizeof (pngFileHeader));
 
if (isSyscallSuccessful (readStatus))
/ * Provjerite je li datoteka PNG jer je dobila podatke. * /
pngCheckResult = checkPngHeader (pngFileHeader, readStatus);
 
if (pngCheckResult == TOO_SHORT)
printf ("Datoteka% s nije PNG datoteka: prekratka je.\ n ", pngFileName);
 
inače ako (pngCheckResult == IS_PNG)
printf ("Datoteka% s je PNG datoteka!\ n ", pngFileName);
 
ostalo
printf ("Datoteka% s nije u PNG formatu.\ n ", pngFileName);
 

 
ostalo
perror ("Čitanje datoteke nije uspjelo");
povratak EXIT_FAILURE;
 

 
/ * Zatvorite datoteku ... * /
if (close (pngFile) == -1)
perror ("Zatvaranje pružene datoteke nije uspjelo");
povratak EXIT_FAILURE;
 

 
pngFile = 0;
 
povratak EXIT_SUCCESS;
 

Vidite, to je puni primjer koji radi i može se sastaviti. Ne ustručavajte se sami sastaviti i testirati, stvarno djeluje. Program biste trebali nazvati s terminala poput ovog:

./ isPng vaše ime datoteke

Sada se usredotočimo na sam poziv za čitanje:

pngFile = otvoreno (pngFileName, O_RDONLY);
if (pngFile == -1)
perror ("Otvaranje dostavljene datoteke nije uspjelo");
povratak EXIT_FAILURE;

/ * Pročitajte nekoliko bajtova da biste utvrdili je li datoteka PNG. * /
readStatus = čitanje (pngFile, pngFileHeader, sizeof (pngFileHeader));

Potpis za čitanje je sljedeći (izvučen iz Linux-ovih stranica):

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

Prvo, fd argument predstavlja deskriptor datoteke. Malo sam objasnio ovaj koncept u svom članku o vilicama.  Deskriptor datoteke je int koji predstavlja otvorenu datoteku, utičnicu, cijev, FIFO, uređaj, pa, puno je stvari u kojima se podaci mogu čitati ili pisati, uglavnom na način sličan toku. O tome ću detaljnije u sljedećem članku.

funkcija open jedan je od načina da se Linuxu kaže: želim raditi stvari s datotekom na tom putu, pronađite je tamo gdje je i dajte mi pristup njoj. Vratit će vam ovaj int koji se naziva deskriptor datoteke i sada, ako želite učiniti bilo što s ovom datotekom, upotrijebite taj broj. Ne zaboravite nazvati close kad završite s datotekom, kao u primjeru.

Zato morate navesti ovaj poseban broj za čitanje. Zatim tu je argument buf. Ovdje biste trebali pružiti pokazivač na niz u kojem će čitanje pohraniti vaše podatke. Konačno, računajte koliko će bajtova pročitati najviše.

Povratna vrijednost je tipa ssize_t. Čudan tip, zar ne? To znači "potpisana veličina_t", u osnovi je to dugačak int. Vraća broj bajtova koje je uspješno pročitao, ili -1 ako postoji problem. Točan uzrok problema možete pronaći u errno globalnoj varijabli koju je stvorio Linux, definiranoj u . No za ispis poruke o pogrešci bolje je koristiti perror jer se u vaše ime ispisuje pogrešno.

U normalnim datotekama - i samo u ovom slučaju - čitanje će vratiti manje od broja samo ako ste došli do kraja datoteke. Niz buf koji pružate mora biti dovoljno velik da stane barem na bajtove, inače se vaš program može srušiti ili stvoriti sigurnosnu pogrešku.

Čitanje sada nije korisno samo za normalne datoteke i ako želite osjetiti njegove super moći - Da, znam da to nije ni u jednom Marvelovom stripu, ali ima istinske moći - morat ćete ga koristiti s drugim strujama poput cijevi ili utičnica. Pogledajmo to:

Linux posebne datoteke i čita sistemski poziv

Čitana činjenica radi s različitim datotekama kao što su cijevi, utičnice, FIFO-ovi ili posebni uređaji poput diska ili serijskog porta ono što ga čini stvarno moćnijim. S nekim adaptacijama možete raditi zaista zanimljive stvari. Prvo, to znači da možete doslovno napisati funkcije koje rade na datoteci i umjesto toga ih koristiti s cijevi. Zanimljivo je prosljeđivati ​​podatke bez udaranja na disk, osiguravajući najbolje performanse.

Međutim, ovo pokreće i posebna pravila. Uzmimo primjer čitanja retka s terminala u usporedbi s normalnom datotekom. Kada nazovete čitanje u normalnoj datoteci, Linuxu treba samo nekoliko milisekundi da dobije količinu podataka koju tražite.

Ali što se tiče terminala, to je druga priča: recimo da tražite korisničko ime. Korisnik tipka u svoje korisničko ime i pritisnite Enter. Sada slijedite moj gornji savjet i pozivate čitanje s velikim međuspremnikom kao što je 256 bajtova.

Ako bi čitanje funkcioniralo kao kod datoteka, pričekalo bi da korisnik upiše 256 znakova prije nego što se vrati! Vaš bi korisnik zauvijek pričekao, a zatim nažalost ubio vašu aplikaciju. To sigurno nije ono što želite, a imali biste velik problem.

Dobro, mogli biste čitati po jedan bajt, ali ovo je rješenje užasno neučinkovito, kao što sam vam gore rekao. Sigurno djeluje bolje od toga.

No programeri Linuxa mislili su da čitaju drugačije kako bi izbjegli ovaj problem:

  • Kada čitate normalne datoteke, pokušava što je više moguće pročitati prebrojane bajtove i aktivno će dobiti bajtove s diska ako je to potrebno.
  • Za sve ostale vrste datoteka vratit će se što prije dostupni su neki podaci i najviše brojanje bajtova:
    1. Za terminale je općenito kada korisnik pritisne tipku Enter.
    2. Za TCP utičnice, čim vaše računalo nešto primi, nebitno je koliko bajtova dobiva.
    3. Za FIFO ili cijevi to je općenito jednak iznos kao što je napisala druga aplikacija, ali Linux kernel može istodobno isporučivati ​​manje ako je to prikladnije.

Tako možete sigurno nazvati sa svojim međuspremnikom od 2 KiB bez da zauvijek ostanete zaključani. Imajte na umu da se također može prekinuti ako aplikacija primi signal. Kako čitanje iz svih ovih izvora može trajati sekunde ili čak sate - sve dok druga strana ipak ne odluči pisati - ako vas signali prekidaju, možete prestati predugo ostati blokirani.

To, međutim, ima i nedostatak: kada želite točno pročitati 2 KiB s ovim posebnim datotekama, morat ćete provjeriti povratnu vrijednost čitanja i pozivanje pročitati više puta. čitanje će rijetko ispuniti cijeli vaš međuspremnik. Ako vaša aplikacija koristi signale, morat ćete provjeriti je li čitanje propalo s -1 jer ju je prekinuo signal, koristeći errno.

Dopustite mi da vam pokažem kako može biti zanimljivo koristiti ovo posebno svojstvo čitanja:

#define _POSIX_C_SOURCE 1 / * preusmjeravanje nije dostupno bez ovog #define. * /
#include
#include
#include
#include
#include
#include
/ *
* isSignal govori je li čitanje syscall-a prekinuo signal.
*
* Vraća TRUE ako je čitani syscall signal prekinut.
*
* Globalne varijable: čita errno definirano u errno.h
* /
nepotpisani int isSignal (const ssize_t readStatus)
povratak (readStatus == -1 && errno == EINTR);

nepotpisani int isSyscallSuccessful (const ssize_t readStatus)
vrati readStatus> = 0;

/ *
* shouldRestartRead govori kada je čitani syscall prekinut a
* signalni događaj ili ne, a s obzirom na to da je razlog "pogreške" prijelazan, možemo
* sigurno ponovno pokrenite pročitani poziv.
*
* Trenutno provjerava samo je li čitanje prekinuo signal, ali da
* može se poboljšati kako bi se provjerilo je li pročitan ciljni broj bajtova i je li
* nije slučaj, vratite TRUE za ponovno čitanje.
*
* /
nepotpisani int shouldRestartRead (const ssize_t readStatus)
return isSignal (readStatus);

/ *
* Potreban nam je prazan rukovatelj jer će se čitani syscall prekinuti samo ako
* obrađuje se signal.
* /
void emptyHandler (int zanemaren)
povratak;

int main ()
/ * To je za nekoliko sekundi. * /
const int alarmInterval = 5;
const struct sigaction emptySigaction = emptyHandler;
char lineBuf [256] = 0;
ssize_t readStatus = 0;
nepotpisano int vrijeme čekanja = 0;
/ * Ne mijenjajte prepisivanje, osim ako točno znate što radite. * /
sigakcija (SIGALRM, & emptySigaction, NULL);
alarm (alarmInterval);
fputs ("Vaš tekst: \ n", stderr);
čini
/ * Ne zaboravite na '\ 0' * /
readStatus = čitanje (STDIN_FILENO, lineBuf, sizeof (lineBuf) - 1);
if (isSignal (readStatus))
vrijeme čekanja + = alarmInterval;
alarm (alarmInterval);
fprintf (stderr, "% u sekundi neaktivnosti ... \ n", vrijeme čekanja);

while (shouldRestartRead (readStatus));
if (isSyscallSuccessful (readStatus))
/ * Prekinite niz da biste izbjegli bug kada ga pružate fprintf. * /
lineBuf [readStatus] = '\ 0';
fprintf (stderr, "Upisali ste znakove% lu. Evo vašeg niza: \ n% s \ n ", strlen (lineBuf),
lineBuf);
ostalo
perror ("Čitanje sa stdina nije uspjelo");
povratak EXIT_FAILURE;

povratak EXIT_SUCCESS;

Još jednom, ovo je puna C aplikacija koju možete kompilirati i stvarno pokrenuti.

Čini sljedeće: čita redak sa standardnog unosa. Međutim, svakih 5 sekundi ispisuje redak koji poručuje korisniku da još nijedan unos nije dat.

Primjer ako pričekam 23 sekunde prije nego što natipkam "Penguin":

$ alarm_read
Tvoj tekst:
5 sekundi neaktivnosti ..
10 sekundi neaktivnosti ..
15 sekundi neaktivnosti ..
20 sekundi neaktivnosti ..
Pingvin
Upisali ste 8 znakova. Evo vašeg niza:
Pingvin

To je nevjerojatno korisno. Može se koristiti za često ažuriranje korisničkog sučelja za ispis napretka čitanja ili obrade vaše aplikacije koju radite. Može se koristiti i kao mehanizam vremenskog ograničenja. Također vas može prekinuti bilo koji drugi signal koji bi mogao biti koristan za vašu aplikaciju. Svejedno, to znači da vaša aplikacija sada može reagirati umjesto da zauvijek ostane zaglavljena.

Dakle, prednosti premašuju gore opisani nedostatak. Ako se pitate trebate li podržavati posebne datoteke u aplikaciji koja normalno radi s normalnim datotekama - i tako zove čitati u petlji - Rekao bih da to učinite, osim ako se žurite, moje osobno iskustvo često je pokazalo da zamjena datoteke cijevi ili FIFO-om doslovno može učiniti aplikaciju mnogo korisnijom uz male napore. Na Internetu postoje čak i unaprijed izrađene funkcije C koje implementiraju tu petlju umjesto vas: one se nazivaju readn funkcije.

Zaključak

Kao što vidite, fread i read možda izgledaju slično, nisu. A uz samo nekoliko promjena u načinu na koji čitanje funkcionira za programera C, čitanje je mnogo zanimljivije za dizajniranje novih rješenja problema s kojima se susrećete tijekom razvoja aplikacije.

Sljedeći put ću vam reći kako funkcionira pisanje syscall-a, jer je čitanje super, ali puno je bolje moći raditi oboje. U međuvremenu eksperimentirajte s čitanjem, upoznajte ga i želim vam sretnu Novu godinu!

Kontrolirajte i upravljajte kretanjem miša između više monitora u sustavu Windows 10
Dvostruki zaslon upravitelja miša omogućuje vam kontrolu i konfiguriranje kretanja miša između više monitora, usporavajući njegovo kretanje blizu gran...
WinMouse vam omogućuje prilagodbu i poboljšanje kretanja pokazivača miša na Windows računalu
Ako želite poboljšati zadane funkcije pokazivača miša, upotrijebite besplatni program WinMouse. Dodaje više značajki koje će vam pomoći da na najbolji...
Lijevi gumb miša ne radi u sustavu Windows 10
Ako s prijenosnim računalom ili stolnim računalom koristite namjenski miš, ali lijevi gumb miša ne radi u sustavu Windows 10/8/7 iz nekog razloga evo ...