C programavimas

Perskaitykite „Syscall Linux“

Perskaitykite „Syscall Linux“
Taigi reikia skaityti dvejetainius duomenis? Galbūt norėsite skaityti iš FIFO ar lizdo? Matote, galite naudoti C standartinės bibliotekos funkciją, tačiau tai padarę jums nebus naudingos specialios „Linux Kernel“ ir „POSIX“ funkcijos. Pvz., Galite naudoti skirtąjį laiką skaitydami tam tikru laiku, nenaudodami rinkimų. Be to, gali tekti ką nors perskaityti nesirūpinant, jei tai yra specialus failas, lizdas ar dar kas nors. Vienintelė užduotis yra perskaityti kai kuriuos dvejetainius failus ir juos gauti savo programoje. Štai kur šviečia perskaitytas sisteminis skambutis.

Perskaitykite įprastą failą naudodami „Linux“ sistemos skambutį

Geriausias būdas pradėti dirbti su šia funkcija yra skaityti įprastą failą. Tai paprasčiausias būdas naudoti tą sisteminį skambutį ir dėl tam tikrų priežasčių: jis neturi tiek apribojimų, kaip kitų tipų srautas ar vamzdis. Jei galvojate apie tai, kas logiška, skaitydami kitos programos išvestį, prieš skaitydami turite turėti tam tikrą išvestį, todėl turėsite palaukti, kol ši programa parašys.

Pirma, pagrindinis skirtumas su standartine biblioteka: nėra jokio buferio. Kiekvieną kartą, kai skambinate skaitymo funkcija, paskambinsite į „Linux“ branduolį, taigi tam reikės laiko - tai beveik akimirksniu, jei paskambinsite vieną kartą, bet gali jus sulėtinti, jei skambinsite tūkstančius kartų per sekundę. Palyginimui, standartinė biblioteka buferiuos jums įvestį. Taigi, kai skambinate perskaitytu, turėtumėte perskaityti daugiau nei kelis baitus, bet didesnį buferį, pavyzdžiui, kelis kilobaitus - išskyrus tuos atvejus, kai jums reikia tikrai kelių baitų, pavyzdžiui, jei patikrinate, ar failas yra ir ar jis nėra tuščias.

Tačiau tai turi pranašumų: kiekvieną kartą, kai skambinate skaityti, esate tikri, kad gausite atnaujintus duomenis, jei kuri nors kita programa šiuo metu pakeis failą. Tai ypač naudinga specialiems failams, tokiems kaip / proc arba / sys.

Laikas parodyti jums tikrą pavyzdį. Ši C programa patikrina, ar failas yra PNG, ar ne. Norėdami tai padaryti, jis nuskaito failą, nurodytą kelyje, kurį pateikiate komandinės eilutės argumente, ir patikrina, ar pirmieji 8 baitai atitinka PNG antraštę.

Štai kodas:

# įtraukti
# įtraukti
# įtraukti
# įtraukti
# įtraukti
# įtraukti
# įtraukti
 
typedef enum
IS_PNG,
PER TRUMPAS,
INVALID_HEADER
pngStatus_t;
 
nepasirašytas int isSyscallSuccessful (const ssize_t readStatus)
grąžinti readStatus> = 0;
 

 
/ *
* checkPngHeader tikrina, ar masyvas pngFileHeader atitinka PNG
* failo antraštė.
*
* Šiuo metu jis tikrina tik pirmuosius 8 masyvo baitus. Jei masyvas yra mažesnis
* daugiau nei 8 baitai, grąžinama TOO_SHORT.
*
* pngFileHeaderLength turi išlaikyti tae masyvo ilgį. Bet kokia neteisinga vertė
* gali sukelti neapibrėžtą elgesį, pvz., programos gedimą.
*
* Pateikia IS_PNG, jei jis atitinka PNG failo antraštę. Jei yra bent jau
* 8 baitai masyve, bet tai nėra PNG antraštė, grąžinama INVALID_HEADER.
*
* /
(const nepasirašyta char * const pngFileHeader. pngStatus_t checkPngHeader,
size_t pngFileHeaderLength) const nepasirašyta simbolis laukiamaPngHeader [8] =
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A;
int i = 0;
 
jei (pngFileHeaderLength < sizeof(expectedPngHeader))
grįžti TOO_SHORT;
 

 
už (i = 0; i < sizeof(expectedPngHeader); i++)
jei (pngFileHeader [i] != numatomasPngHeader [i])
grąžinti INVALID_HEADER;
 


 
/ * Jei jis pasiekia čia, visi pirmieji 8 baitai atitinka PNG antraštę. * /
grįžti IS_PNG;

 
int main (int argumentasLength, char * argumentList [])
char * pngFileName = NULL;
nepasirašytas simbolis pngFileHeader [8] = 0;
 
ssize_t readStatus = 0;
/ * „Linux“ naudoja skaičių atidarytam failui identifikuoti. * /
int pngFailas = 0;
pngStatus_t pngCheckResult;
 
jei (argumentasLength != 2)
fputs ("Jūs turite iškviesti šią programą naudodami isPng jūsų failo pavadinimas.\ n ", stderr);
grįžti EXIT_FAILURE;
 

 
pngFileName = argumentų sąrašas [1];
pngFile = atidaryti (pngFileName, O_RDONLY);
 
jei (pngFailas == -1)
klaida ("Nepavyko atidaryti pateikto failo");
grįžti EXIT_FAILURE;
 

 
/ * Perskaitykite keletą baitų, kad nustatytumėte, ar failas yra PNG. * /
readStatus = skaityti (pngFile, pngFileHeader, sizeof (pngFileHeader));
 
if (isSyscallSuccessful (readStatus))
/ * Patikrinkite, ar failas yra PNG, nes jis gavo duomenis. * /
pngCheckResult = checkPngHeader (pngFileHeader, readStatus);
 
jei (pngCheckResult == TOO_SHORT)
printf ("Failas% s nėra PNG failas: jis per trumpas.\ n ", pngFileName);
 
else if (pngCheckResult == IS_PNG)
printf ("Failas% s yra PNG failas!\ n ", pngFileName);
 
Kitas
printf ("Failas% s nėra PNG formato.\ n ", pngFileName);
 

 
Kitas
klaida („Failo skaityti nepavyko“);
grįžti EXIT_FAILURE;
 

 
/ * Uždarykite failą ... * /
jei (uždaryti (pngFile) == -1)
klaida ("Nepavyko uždaryti pateikto failo");
grįžti EXIT_FAILURE;
 

 
pngFailas = 0;
 
grąžinti EXIT_SUCCESS;
 

Žiūrėk, tai visiškai pravertas, veikiantis ir parengiamas pavyzdys. Nesivaržykite patys sudaryti ir išbandyti, tai tikrai veikia. Turėtumėte iškviesti programą iš tokio terminalo:

./ isPng jūsų failo vardas

Dabar sutelkime dėmesį į patį skaitytą skambutį:

pngFile = atidaryti (pngFileName, O_RDONLY);
jei (pngFailas == -1)
klaida ("Nepavyko atidaryti pateikto failo");
grįžti EXIT_FAILURE;

/ * Perskaitykite keletą baitų, kad nustatytumėte, ar failas yra PNG. * /
readStatus = skaityti (pngFile, pngFileHeader, sizeof (pngFileHeader));

Perskaitytas parašas yra toks (išgautas iš „Linux“ vadovo puslapių):

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

Pirma, fd argumentas nurodo failo deskriptorių. Šiek tiek paaiškinau šią sampratą savo šakutės straipsnyje.  Failo deskriptorius yra int, nurodantis atvirą failą, lizdą, vamzdį, FIFO, įrenginį. Na, tai yra daug dalykų, kur duomenis galima skaityti ar rašyti, paprastai panašiu į srautą. Apie tai išsamiau nagrinėsiu būsimame straipsnyje.

„open“ funkcija yra vienas iš būdų pasakyti „Linux“: noriu atlikti veiksmus su tuo keliu esančiu failu, raskite jį ten, kur jis yra, ir suteikite man prieigą prie jo. Tai jums grąžins šį tarptautinį vadinamąjį failo aprašą, o dabar, jei norite ką nors padaryti su šiuo failu, naudokite tą skaičių. Nepamirškite skambinti uždaryti, kai baigsite rinkmeną, kaip pavyzdyje.

Taigi, norėdami perskaityti, turite pateikti šį specialų numerį. Tada yra „buf“ argumentas. Čia turėtumėte pateikti rodyklę į masyvą, kuriame skaitant bus saugomi jūsų duomenys. Galiausiai suskaičiuokite, kiek baitų jis skaitys.

Grąžinimo vertė yra ssize_t tipo. Keistas tipas, ar ne? Tai reiškia „pasirašytas dydis_t“, iš esmės tai ilgas int. Jis grąžina sėkmingai perskaitytų baitų skaičių arba -1, jei yra problema. Tikslios problemos priežastį galite rasti „Linux“ sukurtame „errno“ kintamajame, apibrėžtame . Bet norint atsispausdinti klaidos pranešimą, geriau naudoti klaidą, nes ji išspausdina „errno“ jūsų vardu.

Įprastose bylose - ir tik Šiuo atveju - perskaičius bus grąžinta mažiau nei bus tik tada, jei pasieksite failo pabaigą. Jūsų pateiktas buf masyvas turi būti pakankamai didelis, kad tilptų bent jau baitai, kitaip programa gali sugesti arba sukurti saugos klaidą.

Dabar skaityti naudinga ne tik įprastiems failams ir norint pajusti jo galias - Taip, aš žinau, kad tai nėra nė vieno „Marvel“ komikso, bet jis turi tikrų galių - norėsite jį naudoti su kitais srautais, tokiais kaip vamzdžiai ar kištukiniai lizdai. Pažvelkime į tai:

Specialūs „Linux“ failai ir sistemos skambučio skaitymas

Perskaitytas faktas veikia su įvairiais failais, tokiais kaip vamzdžiai, lizdai, FIFO ar specialūs įtaisai, pvz., Diskas ar nuoseklusis prievadas, daro jį tikrai galingesnį. Su kai kuriomis adaptacijomis galite padaryti tikrai įdomių dalykų. Pirma, tai reiškia, kad galite pažodžiui parašyti funkcijas, susijusias su byla, ir naudoti ją su pypke. Įdomu perduoti duomenis niekada nepasiekus disko, užtikrinant geriausią našumą.

Tačiau tai taip pat sukelia specialias taisykles. Paimkime eilutės iš terminalo skaitymo pavyzdį, palyginti su įprastu failu. Kai skambinate skaityti įprastu failu, norint gauti jūsų prašomą duomenų kiekį, „Linux“ reikia tik kelių milisekundžių.

Bet kalbant apie terminalą, tai kita istorija: tarkime, jūs paprašote vartotojo vardo. Vartotojas įveda savo vartotojo vardą ir paspauskite Enter. Dabar jūs laikotės mano patarimo aukščiau ir skambinate skaityti naudodami didelį buferį, pvz., 256 baitus.

Jei skaitymas veikė taip, kaip su failais, prieš grįždamas jis laukė, kol vartotojas suvers 256 simbolius! Jūsų vartotojas laukė amžinai ir, deja, užmušė jūsų programą. Tai tikrai ne tai, ko norite, ir turėtumėte didelę problemą.

Gerai, galite skaityti po vieną baitą, bet šis sprendimas yra siaubingai neefektyvus, kaip sakiau aukščiau. Tai turi veikti geriau.

Tačiau, norėdami išvengti šios problemos, „Linux“ kūrėjai manė, kad skaityti kitaip:

  • Kai skaitote įprastus failus, jis bando kiek įmanoma daugiau perskaityti skaičiavimo baitus ir, jei to reikia, aktyviai gaus baitus iš disko.
  • Visiems kitiems failų tipams jis grįš kuo greičiau yra tam tikrų duomenų ir labiausiai skaičiuoti baitus:
    1. Terminalams tai apskritai vartotojui paspaudus Enter klavišą.
    2. TCP lizdų atveju, kai tik jūsų kompiuteris ką nors gauna, nesvarbu, kokio baitų jis gauna.
    3. FIFO ar vamzdžių atveju tai paprastai yra ta pati suma, kurią parašė kita programa, tačiau „Linux“ branduolys vienu metu gali pristatyti mažiau, jei tai yra patogiau.

Taigi galite saugiai skambinti naudodamiesi 2 KiB buferiu nelikdami amžinai užrakinti. Atkreipkite dėmesį, kad programa taip pat gali būti pertraukta, jei programa gauna signalą. Kadangi skaityti iš visų šių šaltinių gali užtrukti kelias sekundes ar net valandas - kol kita pusė nenuspręs rašyti - signalų pertraukimas leidžia nebeužsibūti per ilgai.

Tačiau tai taip pat turi trūkumą: kai norite tiksliai perskaityti 2 KiB su šiais specialiais failais, turėsite patikrinti „read“ grąžinimo vertę ir skambinti skaityti kelis kartus. perskaitytas retai užpildys visą jūsų buferį. Jei jūsų programa naudoja signalus, taip pat turėsite patikrinti, ar skaityti nepavyko su -1, nes jį pertraukė signalas, naudodami errno.

Leiskite man parodyti, kaip gali būti įdomu naudoti šią ypatingą savybę skaityti:

#define _POSIX_C_SOURCE 1 / * žymėjimas negalimas be šio # apibrėžti. * /
# įtraukti
# įtraukti
# įtraukti
# įtraukti
# įtraukti
# įtraukti
/ *
* „isSignal“ nurodo, ar skaitytas sistemos skambutis buvo nutrauktas signalu.
*
* Grąžina TRUE, jei skaitytas sistemos skambutis buvo nutrauktas signalu.
*
* Visuotiniai kintamieji: jis rašo errno apibrėžtą errno.h
* /
nepasirašytas int isSignal (const ssize_t readStatus)
grįžti (readStatus == -1 && errno == EINTR);

nepasirašytas int isSyscallSuccessful (const ssize_t readStatus)
grąžinti readStatus> = 0;

/ *
* shouldRestartRead nurodo, kai skaitytą sistemos skambutį nutraukė a
* signalo įvykis ar ne, ir atsižvelgiant į tai, kad ši „klaidos“ priežastis yra laikina, galime
* saugiai iš naujo paleiskite skaitytą skambutį.
*
* Šiuo metu jis tikrina tik tai, ar skaitymas buvo nutrauktas signalu, bet taip
* gali būti patobulinta, norint patikrinti, ar buvo perskaitytas tikslinis baitų skaičius ir ar jis yra
* ne tas atvejis, grąžinkite TRUE skaityti dar kartą.
*
* /
nepasirašytas int shouldRestartRead (const ssize_t readStatus)
return isSignal (readStatus);

/ *
* Mums reikia tuščio tvarkytuvo, nes perskaitytas sistemos skambutis bus nutrauktas tik tuo atveju, jei
* signalas tvarkomas.
* /
void emptyHandler (int ignoruojamas)
grįžti;

int main ()
/ * Yra per kelias sekundes. * /
const int alarmInterval = 5;
const struct sigaction blankSigaction = emptyHandler;
char lineBuf [256] = 0;
ssize_t readStatus = 0;
nepasirašytas int laukimo laikas = 0;
/ * Nekeiskite ženklų, išskyrus tuos atvejus, kai tiksliai žinote, ką darote. * /
sigaction (SIGALRM, & emptySigaction, NULL);
aliarmas (alarmInterval);
įvestys ("Jūsų tekstas: \ n", stderr);
padaryti
/ * Nepamirškite „\ 0“ * /
readStatus = skaityti (STDIN_FILENO, lineBuf, sizeof (lineBuf) - 1);
if (isSignal (readStatus))
waitTime + = alarmInterval;
aliarmas (alarmInterval);
fprintf (stderr, "% u sek. neveikimo ... \ n", waitTime);

while (turėtųRestartRead (readStatus));
if (isSyscallSuccessful (readStatus))
/ * Nutraukite eilutę, kad išvengtumėte klaidos teikiant ją fprintf. * /
lineBuf [readStatus] = '\ 0';
fprintf (stderr, "Jūs įvedėte% lu simbolius. Štai jūsų eilutė: \ n% s \ n ", strlen (lineBuf),
lineBuf);
Kitas
perror („Nepavyko skaityti iš stdin“);
grįžti EXIT_FAILURE;

grąžinti EXIT_SUCCESS;

Vėlgi, tai yra visa C programa, kurią galite sudaryti ir iš tikrųjų paleisti.

Tai daro taip: nuskaito eilutę iš standartinės įvesties. Tačiau kas 5 sekundes jis išspausdina eilutę, kurioje vartotojui sakoma, kad dar nebuvo įvestas įvestis.

Pavyzdys, jei laukiu 23 sekundes, kol įvesiu „Penguin“:

$ alarm_read
Tavo tekstas:
5 sekundės neveikimo ..
10 sekundžių neveikimo ..
15 sekundžių neveikimo ..
20 sekundžių neveikimo ..
Pingvinas
Jūs įvedėte 8 simbolius. Štai jūsų eilutė:
Pingvinas

Tai nepaprastai naudinga. Jis gali būti naudojamas dažnai atnaujinant vartotojo sąsają, kad būtų išspausdinta jūsų programos skaitymo ar apdorojimo eiga. Jis taip pat gali būti naudojamas kaip skirtasis laikas. Taip pat galite nutraukti bet kokį kitą signalą, kuris gali būti naudingas jūsų programai. Bet kokiu atveju tai reiškia, kad jūsų programa dabar gali būti jautri, užuot likusi įstrigusi amžinai.

Taigi nauda yra didesnė už aukščiau aprašytą trūkumą. Jei įdomu, ar turėtumėte palaikyti specialius failus programoje, paprastai dirbančioje su įprastais failais - ir taip skambina skaityti kilpoje - Sakyčiau, darykite tai, išskyrus atvejus, kai skubate, mano asmeninė patirtis dažnai įrodė, kad failą pakeitus pypke ar FIFO, tiesiogine to žodžio prasme, programa gali būti daug naudingesnė su nedidelėmis pastangomis. Internete yra net iš anksto parengtų C funkcijų, kurios įgyvendina tą kilpą jums: jos vadinamos perskaitytomis funkcijomis.

Išvada

Kaip matote, vartojimas ir skaitymas gali atrodyti panašiai, bet ne taip. Vos tik nedaug pakeitus, kaip skaitytojas veikia C kūrėjui, skaityti yra daug įdomiau kuriant naujus problemų, su kuriomis susiduriate kurdami programą, sprendimus.

Kitą kartą aš jums pasakysiu, kaip veikia „syscall“ rašymas, nes skaityti yra puiku, bet mokėti tai padaryti yra daug geriau. Tuo tarpu eksperimentuokite su skaitymu, susipažinkite su juo ir linkiu laimingų Naujųjų metų!

Kaip atsisiųsti ir paleisti „Sid Meier Civilization VI“ sistemoje „Linux“
Įvadas į žaidimą „Civilization 6“ yra šiuolaikinė klasikinės koncepcijos, pristatytos „Age of Empires“ žaidimų serijoje, koncepcija. Idėja buvo gana p...
Kaip įdiegti ir žaisti „Doom“ sistemoje „Linux“
Įvadas į Doom „Doom“ serija atsirado 90-aisiais, išleidus originalų „Doom“. Tai buvo tiesioginis hitas, o nuo to laiko žaidimų serija gavo daugybę apd...
„Vulkan“, skirta „Linux“ vartotojams
Kiekvienos naujos kartos grafikos plokštės matome, kaip žaidimų kūrėjai peržengia grafinės ištikimybės ribas ir artėja prie fotorealizmo. Nepaisant vi...