C programozás

Olvassa el a Syscall Linuxot

Olvassa el a Syscall Linuxot
Tehát el kell olvasnia a bináris adatokat? Érdemes lehet olvasni egy FIFO-ból vagy egy socketből? Láthatja, hogy használhatja a C szabványos könyvtár funkciót, de ezzel nem fogja kihasználni a Linux Kernel és a POSIX speciális szolgáltatásait. Például érdemes időtúllépéseket használni egy adott időpontban történő olvasáshoz, anélkül, hogy közvélemény-kutatást igényelne. Valamint előfordulhat, hogy gondozás nélkül el kell olvasnia valamit, ha ez egy speciális fájl vagy foglalat vagy bármi más. Az egyetlen feladata néhány bináris tartalom elolvasása és az alkalmazásba történő beolvasása. Ott ragyog az olvasott syscall.

Olvasson el egy normál fájlt Linux syscallal

A funkció használatának legjobb módja egy normál fájl elolvasása. Ez a syscall legegyszerűbb módja, és egy okból: nincs annyi korlátozása, mint más típusú folyamnak vagy csőnek. Ha belegondolunk, hogy ez logikus, akkor amikor egy másik alkalmazás kimenetét elolvassuk, készen kell állnunk egy kimenetre, mielőtt elolvasnánk, és ezért meg kell várnunk, amíg az alkalmazás megírja ezt a kimenetet.

Először is, kulcsfontosságú különbség a szokásos könyvtárral szemben: Egyáltalán nincs pufferelés. Minden alkalommal, amikor meghívja az olvasási funkciót, felhívja a Linux kernelt, és ez időbe fog telni - szinte egyszeri, ha egyszer felhívod, de lassíthat, ha másodpercek alatt több ezerszer hívod. Összehasonlításképpen, a standard könyvtár pufferolja a bemenetet az Ön számára. Tehát, amikor az olvasást hívja, több bájtnál többet kell olvasnia, inkább egy nagy puffert, például néhány kilobájtot - kivéve, ha valóban kevés bájtra van szükséged, például ha megvizsgálod, hogy létezik-e egy fájl és nem üres-e.

Ennek azonban van egy előnye: minden alkalommal, amikor az olvasást hívja, biztosan megkapja a frissített adatokat, ha bármely más alkalmazás módosítja a fájlt. Ez különösen hasznos olyan speciális fájloknál, mint például a / proc vagy a / sys fájlokban.

Ideje valódi példával megmutatni. Ez a C program ellenőrzi, hogy a fájl PNG-e vagy sem. Ehhez beolvassa a parancssori argumentumban megadott útvonalban megadott fájlt, és ellenőrzi, hogy az első 8 bájt megfelel-e egy PNG fejlécnek.

Itt van a kód:

#include
#include
#include
#include
#include
#include
#include
 
typedef enum
IS_PNG,
TÚL RÖVID,
INVALID_HEADER
pngStatus_t;
 
unsigned int isSyscallSuccessful (const ssize_t readStatus)
return readStatus> = 0;
 

 
/ *
* A checkPngHeader ellenőrzi, hogy a pngFileHeader tömb megfelel-e egy PNG-nek
* fájl fejléc.
*
* Jelenleg csak a tömb első 8 bájtját ellenőrzi. Ha a tömb kisebb
* mint 8 bájt, a TOO_SHORT értéket adja vissza.
*
* pngFileHeaderLengthnek meg kell felelnie a tye tömb hosszának. Bármely érvénytelen érték
* meghatározhatatlan viselkedéshez vezethet, például összeomlik az alkalmazás.
*
* Az IS_PNG értéket adja vissza, ha megfelel egy PNG fájl fejlécének. Ha van legalább
* 8 bájt a tömbben, de ez nem PNG fejléc, az INVALID_HEADER értéket adja vissza.
*
* /
pngStatus_t checkPngHeader (const unsigned char * const pngFileHeader,
size_t pngFileHeaderLength) const aláíratlan karakter várhatóPngHeader [8] =
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A;
int i = 0;
 
if (pngFileHeaderLength < sizeof(expectedPngHeader))
visszatérés TOO_SHORT;
 

 
mert (i = 0; i < sizeof(expectedPngHeader); i++)
if (pngFileHeader [i] != várhatóPngHeader [i])
return INVALID_HEADER;
 


 
/ * Ha ideér, az első 8 bájt megfelel a PNG fejlécének. * /
return IS_PNG;

 
int main (int argumentumHossz, char * argumentLista [])
char * pngFileName = NULL;
unsigned char pngFileHeader [8] = 0;
 
ssize_t readStatus = 0;
/ * A Linux számot használ a nyitott fájl azonosítására. * /
int pngFájl = 0;
pngStatus_t pngCheckResult;
 
ha (argumentumHossz != 2)
fputs ("Ezt a programot az isPng a fájlnév használatával kell meghívnod.\ n ", stderr);
return EXIT_FAILURE;
 

 
pngFileName = argumentList [1];
pngFile = nyitott (pngFileName, O_RDONLY);
 
if (pngFájl == -1)
perror ("A megadott fájl megnyitása nem sikerült");
visszatér EXIT_FAILURE;
 

 
/ * Olvasson el néhány bájtot annak megállapításához, hogy a fájl PNG-e. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));
 
if (isSyscallSuccessful (readStatus))
/ * Ellenőrizze, hogy a fájl PNG-e, mióta megkapta az adatokat. * /
pngCheckResult = checkPngHeader (pngFileHeader, readStatus);
 
if (pngCheckResult == TOO_SHORT)
printf ("A% s fájl nem PNG fájl: túl rövid.\ n ", pngFileName);
 
else if (pngCheckResult == IS_PNG)
printf ("A% s fájl egy PNG fájl!\ n ", pngFileName);
 
más
printf ("A (z)% s fájl nem PNG formátumú.\ n ", pngFileName);
 

 
más
perror ("A fájl olvasása nem sikerült");
visszatér EXIT_FAILURE;
 

 
/ * Bezárja a fájlt ... * /
if (bezár (pngFile) == -1)
perror ("A megadott fájl bezárása nem sikerült");
visszatér EXIT_FAILURE;
 

 
pngFájl = 0;
 
visszatér EXIT_SUCCESS;
 

Látja, ez egy teljes értékű, működő és fordítható példa. Ne habozzon összeállítani és tesztelni, valóban működik. Hívja a programot egy ilyen terminálról:

./ isPng a fájlnév

Most összpontosítsunk magára az olvasási hívásra:

pngFile = nyitott (pngFileName, O_RDONLY);
if (pngFájl == -1)
perror ("A megadott fájl megnyitása nem sikerült");
visszatér EXIT_FAILURE;

/ * Olvasson el néhány bájtot annak megállapításához, hogy a fájl PNG-e. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));

Az olvasott aláírás a következő (kivonva a Linux man-oldalairól):

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

Először az fd argumentum képviseli a fájlleírót. Kicsit elmagyaráztam ezt a fogalmat a villás cikkemben.  A fájlleíró egy nyitott fájlt, socketet, csövet, FIFO-t, eszközt képviselő int. Nos, sok minden olyan, ahol az adatok olvashatók vagy írhatók, általában patakszerű módon. Erről egy későbbi cikkemben részletesebben foglalkozom.

az open funkció az egyik módja annak, hogy elmondjam a Linuxnak: Az adott útvonalon lévő fájlokkal szeretnék dolgokat csinálni, kérjük, keresse meg, hol van, és adjon hozzáférést hozzá. Visszaadja ezt az int nevű fájlleírót, és most, ha bármit is szeretne csinálni ezzel a fájllal, használja ezt a számot. Ne felejtsen el bezárni, ha elkészült a fájllal, mint a példában.

Tehát meg kell adnia ezt a különleges számot az olvasáshoz. Aztán ott van a buf argumentum. Itt kell megadnia egy mutatót a tömbhöz, ahol az read az adatokat tárolja. Végül számolja meg, hogy legfeljebb hány bájtot fog olvasni.

A visszatérési érték ssize_t típusú. Furcsa típus, nem igaz? Ez azt jelenti, hogy „aláírt méret_t”, alapvetően hosszú int. Visszaadja a sikeresen elolvasott bájtok számát, vagy -1-et, ha probléma adódik. A probléma pontos okát a Linux által létrehozott errno globális változóban találhatja meg, amelyet itt határoztak meg . De egy hibaüzenet kinyomtatásához jobb a perror használata, mivel az errno-t nyomtat az Ön nevében.

Normál fájlokban - és csak ebben az esetben a read csak akkor lesz kevesebb, mint a count, ha elérte a fájl végét. Az általad megadott buf tömb kell legyen elég nagy ahhoz, hogy legalább a bájtokat megszámolja, különben a program összeomolhat vagy biztonsági hibát hozhat létre.

Most az olvasás nemcsak a normál fájloknál hasznos, és ha meg akarja érezni a szuperhatalmát - Igen, tudom, hogy nem szerepel a Marvel képregényeiben, de valódi erőkkel bír - más streamekkel, például csövekkel vagy aljzatokkal szeretné használni. Vessünk egy pillantást erre:

Linux speciális fájlok és rendszerhívás olvasása

Az olvasott tény különféle fájlokkal, például csövekkel, aljzatokkal, FIFO-kkal vagy speciális eszközökkel, például lemezzel vagy soros porttal működik, ez teszi igazán hatékonyabbá. Néhány adaptációval igazán érdekes dolgokat tehet. Először is ez azt jelenti, hogy szó szerint írhat egy függvényt egy fájlra, és ehelyett egy csővel használhatja. Érdekes az adatok átadása anélkül, hogy a lemezre kerülne, és ezzel a legjobb teljesítmény érhető el.

Ez azonban speciális szabályokat is kivált. Vegyünk egy példát arra, hogy a terminálról egy sort leolvasunk egy normál fájlhoz képest. Ha normál fájlon hívja az olvasott fájlt, akkor csak néhány milliszekundumra van szüksége a Linuxhoz a kért adatmennyiség megszerzéséhez.

De ami a terminált illeti, az egy másik történet: tegyük fel, hogy felhasználónevet kér. A felhasználó beírja a felhasználónevét és nyomja meg az Enter billentyűt. Most követi a fenti tanácsomat, és nagy pufferrel, például 256 bájttal hívja az olvasást.

Ha az olvasás úgy működött, mint a fájlokkal, megvárja, amíg a felhasználó 256 karaktert beír, mielőtt visszatér! A felhasználó örökké várna, majd sajnos megöli az alkalmazását. Természetesen nem ez az, amit akarsz, és nagy problémád lenne.

Rendben, olvashat egy-egy bájtot, de ez a megoldás rettenetesen nem hatékony, ahogy fentebb mondtam. Ennél jobban kell működnie.

De a Linux fejlesztői másként gondolták ezt a problémát:

  • Amikor normál fájlokat olvas, megpróbálja a lehető legtöbbet olvasni a bájtok számát, és ha szükséges, akkor aktívan megkapja a bájtokat a lemezről.
  • Minden más fájltípus esetén visszatér amint van néhány adat elérhető és leginkább bájtok száma:
    1. A terminálok esetében az általában amikor a felhasználó megnyomja az Enter billentyűt.
    2. A TCP aljzatok esetében nem számít, mennyi bájtot kap, amint a számítógép kap valamit.
    3. A FIFO vagy a pipe-ok esetében általában ugyanannyi, mint amit a másik alkalmazás írt, de a Linux kernel kevesebbet tud egyszerre szállítani, ha ez kényelmesebb.

Így biztonságosan felhívhatja a 2 KiB-os puffert anélkül, hogy örökre bezárva maradna. Ne feledje, hogy akkor is megszakadhat, ha az alkalmazás jelet kap. Mivel ezekből a forrásokból az olvasás másodperceket vagy akár órákat is igénybe vehet - míg végül a másik oldal úgy dönt, hogy ír - jelekkel megszakítva lehetővé válik a túl sokáig blokkolt állapot megállítása.

Ennek azonban van egy hátránya is: ha pontosan ki akarja olvasni a 2 KiB-ot ezekkel a speciális fájlokkal, akkor ellenőriznie kell a read visszatérési értékét, és többször kell hívnia az olvasást. az olvasás ritkán tölti ki az egész puffert. Ha az alkalmazás jeleket használ, akkor azt is ellenőriznie kell, hogy az olvasás sikertelen-e -1-vel, mert egy jel szakította meg, az errno használatával.

Hadd mutassam meg, milyen érdekes lehet használni ezt a különleges tulajdonságot:

#define _POSIX_C_SOURCE 1 / * sigaction nem érhető el a #define nélkül. * /
#include
#include
#include
#include
#include
#include
/ *
* az isSignal megmondja, hogy az olvasott syscallt megszakította-e egy jel.
*
* IGAZ értéket ad vissza, ha az olvasott rendszerhívást egy jel megszakította.
*
* Globális változók: az errno-ban definiált errno-t olvassa.h
* /
unsigned int isSignal (const ssize_t readStatus)
return (readStatus == -1 && errno == EINTR);

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

/ *
A * shouldRestartRead azt mondja meg, ha az olvasott syscallt megszakította a
* jelez-e eseményt, vagy sem, és mivel ez a "hiba" oka átmeneti, tudjuk
* biztonságosan indítsa újra az olvasási hívást.
*
* Jelenleg csak azt ellenőrzi, hogy az olvasást megszakította-e egy jel, de azt
* tovább lehetne javítani annak ellenőrzésére, hogy a cél bájtok száma beolvasásra került-e, és ha igen
* nem ez a helyzet, adja vissza az IGAZ elemet, hogy újra olvassa.
*
* /
unsigned int shouldRestartRead (const ssize_t readStatus)
return isSignal (readStatus);

/ *
* Szükségünk van egy üres kezelőre, mivel az olvasott syscall csak akkor szakad meg, ha a
* a jelet kezeljük.
* /
void emptyHandler (int figyelmen kívül hagyva)
Visszatérés;

int main ()
/ * Másodpercben van. * /
const int alarmInterval = 5;
const struct sigaction emptySigaction = emptyHandler;
char lineBuf [256] = 0;
ssize_t readStatus = 0;
aláíratlan int várakozási idő = 0;
/ * Ne módosítsa a szignációt, kivéve, ha pontosan tudja, mit csinál. * /
sigaction (SIGALRM, & emptySigaction, NULL);
riasztás (alarmInterval);
fputs ("A szöveged: \ n", stderr);
csináld
/ * Ne felejtsd el a '\ 0' * /
readStatus = olvasás (STDIN_FILENO, lineBuf, sizeof (lineBuf) - 1);
if (isSignal (readStatus))
waitTime + = alarmInterval;
riasztás (alarmInterval);
fprintf (stderr, "inaktivitás% u sec-a… \ n", waitTime);

while (shouldRestartRead (readStatus));
if (isSyscallSuccessful (readStatus))
/ * Szüntesse meg a karakterláncot, hogy elkerülje a hibát, amikor az fprintf-nek adja. * /
lineBuf [readStatus] = '\ 0';
fprintf (stderr, "% lu karaktereket írt be. Itt van a karakterlánc: \ n% s \ n ", strlen (lineBuf),
lineBuf);
más
perror ("Nem sikerült olvasni a stdin-ből");
visszatér EXIT_FAILURE;

visszatér EXIT_SUCCESS;

Ez ismét egy teljes C alkalmazás, amelyet lefordíthat és ténylegesen futtathat.

A következőket teszi: egy sort olvas be a standard bemenetből. 5 másodpercenként azonban kinyomtat egy sort, amely közli a felhasználóval, hogy még nem adtak meg bemenetet.

Példa, ha 23 másodpercet várok a „Penguin” beírása előtt:

$ alarm_read
A te szöveged:
5 másodperc inaktivitás…
10 másodperc inaktivitás…
15 másodperc inaktivitás…
20 másodperc inaktivitás…
Pingvin
8 karaktert írt be. Itt van a karakterlánc:
Pingvin

Ez hihetetlenül hasznos. Használható a felhasználói felület gyakran frissítésére, hogy kinyomtassa az olvasás vagy az alkalmazás folyamatának előrehaladását. Időkorlátos mechanizmusként is használható. Azt is megzavarhatja bármilyen más jel, amely hasznos lehet az alkalmazásához. Egyébként ez azt jelenti, hogy az alkalmazás mostantól reagálhat, ahelyett, hogy örökre elakadna.

Tehát az előnyök felülmúlják a fent leírt hátrányt. Ha kíváncsi arra, hogy támogatnia kell-e speciális fájlokat egy olyan alkalmazásban, amely normál fájlokkal működik - és így hív olvas hurokban - Azt mondanám, hogy tedd meg, kivéve, ha sietsz, személyes tapasztalataim gyakran bebizonyították, hogy ha egy fájlt pipával vagy FIFO-val cserélsz, akkor szó szerint egy alkalmazás sokkal hasznosabbá válhat kis erőfeszítésekkel. Még az interneten is vannak olyan előre elkészített C funkciók, amelyek megvalósítják ezt a kört az Ön számára: readn függvényeknek hívják.

Következtetés

Amint láthatja, a rettegés és az olvasás hasonló lehet, de nem az. És csak kevés változtatással az olvasás működésével a C fejlesztő számára, az olvasás sokkal érdekesebb, ha új megoldásokat tervezünk az alkalmazások fejlesztése során felmerülő problémákra.

Legközelebb elmondom, hogyan működik a syscall írása, mivel az olvasás klassz, de mindkettőt sokkal jobb tudni. Addig is kísérletezz az olvasással, ismerd meg és boldog új évet kívánok neked!

Az OSD-fedvény megjelenítése teljes képernyős Linux-alkalmazásokban és játékokban
Teljes képernyős játékok lejátszása vagy alkalmazások figyelemelterelés nélküli, teljes képernyős módban történő használata elvághatja a panelen vagy ...
Az 5 legjobb játékrögzítő kártya
Mindannyian láttuk és szerettük a YouTube-on a streaming játékokat. A PewDiePie, a Jakesepticye és a Markiplier csak néhány a legnépszerűbb játékosok ...
Hogyan lehet játékot fejleszteni Linuxon
Egy évtizeddel ezelőtt nem sok Linux-felhasználó jósolta, hogy kedvenc operációs rendszerük egy napon a videojátékok népszerű játékplatformja lesz. El...